Один сервер прощает то, чего не прощает кластер
На одном сервере транзакция либо целиком закоммичена, либо целиком откачена, и порядок изменений однозначен. Разнесли данные по узлам - и каждое из этих свойств приходится покупать отдельно, обычно за задержку или за риск.
Two-phase commit (2PC)
PREPARE TRANSACTION 'имя' разбивает COMMIT на два шага: сначала все
узлы голосуют «готов зафиксировать» и доводят запись до durable, потом
координатор шлёт всем COMMIT PREPARED. Так несколько баз
фиксируют изменение атомарно.
Ловушка - между фазами. Prepared-транзакция уже не откатится сама: она ждёт решения координатора и держит все свои блокировки и свой xmin. Если координатор упал и не пришёл с вердиктом, она висит. А пока висит:
- её блокировки никто не снимет;
- её xmin держит горизонт - vacuum не чистит мёртвые версии во всём кластере, копится bloat;
max_prepared_transactionsограничен, повисшие забивают лимит.
-- повисшие 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: при разрыве сети (Partition) между узлами выбираешь либо отвечать на запросы и рисковать рассогласованием (Availability), либо отказывать ради согласованности (Consistency). Третьего во время разрыва нет.
PACELC честнее: разрыв (P) - выбор A или C; а Else (когда сети нормальные) - всё равно выбор между задержкой (Latency) и согласованностью (C). Синхронный standby - это явный выбор C над L: каждый COMMIT ждёт подтверждения и потому медленнее. Асинхронный - выбор L над C: быстро, но реплика отстаёт и при падении primary можно потерять хвост транзакций.
Связано с streaming-replication (синхронный против асинхронного) и logical-replication (откуда берутся конфликты записи).