Режимы репликации
Patroni использует потоковую репликацию PostgreSQL. Подробнее см. документацию Postgres . По умолчанию Patroni настраивает PostgreSQL на асинхронную репликацию. Выбор схемы репликации зависит от требований бизнеса. Изучите асинхронную и синхронную репликацию, а также другие решения HA, чтобы выбрать подходящий вариант.
Долговечность в асинхронном режиме
В асинхронном режиме ради доступности кластер может потерять часть зафиксированных транзакций. При отказе или недоступности первичного сервера Patroni автоматически повышает достаточно исправный резервный сервер до первичного. Транзакции, не реплицированные на этот резервный сервер, остаются в «ответвившейся временной шкале» первичного и фактически невосстановимы1.
Объём транзакций, которые могут быть потеряны, контролируется параметром maximum_lag_on_failover. Поскольку позиция журнала транзакций первичного сервера не измеряется в реальном времени, в худшем случае при переключении теряется maximum_lag_on_failover байтов журнала плюс объём, записанный за последние ttl секунд (в среднем за loop_wait/2 секунд). Однако типичная задержка репликации в стабильном состоянии значительно меньше секунды.
По умолчанию при выборах лидера Patroni не учитывает текущую временную шкалу реплик, что иногда нежелательно. Чтобы узел с временной шкалой, отличной от прежнего первичного сервера, не стал новым лидером, задайте параметру check_timeline значение true.
Синхронная репликация PostgreSQL
С Patroni можно использовать синхронную репликацию Postgres. Она обеспечивает согласованность кластера, подтверждая запись на вторичный сервер до возврата успешного результата подключённому клиенту. Цена синхронной репликации — повышенная задержка и сниженная пропускная способность записи, полностью зависящая от производительности сети.
В размещённых окружениях центров обработки данных, например AWS, Rackspace или любой неподконтрольной сети, синхронная репликация значительно повышает изменчивость производительности записи. Если последователи становятся недоступны лидеру, лидер фактически переходит в режим только для чтения.
Для простого теста синхронной репликации добавьте следующие строки в раздел parameters файлов конфигурации YAML:
При синхронной репликации PostgreSQL используйте не менее трёх узлов данных Postgres, чтобы сохранить доступность записи при отказе одного узла.
Синхронная репликация PostgreSQL не гарантирует отсутствие потерь транзакций при любых обстоятельствах. Если первичный и вторичный сервер, служащий синхронной репликой, отказывают одновременно, будет повышен третий узел, который может содержать не все транзакции.
Синхронный режим
Для сценариев, где потеря зафиксированных транзакций недопустима, включите synchronous_mode Patroni. При включённом synchronous_mode Patroni не повышает резервный сервер, пока не убедится, что тот содержит все транзакции, для которых клиент мог получить успешный статус фиксации2. Поэтому система может быть недоступна для записи, хотя часть серверов работает. Системные администраторы всё ещё могут вручную переключить резервный сервер при отказе, даже если это приведёт к потере транзакций.
Включение synchronous_mode не гарантирует долговечность фиксаций на нескольких узлах при любых обстоятельствах. Если подходящего резервного сервера нет, первичный всё равно принимает записи, но не гарантирует их репликацию. При отказе первичного сервера в этом режиме резервный сервер не повышается. Когда прежний первичный узел возвращается, он повышается автоматически, если системный администратор не выполнил ручное переключение. Благодаря этому синхронный режим применим в кластерах из 2 узлов.
Если при включённом synchronous_mode
резервный сервер отказывает, фиксации блокируются до следующей итерации Patroni, которая переводит первичный сервер в автономный режим (задержка записи в худшем случае — ttl секунд, в среднем — loop_wait/2 секунд). Ручная остановка или перезапуск резервного сервера не прерывает службу фиксации: до остановки PostgreSQL резервный сервер сообщает первичному, что освобождается от обязанностей синхронной реплики.
Если необходимо гарантировать долговечное хранение каждой записи как минимум на двух узлах, в дополнение к synchronous_mode
включите synchronous_mode_strict. Этот параметр не позволяет Patroni отключать синхронную репликацию на первичном сервере при отсутствии подходящих резервных кандидатов, если только транзакция Postgres явно не отключила synchronous_commit; все клиентские запросы записи блокируются до появления хотя бы одной синхронной реплики.
Когда synchronous_mode_strict включён и активные соединения репликации не удовлетворяют минимальному коэффициенту репликации, Patroni определяет synchronous_standby_names следующим образом:
Последние известные синхронные узлы доступны в ключе
/syncDCS: Patroni задаёт или сохраняет вsynchronous_standby_namesуказанные там узлы. Например, если/syncсодержитleader=node1, sync_standby=node2,node3и оба резервных сервера прекращают потоковую передачу, Patroni продолжает использовать:Эти узлы последними получили последнюю известную фиксацию. Фиксации блокируются, пока хотя бы один из них не подключится снова.
Ручное переключение на асинхронный узел: когда повышается узел, которого не было в ключе
/sync, например черезpatronictl failover --force, значениеsynchronous_standby_namesустанавливается равным прежнему первичному серверу, поскольку только он гарантированно содержит последние зафиксированные данные.Ключ
/syncпуст: например, строгий режим только что включён или кластер недавно инициализирован и ещё не имеет реплик. Patroni задаёт:Это встроенное сторожевое значение, не совпадающее ни с одним реальным именем узла. Оно блокирует все записи, пока подходящая реплика не начнёт потоковую передачу с первичного сервера. Заполнитель заменяет прежний шаблон
*, который мог непреднамеренно позволить неподходящему узлу выполнить требование синхронизации.
Значение __patroni_strict_sync_replica_placeholder__ зарезервировано Patroni и не должно использоваться как name узла Patroni в patroni.yaml. С таким именем Patroni откажется запускаться.
При активном строгом режиме Patroni выдаёт предупреждение журнала: "No active replication connections and synchronous_mode_strict is requested. Commits will be delayed." Оно выводится один раз на событие активации, а не при каждой итерации цикла HA.
Чтобы резервный сервер никогда не становился синхронным, задайте тегу nosync значение true. Это рекомендуется для резервных серверов за медленными сетевыми соединениями, которые снизили бы производительность в роли синхронной реплики. Тег nostream, равный true, даёт тот же эффект.
Синхронный режим можно включать и отключать командой patronictl edit-config или через REST-интерфейс Patroni. Инструкции приведены в разделе динамическая конфигурация
.
Примечание: из-за реализации синхронной репликации в PostgreSQL транзакции можно потерять даже с synchronous_mode_strict. Если бэкенд PostgreSQL отменён в ожидании подтверждения репликации, например из-за отмены пакета при тайм-ауте клиента или отказе бэкенда, изменения транзакции становятся видимыми другим бэкендам. Они ещё не реплицированы и могут быть потеряны при повышении резервного сервера.
Коэффициент синхронной репликации
Параметр synchronous_node_count управляет количеством синхронных резервных баз данных в Patroni. По умолчанию он равен 1 и не действует, если synchronous_mode
равен off. При включении Patroni поддерживает точное количество синхронных резервных баз по synchronous_node_count и корректирует состояние в DCS и synchronous_standby_names PostgreSQL при присоединении и выходе участников. Если значение превышает количество подходящих узлов, Patroni автоматически его уменьшает.
Максимальное отставание синхронного узла
По умолчанию Patroni сохраняет узлы, объявленные synchronous согласно представлению pg_stat_replication, даже если другие узлы опережают их. Это уменьшает количество изменений synchronous_standby_names. Поведение можно изменить параметром maximum_lag_on_syncnode, который определяет допустимое отставание реплики, всё ещё считающейся «синхронной».
Если резервных серверов несколько, Patroni использует максимальный LSN реплики, иначе — текущий LSN wal лидера. По умолчанию значение равно -1; при значении 0 или меньше Patroni не заменяет неисправный синхронный резервный сервер. Задайте достаточно высокое значение, чтобы Patroni не менял синхронные реплики слишком часто при большом объёме транзакций.
Реализация синхронного режима
В синхронном режиме Patroni хранит в DCS, в ключе /sync, состояние синхронизации с последним первичным сервером и текущими синхронными резервными базами. Состояние обновляется со строгими ограничениями порядка, обеспечивая следующие инварианты:
- Узел должен быть отмечен как последний лидер всякий раз, когда он может принимать транзакции записи. Отказ Patroni или незавершённая остановка PostgreSQL могут нарушить этот инвариант.
- Узел должен быть задан синхронным резервным сервером PostgreSQL, пока он опубликован как синхронный резервный сервер в ключе
/syncDCS. - Узел, не являющийся лидером или текущим синхронным резервным сервером, не может автоматически повысить себя.
Patroni назначает в synchronous_standby_names один или несколько синхронных резервных узлов только на основе параметра synchronous_node_count.
На каждой итерации цикла HA Patroni заново оценивает выбор синхронных резервных узлов. Если узлы текущего списка подключены и не запросили снятие синхронного статуса, список сохраняется. Иначе выбираются доступные для синхронизации участники кластера, сильнее всего продвинувшиеся в репликации.
Пример:
Ключ /config в DCS
Ключ /sync в DCS
postgresql.conf
В приведённых примерах только узлы node1 и node2 считаются синхронными и могут быть автоматически повышены при отказе первичного сервера (node0).
Режим фиксации по кворуму
Начиная с PostgreSQL v10 Patroni поддерживает синхронную репликацию на основе кворума.
В этом режиме Patroni хранит в DCS состояние синхронизации с последним известным первичным сервером, количеством узлов для кворума и узлами, имеющими право голоса. В стабильном состоянии голосующие узлы — лидер и все синхронные резервные серверы. Состояние обновляется со строгими ограничениями порядка повышения узлов и synchronous_standby_names, чтобы любое способное достичь кворума подмножество голосующих всегда содержало хотя бы один узел с последней успешной фиксацией.
На каждой итерации цикла HA Patroni заново оценивает выбор синхронных резервных серверов и кворум по доступности узлов и запрошенной конфигурации кластера. В версиях PostgreSQL выше 9.6 все подходящие узлы добавляются как синхронные резервные серверы, как только их репликация догоняет лидера.
Фиксация по кворуму снижает задержку в худшем случае даже при нормальной работе, поскольку высокая задержка репликации на один резервный сервер компенсируется другими.
Синхронный режим на основе кворума включается установкой synchronous_mode
в quorum командой patronictl edit-config или через REST-интерфейс Patroni. Инструкции приведены в разделе динамическая конфигурация
.
Остальные параметры, включая synchronous_node_count, maximum_lag_on_syncnode и synchronous_mode_strict, работают так же, как при synchronous_mode=on.
Если в режиме фиксации по кворуму с synchronous_mode_strict нет активных реплик, Patroni задаёт synchronous_standby_names как ANY N (<last known voters>), сохраняя последних известных голосующих из /sync, либо как ANY 1 (__patroni_strict_sync_replica_placeholder__), если в ключе /sync нет голосующих.
Пример:
Ключ /config в DCS
Ключ /sync в DCS
postgresql.conf
При отказе первичного сервера (node0) в приведённом примере два узла из node1, node2, node3 получат последнюю транзакцию, но неизвестно какие. Чтобы определить, получил ли её node1, нужно сравнить его LSN с LSN как минимум одного узла (quorum=1 в ключе /sync) из node2 и node3. Если node1 не отстаёт хотя бы от одного из них, можно гарантировать отсутствие видимой пользователю потери данных при повышении node1.
Данные всё ещё существуют, но для их извлечения требуется ручная работа специалистов по восстановлению. Если Patroni разрешено перематывать состояние с
use_pg_rewind, ответвившаяся временная шкала автоматически удаляется, чтобы снова присоединить отказавший первичный сервер к кластеру. Для правильной работыuse_pg_rewindкластер должен быть инициализирован сdata page checksums(параметр--data-checksumsдляinitdb) и/илиwal_log_hintsдолжен быть равенon. ↩︎Клиенты могут изменять поведение отдельных транзакций параметром PostgreSQL
synchronous_commit. Транзакции со значениямиsynchronous_commitoffиlocalмогут быть потеряны при переключении, но не блокируются задержкой репликации. ↩︎