# Распределённые ловушки: 2PC, multi-master, CAP _Репликация · PostgreSQL Knowledge Base_ **TL;DR:** Как только данные живут на нескольких серверах, появляются проблемы, которых не было на одном. 2PC даёт атомарность поверх узлов, но повисший prepared-транзакшн держит блокировки и горизонт. Самодельный multi-master ловит конфликты записи, которые некому разрешить. CAP/PACELC объясняют, чем именно приходится жертвовать. ## Один сервер прощает то, чего не прощает кластер На одном сервере транзакция либо целиком закоммичена, либо целиком откачена, и порядок изменений однозначен. Разнесли данные по узлам - и каждое из этих свойств приходится покупать отдельно, обычно за задержку или за риск. ## Two-phase commit (2PC) `PREPARE TRANSACTION 'имя'` разбивает COMMIT на два шага: сначала все узлы голосуют «готов зафиксировать» и доводят запись до durable, потом координатор шлёт всем `COMMIT PREPARED`. Так несколько баз фиксируют изменение атомарно. Ловушка - между фазами. Prepared-транзакция уже не откатится сама: она ждёт решения координатора и держит все свои блокировки и свой xmin. Если координатор упал и не пришёл с вердиктом, она висит. А пока висит: - её блокировки никто не снимет; - её xmin держит горизонт - vacuum не чистит мёртвые версии во всём кластере, копится bloat; - `max_prepared_transactions` ограничен, повисшие забивают лимит. ```sql -- повисшие prepared-транзакции; разрешаются вручную SELECT gid, prepared, owner, database FROM pg_prepared_xacts; -- ROLLBACK PREPARED 'gid'; -- или COMMIT PREPARED 'gid'; ``` Поэтому 2PC без надёжного координатора (или мониторинга `pg_prepared_xacts`) опаснее, чем кажется. ## Самодельный multi-master Соблазн: настроить логическую репликацию в обе стороны и писать в любой узел. На демо работает. В проде ломается на первом же конфликте: - **Конфликт первичного ключа.** Два узла выдали `id = 1001` разным строкам (последовательности независимы). Прилетела чужая строка с уже занятым ключом - apply-воркер встал. - **Конфликт записи.** Одну строку обновили на обоих узлах одновременно. Кто прав? Встроенного разрешения у PostgreSQL нет - репликация по этой подписке просто останавливается до ручного вмешательства. - **Циклы.** Без аккуратной настройки origin изменение уезжает на соседа и возвращается обратно бесконечно. Вывод не «нельзя», а «это отдельная распределённая система со своей политикой разрешения конфликтов, а не галочка в конфиге». Если нужен multi-master - берут специализированное решение, а не два `CREATE SUBSCRIPTION` навстречу друг другу. ## CAP и PACELC одной фразой CAP: при разрыве сети (**P**artition) между узлами выбираешь либо отвечать на запросы и рисковать рассогласованием (**A**vailability), либо отказывать ради согласованности (**C**onsistency). Третьего во время разрыва нет. PACELC честнее: разрыв (**P**) - выбор A или C; а **E**lse (когда сети нормальные) - всё равно выбор между задержкой (**L**atency) и согласованностью (**C**). Синхронный standby - это явный выбор C над L: каждый COMMIT ждёт подтверждения и потому медленнее. Асинхронный - выбор L над C: быстро, но реплика отстаёт и при падении primary можно потерять хвост транзакций. Связано с [streaming-replication](/courses/postgres/kb/streaming-replication.md) (синхронный против асинхронного) и [logical-replication](/courses/postgres/kb/logical-replication.md) (откуда берутся конфликты записи). ## Команды ```sql SELECT gid, prepared, owner FROM pg_prepared_xacts; ``` Повисшие prepared-транзакции - держат блокировки и горизонт ```sql SHOW max_prepared_transactions; ``` Лимит одновременных prepared-транзакций (0 - 2PC выключен) ```sql ROLLBACK PREPARED 'gid'; ``` Снять повисшую prepared-транзакцию вручную, освободив её блокировки ## См. также - [Физическая (потоковая) репликация](/courses/postgres/kb/streaming-replication.md) - [Логическая репликация (publication/subscription)](/courses/postgres/kb/logical-replication.md)