Транзакции в распределённых системах
Зачем
Пусть у нас есть несколько узлов (процессов), которых хранят какие-то непересекающиеся данные. Например, на одном узле хранятся банковские счета пользователей на "А", на другом — на "Б", и так далее.
Тогда мы можем хотеть транзакционно изменять данные на разных узлах (см. BEGIN TRANSACTION
и COMMIT TRANSACTION
в SQL).
Определение: |
Транзакция — это единица работы над множеством элементов из базы данных, которую можно в процессе работы целиком отменить (либо сама база данных, либо пользователь, см. ROLLBACK TRANSACTION ), либо подтвердить (база данных может иногда не справиться, тогда транзакция отменяется). |
У транзакции обычно выделяют свойства по аббревиатуре ACID:
- Atomicity (атомарность) — транзакция либо полностью применила все свои изменения, либо полностью откатилась (отменилась)
- Consitency (согласованность) — в конце транзакции система находится в согласованном состоянии
- Isolation (изолированность) — параллельные транзакции не должны влиять друг на друга (например, при помощи phantom reads), а должны выполняться как будто последовательно
- Durability (надёжность) — завершённые (commited) транзакции сохраняются даже в случае сбоев и перезапуска системы
Атомарность и надёжность
Предположим, что нам нужны только атомарность и надёжность. Самое сложное — откатывать транзакцию, если что-то пошло не так. Есть два основных способа этого добиться.
Undo log
Каждый узел сначала записывает предыдущие значения в надёжный журнал (лог, обычно append-only и сохраняется на диск), а только потом изменяет состояние в памяти. Когда транзакция подтверждается, надо записать произведённые изменения в надёжное место и можно стереть кусок журнала. Если транзакцию надо откатить (например, после перезапуска системы в журнале нет записи "транзакция успешна"), то мы идём с конца журнала и восстанавливаем старые значения.
Redo log
Мы вообще не делаем изменения в данных до подтверждения транзакции, а просто пишем в журнал все операции, которые надо произвести с данными. Когда транзакция успешно завершается, у нас две опции:
- Изменить данные прямо на диске.
- Записать об этом в журнал на диск (append-only), а состояние просто каждый раз восстанавливать из этого журнала. Иногда делать checkpoint'ы для сохранения состояния. Это сейчас на практике популярнее.