Комментарии (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 гарантирует:
- Данные, однажды закоммитованные, никогда не потеряются
- Даже при сбое системы, данные восстановятся
- Используется Write-Ahead Logging (WAL)
- Требует синхронизации данных на физический диск
- Есть трейд-офф между скоростью и надёжностью
Механизмы:
- WAL (Write-Ahead Logging)
- REDO/UNDO логи
- Fsync (синхронизация на диск)
- Восстановление (Recovery) после сбоя
- Репликация (для дополнительной надёжности)