Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
ACID: буква D — Durability (Долговечность/Надёжность)
Durability (Долговечность) — это третья буква в аббревиатуре ACID, которая гарантирует, что успешно завершённая транзакция сохраняется навсегда, даже в случае сбоя системы (потеря питания, крах диска, отключение сервера).
Что такое ACID?
ACID — набор свойств транзакций БД:
A - Atomicity (Атомарность)
Либо всё, либо ничего
C - Consistency (Консистентность)
Данные в допустимом состоянии
I - Isolation (Изоляция)
Транзакции не мешают друг другу
D - Durability (Долговечность)
Сохранённые данные не потеряются
Понимание Durability
# Пример: перевод денег между счётами
BEGIN TRANSACTION
SELECT balance FROM accounts WHERE id = 1
# Balance = 1000
UPDATE accounts SET balance = balance - 100 WHERE id = 1
# Account 1: 900
UPDATE accounts SET balance = balance + 100 WHERE id = 2
# Account 2: 1100
COMMIT # Транзакция завершена
После COMMIT:
❌ БЕЗ Durability:
- COMMIT выполнен
- Данные в памяти
- Сервер падает
- Данные потеряны
✓ С Durability:
- COMMIT выполнен
- Данные записаны на диск
- Данные в WAL (Write-Ahead Log)
- Сервер падает
- При перезагрузке данные восстановлены
- Деньги на месте: счёт 1 = 900, счёт 2 = 1100
Как реализуется Durability?
1. Write-Ahead Log (WAL)
Процесс:
1. Транзакция COMMIT
2. БД записывает лог на диск
"Transfer: 100 from account 1 to account 2"
3. Затем обновляет данные в памяти
4. В фоне сбрасывает на диск
Если крах на шаге 3:
- Лог на диске → можно восстановить
- Данные в памяти потеряны → восстановим из лога
Если крах на шаге 4:
- И лог и данные на диске → ничего не потеряется
2. fsync() — запись на диск
# На уровне ОС
import os
# Обычное открытие файла
f = open('data.txt', 'w')
f.write('important data') # ← В буфере, не на диске!
# Может потеряться при крахе!
# Правильно: flush + fsync
f.write('important data')
f.flush() # Выводим из буфера приложения
os.fsync(f.fileno()) # Выводим из буфера ОС на диск
# Теперь данные ДЕЙСТВИТЕЛЬНО на диске
3. Уровни Durability
# PostgreSQL synchronous_commit
# synchronous_commit = off (НЕБЕЗОПАСНО)
COMMIT выполняется сразу
Данные только в памяти
У меньше задержек
Риск потери данных высокий
# synchronous_commit = local (БЕЗОПАСНЕЕ)
Данные записаны в WAL на диск
Копии на реплику НЕ требуются
Сбалансированный вариант
# synchronous_commit = on (БЕЗОПАСНО)
Данные записаны на диск
Данные отправлены на реплику
Медленнее, но очень надёжно
# synchronous_commit = remote_apply (МАКСИМАЛЬНО БЕЗОПАСНО)
Данные применены на реплике
Евро-стандарт для финтеха
Очень медленно, но абсолютная надёжность
Практический пример: Обработка платежей
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Account(Base):
__tablename__ = "accounts"
id = Column(Integer, primary_key=True)
name = Column(String)
balance = Column(Integer)
class PaymentLog(Base):
__tablename__ = "payment_log"
id = Column(Integer, primary_key=True)
from_account = Column(Integer)
to_account = Column(Integer)
amount = Column(Integer)
status = Column(String) # 'pending', 'completed', 'failed'
engine = create_engine(
'postgresql://user:pass@localhost/bank_db',
# Максимальная безопасность для банка
connect_args={'connect_timeout': 10}
)
Session = sessionmaker(bind=engine)
def transfer_money(from_id: int, to_id: int, amount: int) -> bool:
"""
Безопасный перевод денег с ACID гарантиями.
Durability обеспечивает, что если COMMIT прошёл —
данные не потеряются даже при крахе.
"""
session = Session()
try:
# Логируем начало платежа
log_entry = PaymentLog(
from_account=from_id,
to_account=to_id,
amount=amount,
status='pending'
)
session.add(log_entry)
session.flush() # Убедиться что лог в БД
# Проверяем баланс
from_account = session.query(Account).filter(
Account.id == from_id
).with_for_update().first() # Lock
if not from_account or from_account.balance < amount:
session.rollback()
return False
# Выполняем транзакцию
from_account.balance -= amount
to_account = session.query(Account).filter(
Account.id == to_id
).with_for_update().first()
to_account.balance += amount
# Обновляем статус лога
log_entry.status = 'completed'
# ✓ COMMIT — после этого Durability гарантирует сохранение
session.commit()
# Если здесь крах:
# - БД восстановится из WAL
# - Деньги будут переведены
# - Лог будет обновлён
return True
except Exception as e:
session.rollback() # Откатываем всё
log_entry.status = 'failed'
session.commit()
return False
finally:
session.close()
# Использование
if transfer_money(from_id=1, to_id=2, amount=100):
print("✓ Платёж выполнен и сохранён")
else:
print("✗ Платёж отклонён")
Сравнение БД по Durability
# PostgreSQL (ОЧЕНЬ НАДЁЖНАЯ)
# - WAL (Write-Ahead Log) по умолчанию
# - fsync обеспечивает запись на диск
# - Поддерживает асинхронную репликацию
engine = create_engine('postgresql://...')
# MySQL/InnoDB (НАДЁЖНАЯ)
# - InnoDB имеет свой log
# - Можно настроить innodb_flush_log_at_trx_commit
engine = create_engine('mysql+pymysql://...')
# SQLite (МЕНЬШЕ НАДЁЖНА)
# - По умолчанию не гарантирует запись на диск
# - Можно включить synchronous mode
engine = create_engine('sqlite:///data.db')
# Redis (СЛАБАЯ DURABILITY)
# - В памяти, может потерять данные
# - AOF (Append-Only File) помогает
# - Но не гарантирует полную Durability
r = redis.Redis()
# MongoDB (ЗАВИСИТ ОТ КОНФИГУРАЦИИ)
# - journaling обеспечивает Durability
# - write_concern влияет на уровень
from pymongo import MongoClient
client = MongoClient()
db = client.mydb
Практический пример: Критическая операция
import logging
from contextlib import contextmanager
logger = logging.getLogger(__name__)
@contextmanager
def safe_transaction(session):
"""
Context manager с гарантией Durability.
"""
try:
yield session
session.commit()
logger.info("✓ Транзакция успешно завершена (Durability гарантирована)")
except Exception as e:
session.rollback()
logger.error(f"✗ Транзакция отменена: {e}")
raise
finally:
session.close()
# Использование
with safe_transaction(session) as s:
# Все операции здесь атомарны
user = s.query(User).filter(User.id == 1).first()
user.email = "new@example.com"
# При commit() данные гарантированно сохранятся
Проблемы без Durability
# ❌ Сценарий 1: Крах после COMMIT
BEGIN TRANSACTION
UPDATE products SET stock = stock - 1 WHERE id = 1
COMMIT # ← Данные в памяти
# КРАХ СЕРВЕРА
# Последствия:
# - Товар не куплен (откатили? нет, тут нет откатывания)
# - Клиент заплатил, товара нет
# - Деньги потеряны
# ✓ С Durability:
# - Данные в WAL на диске
# - После перезагрузки система восстановится
# - Товар остаётся куплен
# - Все согласовано
# ❌ Сценарий 2: Потеря БД
sql_dump = "backup.sql"
# Создаём снимок БД
dump_db_to_file(sql_dump)
# Потом не восстанавливаем, а продолжаем работу
# БД сбивается, снимок устарел
# Потеряны все новые данные
Как проверить Durability в приложении
import subprocess
import time
from sqlalchemy.orm import Session
def test_durability():
"""
Тест: убедиться что данные сохранились при крахе.
"""
session = Session()
# Шаг 1: Запишем данные и COMMIT
new_item = Item(name="test_durability")
session.add(new_item)
session.commit() # ← Durability гарантирует запись
item_id = new_item.id
# Шаг 2: Симулируем крах (в реальности это может быть killall -9 postgres)
# В тесте мы просто закроем соединение
session.close()
# Шаг 3: Переподключаемся
new_session = Session()
# Шаг 4: Проверяем что данные остались
item = new_session.query(Item).filter(Item.id == item_id).first()
assert item is not None, "Данные потеряны! Durability не гарантирована"
assert item.name == "test_durability"
print("✓ Durability тест пройден")
Тонкости Durability
# Проблема 1: Асинхронная запись
# Даже если на диск записалось, может быть в cache диска
f.write(data)
f.flush() # Вывести из буфера приложения
os.fsync(f.fileno()) # Вывести из буфера ОС (ВАЖНО!)
# Проблема 2: Репликация
# Если используешь репликацию, нужно ждать репики
session.commit() # На primary OK
# Но кто гарантирует что реплика получила?
# Ответ: synchronous_commit = remote_write
# Проблема 3: Сетевые сбои
# Между COMMIT на клиенте и БД
try:
session.commit() # Может упасть
except ConnectionError:
# Но транзакция может быть уже выполнена на сервере!
# Durability не гарантирует что клиент узнает
pass
Рекомендации
# 1. Для финтеха: максимальная Durability
engine = create_engine(
'postgresql://...',
execution_options={
'synchronous_commit': 'remote_apply'
}
)
# 2. Для веб-приложения: баланс скорости и надёжности
engine = create_engine(
'postgresql://...',
execution_options={
'synchronous_commit': 'local' # Хороший компромисс
}
)
# 3. Для кэша: меньше Durability
redis_client = redis.Redis(
decode_responses=True,
write_concern={'w': 0} # Быстро, но может потеряться
)
# 4. Всегда делай резервные копии
# Durability гарантирует не потерять при сбое
# Но не гарантирует от ошибок приложения
Ключевые моменты
- Durability = успешная транзакция сохраняется навсегда
- WAL (Write-Ahead Log) основной механизм реализации
- fsync() обеспечивает запись на физический диск
- Разные БД имеют разные уровни Durability
- Асинхронная репликация может потерять последние данные
- Синхронная репликация добавляет задержку
- Без Durability потеря денег в финтехе или потеря данных
- Проверяй конфигурацию своей БД для критичных приложений