← Назад к вопросам

Как работает Durability в ACID?

2.0 Middle🔥 231 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ответ

Как работает Durability в ACID

Durability (Долговечность) — это четвёртый принцип ACID, гарантирующий, что данные, однажды записанные в БД, будут сохранены даже в случае сбоя системы. Рассмотрим в деталях, как это работает.

ACID напоминание

  • Atomicity (Атомарность) — вся транзакция или ничего
  • Consistency (Согласованность) — данные в корректном состоянии
  • Isolation (Изоляция) — транзакции не мешают друг другу
  • Durability (Долговечность) — данные сохранены надолго

1. Write-Ahead Logging (WAL)

Основной механизм реализации Durability — Write-Ahead Logging.

import java.io.*;
import java.nio.file.*;
import java.util.*;

public class WriteAheadLogging {
    private final Path logFile;
    private final List<String> commitLog;
    private final Object lock = new Object();
    
    public WriteAheadLogging(Path logPath) {
        this.logFile = logPath;
        this.commitLog = new ArrayList<>();
    }
    
    // Процесс транзакции с WAL
    public void executeTransaction(String transactionId, List<String> operations) throws Exception {
        synchronized (lock) {
            // Шаг 1: Записать REDO лог на диск ДО выполнения операций
            writeToLog("BEGIN " + transactionId);
            
            // Шаг 2: Выполнить операции в памяти
            for (String operation : operations) {
                writeToLog("EXECUTE " + operation);
                executeOperation(operation);
            }
            
            // Шаг 3: Написать COMMIT маркер на диск
            writeToLog("COMMIT " + transactionId);
            
            // Шаг 4: Синхронизировать диск (fsync)
            syncToDisk();
        }
    }
    
    private void writeToLog(String entry) throws IOException {
        commitLog.add(entry);
        Files.write(logFile, entry.getBytes(), StandardOpenOption.APPEND, StandardOpenOption.CREATE);
    }
    
    private void syncToDisk() throws IOException {
        // Гарантирует, что данные записаны на физический диск
        // В Java это сложнее, но концепция:
        // Операционная система получает сигнал fsync
    }
    
    private void executeOperation(String operation) {
        System.out.println("Executing: " + operation);
        // Выполняем операцию
    }
}

2. Восстановление после сбоя

public class DatabaseRecovery {
    private final Path logFile;
    private final Map<String, Object> database;
    
    public DatabaseRecovery(Path logPath) {
        this.logFile = logPath;
        this.database = new HashMap<>();
    }
    
    // Восстановление (Recovery) после сбоя
    public void recover() throws IOException {
        List<String> logEntries = Files.readAllLines(logFile);
        
        Map<String, TransactionState> transactions = new HashMap<>();
        
        // Фаза REDO: Повторяем все коммитированные операции
        System.out.println("Recovery: REDO phase");
        for (String entry : logEntries) {
            if (entry.startsWith("BEGIN")) {
                String txnId = entry.split(" ")[1];
                transactions.put(txnId, TransactionState.STARTED);
            } else if (entry.startsWith("EXECUTE")) {
                String operation = entry.substring(8);
                // Повторить операцию
                System.out.println("Redoing: " + operation);
            } else if (entry.startsWith("COMMIT")) {
                String txnId = entry.split(" ")[1];
                transactions.put(txnId, TransactionState.COMMITTED);
            }
        }
        
        // Фаза UNDO: Откатываем некоммитированные операции
        System.out.println("Recovery: UNDO phase");
        for (Map.Entry<String, TransactionState> entry : transactions.entrySet()) {
            if (entry.getValue() == TransactionState.STARTED) {
                System.out.println("Undoing transaction: " + entry.getKey());
            }
        }
    }
    
    enum TransactionState {
        STARTED, COMMITTED, ROLLED_BACK
    }
}

3. Durability на уровне PostgreSQL

import java.sql.*;

public class PostgreSQLDurability {
    // Транзакция с гарантией Durability
    public static void durableTransaction(Connection conn) throws SQLException {
        // Отключить автокоммит
        conn.setAutoCommit(false);
        
        Savepoint savepoint = null;
        try {
            // Записать данные в лог ПЕРЕД отправкой на диск
            String updateSQL = "UPDATE accounts SET balance = balance - 100 WHERE id = 1";
            try (Statement stmt = conn.createStatement()) {
                stmt.executeUpdate(updateSQL);
            }
            
            // Создать точку сохранения
            savepoint = conn.setSavepoint("after_update");
            
            // Если всё хорошо, коммитимся
            // WAL гарантирует, что данные на диске
            conn.commit();
            
            System.out.println("Transaction committed - data is durable");
        } catch (SQLException e) {
            if (savepoint != null) {
                conn.rollback(savepoint);
            } else {
                conn.rollback();
            }
            System.out.println("Transaction rolled back");
            throw e;
        } finally {
            conn.setAutoCommit(true);
        }
    }
}

4. Durability уровни

public class DurabilityLevels {
    // Уровень 1: In-Memory (NO DURABILITY)
    // Данные теряются при краше
    class InMemoryDatabase {
        private final Map<Integer, String> data = new HashMap<>();
        
        public void insert(int id, String value) {
            data.put(id, value);
            // Нет гарантии сохранения!
        }
    }
    
    // Уровень 2: Disk Write (PARTIAL DURABILITY)
    // Данные на диске, но могут быть корруптированы при краше
    class DiskWriteDatabase {
        private final Path dataFile;
        
        public void insert(int id, String value) throws IOException {
            String entry = id + "," + value;
            Files.write(dataFile, entry.getBytes(), StandardOpenOption.APPEND);
            // Данные на диске, но не fsync'нуты
        }
    }
    
    // Уровень 3: Disk Sync (FULL DURABILITY)
    // Данные синхронизированы на диск - максимум гарантий
    class DiskSyncDatabase {
        private final RandomAccessFile dataFile;
        
        public void insert(int id, String value) throws IOException {
            String entry = id + "," + value + System.lineSeparator();
            dataFile.writeBytes(entry);
            dataFile.getFD().sync();  // Синхронизировать на диск
            // Полная гарантия durability!
        }
    }
}

5. Redo/Undo Logs

public class RedoUndoLogs {
    static class RedoLog {
        // REDO лог: "что делать если сбой после коммита"
        // Используется для повторения операций
        private final List<String> redoLogEntries;
        
        public void writeRedo(String operation) {
            // Пример: "UPDATE accounts SET balance = 500 WHERE id = 1"
            redoLogEntries.add(operation);
        }
        
        // При восстановлении: повторяем ВСЕ redo-операции коммитированных транзакций
        public void redoAllOperations() {
            for (String operation : redoLogEntries) {
                System.out.println("Redo: " + operation);
                // Выполнить операцию заново
            }
        }
    }
    
    static class UndoLog {
        // UNDO лог: "как откатить операцию"
        // Используется для откатки незаконченных транзакций
        private final List<String> undoLogEntries;
        
        public void writeUndo(String operation) {
            // Пример: "UPDATE accounts SET balance = 600 WHERE id = 1" (старое значение)
            undoLogEntries.add(operation);
        }
        
        // При восстановлении: откатываем НЕЗАКОНЧЕННЫЕ транзакции
        public void undoUncommittedOperations() {
            for (int i = undoLogEntries.size() - 1; i >= 0; i--) {
                System.out.println("Undo: " + undoLogEntries.get(i));
                // Откатить операцию
            }
        }
    }
}

6. Практический пример: Транзакция с Durability

import java.io.*;
import java.nio.file.*;
import java.util.*;

public class DurableTransaction {
    private final Path logDirectory;
    private long transactionCounter = 0;
    
    public DurableTransaction(Path logDir) throws IOException {
        this.logDirectory = logDir;
        Files.createDirectories(logDir);
    }
    
    public class TransactionExecutor {
        private final String txnId;
        private final List<String> operations = new ArrayList<>();
        private boolean isCommitted = false;
        
        public TransactionExecutor(String txnId) {
            this.txnId = txnId;
        }
        
        // Фаза 1: Запись логов
        public void addOperation(String operation) throws IOException {
            operations.add(operation);
            writeLog("OPERATION: " + operation);
        }
        
        // Фаза 2: Коммит с гарантией durability
        public void commit() throws IOException {
            writeLog("COMMIT START");
            
            // Выполнить все операции
            for (String op : operations) {
                System.out.println("Executing: " + op);
            }
            
            // Гарантировать физическую запись на диск
            writeLog("COMMIT END");
            isCommitted = true;
            
            System.out.println("Transaction " + txnId + " is durable");
        }
        
        // Фаза 3: Откат если нужно
        public void rollback() throws IOException {
            writeLog("ROLLBACK");
            isCommitted = false;
            System.out.println("Transaction " + txnId + " rolled back");
        }
        
        private void writeLog(String entry) throws IOException {
            Path logFile = logDirectory.resolve(txnId + ".log");
            String timestamp = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
                .format(new Date());
            String logEntry = "[" + timestamp + "] " + entry;
            Files.write(logFile, (logEntry + System.lineSeparator()).getBytes(), 
                StandardOpenOption.CREATE, StandardOpenOption.APPEND);
        }
    }
    
    public TransactionExecutor begin() {
        return new TransactionExecutor("TXN-" + (++transactionCounter));
    }
    
    // Пример использования
    public static void main(String[] args) throws IOException {
        DurableTransaction manager = new DurableTransaction(Paths.get("./txn_logs"));
        
        TransactionExecutor txn = manager.begin();
        txn.addOperation("INSERT INTO users VALUES (1, 'John')");
        txn.addOperation("INSERT INTO accounts VALUES (1, 1000)");
        txn.commit();  // Гарантирует durability
        
        System.out.println("\nAll data is persistent and will survive system crash");
    }
}

7. Durability vs Performance

public class DurabilityPerformanceTradeoff {
    // Максимум Durability (медленно)
    public static void maxDurability(Connection conn) throws SQLException {
        conn.setAutoCommit(false);
        // Синхронное коммитирование
        conn.commit();  // Ждёт пока данные физически запишутся на диск
        // Много времени, но 100% гарантия
    }
    
    // Быстро, но меньше гарантий
    public static void asyncDurability(Connection conn) throws SQLException {
        conn.setAutoCommit(true);
        // Асинхронное коммитирование
        // Данные скоро запишутся, но не гарантировано
        // Риск потери данных при краше
    }
    
    // Баланс
    public static void balancedDurability(Connection conn) throws SQLException {
        conn.setAutoCommit(false);
        // Груп-коммит: несколько транзакций коммитятся вместе
        conn.commit();
        // Хороший баланс между скоростью и надёжностью
    }
}

8. Проверка Durability в базе

# PostgreSQL: проверка WAL (Write-Ahead Log)
ls -la /var/lib/postgresql/data/pg_wal/

# MySQL: проверка binlog
ls -la /var/log/mysql/mysql-bin.*

# MongoDB: проверка journal
ls -la /data/db/journal/
public class DurabilityChecking {
    public static void checkDurability() {
        // Проверить, что WAL включен
        System.out.println("Checking durability settings:");
        System.out.println("1. WAL (Write-Ahead Logging) is enabled");
        System.out.println("2. Disk sync is configured");
        System.out.println("3. Backup strategy is in place");
        System.out.println("4. Log files are being written");
        System.out.println("5. Recovery procedures are tested");
    }
}

9. Сравнение уровней Durability

УровеньГарантияСкоростьРискКогда использовать
In-MemoryНетОчень быстроПотеря при крашеCache, temp data
Async WriteНизкаяБыстроПотеря при крашеNon-critical data
Disk WriteСредняяНормальноКорруптация при крашеMost applications
Disk SyncВысокаяМедленнееОчень редкоFinancial systems
ReplicationОчень высокаяМедленнееЭкстремально редкоMission-critical

10. Best Practices для Durability

public class DurabilityBestPractices {
    public static void main(String[] args) throws SQLException {
        // 1. Используйте транзакции для atomic и durable операций
        // 2. Правильно конфигурируйте WAL
        // 3. Регулярно создавайте бэкапы
        // 4. Тестируйте восстановление после сбоя
        // 5. Мониторьте размер логов
        // 6. Используйте репликацию для критичных данных
        // 7. Проверяйте целостность данных
        // 8. Документируйте процессы восстановления
        
        System.out.println("Durability = WAL + Disk Sync + Verification");
    }
}

Выводы

Durability гарантирует:

  1. Данные, однажды закоммитованные, никогда не потеряются
  2. Даже при сбое системы, данные восстановятся
  3. Используется Write-Ahead Logging (WAL)
  4. Требует синхронизации данных на физический диск
  5. Есть трейд-офф между скоростью и надёжностью

Механизмы:

  • WAL (Write-Ahead Logging)
  • REDO/UNDO логи
  • Fsync (синхронизация на диск)
  • Восстановление (Recovery) после сбоя
  • Репликация (для дополнительной надёжности)