Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → 2PC: двухфазный коммит и его ограничения

2PC: как работает двухфазный коммит и почему его избегают

Двухфазный коммит: prepare и commit, координатор, блокировки, сценарии отказа и альтернативы — saga, outbox, идемпотентность.

Прямой ответ

Двухфазный коммит (two-phase commit, 2PC) — протокол, который обеспечивает атомарность изменения данных в нескольких независимых системах: либо фиксируются все, либо ни одного.

Участники: координатор и несколько участников (обычно базы данных или брокеры сообщений).

Фаза 1 — голосование (prepare). Координатор отправляет всем участникам команду PREPARE. Каждый участник выполняет работу, записывает намерение в устойчивый журнал, блокирует затронутые данные и отвечает «готов» или «нет». Ответ «готов» — это обязательство: участник гарантирует, что сможет зафиксировать изменения, даже если произойдёт сбой.

Фаза 2 — фиксация (commit). Если все участники ответили «готов», координатор записывает решение в свой журнал и рассылает COMMIT. Если хотя бы один ответил «нет» или не ответил — рассылает ROLLBACK. Участники применяют решение и освобождают блокировки.

-- PostgreSQL поддерживает сторону участника 2PC
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
PREPARE TRANSACTION 'order-1042-phase1';
-- ... координатор принимает решение ...
COMMIT PREPARED 'order-1042-phase1';
-- либо
-- ROLLBACK PREPARED 'order-1042-phase1';

Главная проблема протокола — блокирующее поведение. Если координатор отказал после того, как участники проголосовали «готов», они остаются в неопределённом состоянии: данные заблокированы, решение неизвестно. Участники не могут ни зафиксировать, ни откатить транзакцию самостоятельно, потому что не знают решения других. Восстановление требует вмешательства координатора или администратора.

Дополнительные ограничения:

Проблема Следствие
Единая точка отказа Координатор блокирует всех участников
Блокировки на весь протокол Пропускная способность падает
Синхронность Задержка равна сумме самого медленного участника и сети
Сетевое разделение Часть участников может зависнуть в подготовленном состоянии

Практика: когда 2PC оправдан и чем его заменяют

Где 2PC работает хорошо:

  1. Внутри одной системы с несколькими ресурсами. XA-транзакции в рамках одного приложения, где координатор надёжен и доступен.
  2. Операции с низкой частотой и высокой критичностью. Например, согласованное изменение конфигурации в нескольких хранилищах.
  3. Системы, где координатор сам реплицирован. Это снимает проблему единой точки отказа, но усложняет эксплуатацию.

Чем заменяют в распределённых сервисах:

1. Saga — последовательность локальных транзакций с компенсирующими действиями. Вместо атомарности достигается eventual consistency: если шаг не удался, выполняются отменяющие операции для уже выполненных шагов. Подробно — в статье про saga и outbox.

Зарезервировать товар → Списать оплату → Создать доставку
Отмена: Отменить доставку ← Вернуть оплату ← Снять резерв

2. Outbox — атомарная запись в основную базу и таблицу исходящих сообщений. Локальная транзакция фиксирует и изменение данных, и сообщение; отдельный процесс публикует его в брокер. Это даёт согласованность без распределённой транзакции.

3. Идемпотентность и повторы. Каждый шаг делается безопасным для повторного выполнения, поэтому сбой решается повтором, а не откатом — см. идемпотентность API.

4. Согласование через события. Состояние систем приводится к согласованности асинхронно, с проверкой и исправлением расхождений.

Рекомендации:

  1. Начинайте с вопроса: нужна ли строгая атомарность. Во многих сценариях допустима временная несогласованность с автоматическим исправлением.
  2. Если 2PC неизбежен, делайте координатор надёжным. Репликация координатора и устойчивый журнал решений — обязательные условия.
  3. Ограничивайте время подготовленного состояния. Долгая блокировка ресурсов влияет на все операции с этими данными.
  4. Мониторьте подготовленные транзакции. Транзакция, оставшаяся в состоянии prepared, — инцидент; она должна поднимать алерт.
  5. Проектируйте компенсирующие действия заранее. Если переходите на saga, отменяющая операция должна существовать для каждого шага — о паттернах в статье про распределённые транзакции.

Практический сценарий: блокировка платежей из-за отказа координатора

Контекст: списание средств и создание записи в системе учёта выполнялись через XA-транзакцию с координатором в приложении.

  1. Инцидент. Координатор перезапустился во время развёртывания. 14 транзакций остались в состоянии prepared: средства заблокированы на счетах, запись в учёте не создана.
  2. Симптомы. Пользователи видели списание, но не видели заказа. Повторная оплата отклонялась из-за блокировки.
  3. Восстановление. Администратор вручную проверил состояние каждой транзакции и выполнил ROLLBACK PREPARED для тех, где запись в учёте отсутствовала. Заняло около трёх часов.
  4. Что изменили. (а) От XA отказались в пользу outbox: списание и сообщение о нём фиксируются одной локальной транзакцией; (б) создание записи в учёте стало асинхронным с идемпотентным обработчиком; (в) добавили компенсирующее действие — возврат списания, если заказ не создан в течение 15 минут; (г) добавили мониторинг подготовленных транзакций как страховку на случай возврата к 2PC в других местах.
  5. Результат. Инциденты с блокировкой средств прекратились; при сбое обработчика расхождение устраняется автоматически в течение минут.

Вывод: 2PC даёт строгую атомарность ценой блокировок и зависимости от координатора. В системах с высокой доступностью эта цена обычно оказывается выше, чем переход к компенсирующим операциям.

FAQ

Можно ли использовать 2PC между микросервисами? Технически возможно, если каждый сервис использует базу с поддержкой XA. Практически это связывает доступность всех участников: сбой одного блокирует остальных, что противоречит цели выделения сервисов.

Чем 2PC отличается от 3PC? Трёхфазный коммит добавляет промежуточную фазу и таймауты, что снижает вероятность блокировки, но не устраняет её полностью и добавляет задержку. На практике используется редко.

Что делать, если участник ответил «готов» и потерял связь? Он обязан сохранить подготовленное состояние до получения решения. При восстановлении связи координатор сообщает решение. Если координатор недоступен постоянно, требуется ручное вмешательство.

Даёт ли saga те же гарантии, что 2PC? Нет. Saga обеспечивает eventual consistency и требует компенсирующих действий; промежуточное состояние системы видно другим участникам. Это осознанный компромисс в обмен на отсутствие глобальных блокировок.

Источники: PostgreSQL docs (PREPARE TRANSACTION), Wikipedia (two-phase commit), microservices.io (saga) — ссылки в шапке.