F.35. multimaster — синхронный кластер, который обеспечивает масштабируемость OLTP и высокую степень доступности #
multimaster
— это расширение Postgres Pro Enterprise, которое в сочетании с набором доработок ядра превращает Postgres Pro Enterprise в синхронный кластер без разделения ресурсов, который обеспечивает масштабируемость OLTP для читающих транзакций, а также высокую степень доступности с автоматическим восстановлением после сбоев.
По сравнению со стандартным кластером PostgreSQL конструкции ведущий-ведомый, в кластере, построенном с использованием multimaster
, все узлы являются ведущими. Это даёт следующие преимущества:
Устойчивость к сбоям и автоматическое восстановление узлов
Синхронная логическая репликация и репликация DDL
Масштабируемость чтения
Поддерживается работа с временными таблицами на каждом узле кластера
Незаметное для клиентов кластера multimaster обновление Postgres Pro Enterprise в пределах одной основной версии.
Важно
Прежде чем разворачивать multimaster
в производственной среде, примите к сведению ограничения, связанные с репликацией. За подробностями обратитесь к Подразделу F.35.1.
Расширение multimaster
реплицирует вашу базу данных на все узлы кластера и позволяет выполнять пишущие транзакции на любом узле. Пишущие транзакции синхронно реплицируются на все узлы, что увеличивает задержку при фиксации. Читающие транзакции и запросы выполняются локально, без каких-либо ощутимых издержек.
Для обеспечения высокой степени доступности и отказоустойчивости кластера multimaster
определяет результат каждой транзакции по алгоритму консенсуса Паксос, используя специальный протокол восстановления и контроль состояния для обнаружения сбоев. Кластер с N
ведущими узлами может продолжать работать, пока функционируют и доступны друг для друга большинство узлов. Чтобы в кластере можно было настроить multimaster
, он должен включать в себя как минимум три узла. Так как на всех узлах кластера будут одни и те же данные, обычно нет смысла делать в кластере более пяти узлов. Также поддерживается особая схема 2+1 (с рефери), в которой 2 узла содержат данные, а дополнительный узел, так называемый рефери, только участвует в голосовании. Эта схема, по сравнению с обычной схемой с тремя узлами, обходится дешевле (рефери не предъявляет больших требований к ресурсам), но её степень доступности ниже. За подробностями обратитесь к Подразделу F.35.3.3.
Когда узел снова подключается к кластеру, multimaster
автоматически доводит его до актуального состояния, применяя данные WAL из соответствующего слота репликации. Если узел был полностью исключён из кластера, его можно добавить, используя pg_basebackup.
Чтобы узнать больше о внутреннем устройстве multimaster
, обратитесь к Подразделу F.35.2.
F.35.1. Ограничения #
Расширение multimaster
осуществляет репликацию данных полностью автоматическим образом. Вы можете одновременно выполнять пишущие транзакции и работать с временными таблицами на любом узле кластера. Однако при этом нужно учитывать следующие ограничения репликации:
Операционная система Microsoft Windows не поддерживается.
Решения 1С по ряду причин не поддерживаются.
multimaster
может реплицировать только одну базу данных в кластере. Если требуется реплицировать содержимое нескольких баз данных, вы можете либо перенести все данные в разные схемы одной базы данных, либо создать для каждой базы отдельный кластер и настроитьmultimaster
в каждом из этих кластеров.Большие объекты не поддерживаются. Хотя их можно создать, multimaster не сможет реплицировать такие объекты и их идентификаторы (OID) на разных узлах могут конфликтовать, поэтому использовать большие объекты не рекомендуется.
Так как
multimaster
основан на логической репликации и протоколе трёхфазной фиксации с алгоритмом Паксос, его производительность в большой степени зависит от скорости сети. Поэтому разворачивать кластерmultimaster
на географически разнесённых узлах не рекомендуется.Использование таблиц без первичных ключей может привести к падению производительности. В некоторых случаях это может даже помешать восстановить узел кластера, поэтому стоит избегать реплицирования таких таблиц средствами
multimaster
.В отличие от ванильного PostgreSQL, в кластере multimaster на уровне изоляции
read committed
могут происходить сбои сериализации (с кодом SQLSTATE 40001), если на разных узлах будут выполняться конфликтующие транзакции. Поэтому приложение должно быть готово повторять транзакции в случае сбоя. Уровень изоляцииSerializable
работает только применительно к локальным транзакциям на текущем узле.Генерация последовательностей. Во избежание конфликтов уникальных идентификаторов на разных узлах,
multimaster
меняет стандартное поведение генераторов последовательностей. По умолчанию для каждого узла идентификаторы генерируются, начиная с номера узла, и увеличиваются на число узлов. Например, в кластере с тремя узлами идентификаторы 1, 4 и 7 выделяются для объектов, создаваемых на первом узле, а 2, 5 и 8 резервируются для второго узла. Если число узлов в кластере изменяется, величина прироста идентификаторов корректируется соответственно. Таким образом, значения последовательностей будут не монотонными. Если важно, чтобы последовательность во всём кластере увеличивалась монотонно, задайте для параметраmultimaster.monotonic_sequences
значениеtrue
.Задержка фиксации. В текущей реализации логической репликации
multimaster
передаёт данные узлам-подписчикам только после локальной фиксации, так что приходится ожидать двойной обработки транзакции: сначала на локальном узле, а затем на всех других узлах одновременно. Если транзакция производит запись в большом объёме, задержка может быть весьма ощутимой.При логической репликации не гарантируется, что идентификатор (OID) системного объекта будет одинаковым на всех узлах кластера, поэтому один и тот же объект на разных узлах кластера
multimaster
может иметь разные OID. Если ваше приложение или драйвер доступа к БД в своей работе полагается на OID, во избежание ошибок обеспечьте для него привязку к одному узлу, без возможности переключения на другие. Например, драйверNpgsql
может работать некорректно с кластеромmultimaster
, если методNpgsqlConnection.GlobalTypeMapper
будет использовать сохранённые внутри драйвера значения OID, подключаясь к разным узлам кластера.Узел кластера
multimaster
не может быть подписчиком логической репликации. Хотя узелmultimaster
может быть публикующим, подписка не может автоматически переключиться на другой узел при отказе этого публикующего узла.Реплицируемые неконфликтующие транзакции применяются на получающих узлах параллельно, так что их результаты могут появляться на разных узлах в разном порядке.
Если
multimaster
работает с большой нагрузкой, и один из узлов останавливается даже на короткое время, этот узел может сильно отставать от других узлов, поскольку они берут на себя нагрузку в нескольких потоках, а отстающий узел навёрстывает текущее состояние в одном потоке. В этом случае может потребоваться разгрузить другие узлы, чтобы синхронизировать все узлы.Операции
CREATE INDEX CONCURRENTLY
,REINDEX CONCURRENTLY
,CREATE TABLESPACE
иDROP TABLESPACE
не поддерживаются.Конструкция
COMMIT AND CHAIN
не поддерживается.
F.35.2. Архитектура #
F.35.2.1. Репликация #
Так как каждый сервер в кластере multimaster
может принимать запросы на запись, любой сервер может прервать транзакцию из-за параллельного изменения — так же как это происходит на одном сервере с несколькими обслуживающими процессами. Чтобы обеспечить высокую степень доступности и согласованность данных на всех узлах кластера, multimaster
применяет логическую репликацию и протокол трёхфазной фиксации, определяя результат транзакции по алгоритму консенсуса Паксос.
Когда Postgres Pro Enterprise загружает разделяемую библиотеку multimaster
, код multimaster
создаёт поставщика и потребителя логической репликации для каждого узла и внедряется в процедуру фиксирования транзакций. Типичная последовательность действий при репликации данных включает следующие фазы:
Фаза
PREPARE
. Кодmultimaster
перехватывает каждый операторCOMMIT
и преобразует его в операторPREPARE
. Все узлы, получающие транзакцию через протокол репликации (узлы когорты), передают свой голос для одобрения или отклонения транзакции служебному процессу на исходном узле. Это гарантирует, что вся когорта может принять эту транзакцию и конфликт при записи отсутствует. Подробнее поддержка транзакций сPREPARE
в PostgreSQL рассматривается в описании PREPARE TRANSACTION.Фаза
PRECOMMIT
. Если все узлы когорты одобряют транзакцию, служебный процесс отправляет всем этим узлам сообщениеPRECOMMIT
, выражающее намерение зафиксировать эту транзакцию. Узлы когорты отвечают этому процессу сообщениемPRECOMMITTED
. В случае сбоя все узлы могут использовать эту информацию для завершения транзакции по правилам кворума.Фаза
COMMIT
. Если результатPRECOMMIT
положительный, транзакция фиксируется на всех узлах.
Если узел отказывает или отключается от кластера между фазами PREPARE
и COMMIT
, фаза PRECOMMIT
даёт гарантию, что оставшиеся в строю узлы имеют достаточно информации для завершения подготовленной транзакции. Сообщения PRECOMMITTED
помогают избежать ситуации, когда отказавший узел зафиксировал или прервал транзакцию, но не успел уведомить о состоянии транзакции другие узлы. При двухфазной фиксации (2PC, two-phase commit), такая транзакция должна блокировать ресурсы (удерживать блокировки) до восстановления отказавшего узла. В противном случае данные могут оказаться несогласованными после восстановления. Это возможно, например, если отказавший узел зафиксирует транзакцию, а оставшийся узел откатит её.
Для фиксирования транзакции служебный процесс должен получить ответ от большинства узлов. Например, в кластере из 2N
+ 1 узлов необходимо получить минимум N
+ 1 ответов. Таким образом multimaster
обеспечивает доступность кластера для чтения и записи, пока работает большинство узлов, и гарантирует согласованность данных при отказе узла или прерывании соединения.
F.35.2.2. Обнаружение сбоя и восстановление #
Так как multimaster
допускает запись на всех узлах, он должен ждать ответа с подтверждением транзакции от всех остальных узлов. Если не принять специальных мер, в случае отказа узла для фиксации транзакции пришлось бы ждать пока он не будет восстановлен. Чтобы не допустить этого, multimaster
периодически опрашивает узлы и проверяет их состояние и соединение между ними. Когда узел не отвечает на несколько контрольных обращений подряд, этот узел убирается из кластера, чтобы оставшиеся в строю узлы могли производить запись. Частоту обращений и тайм-аут ожидания ответа можно задать в параметрах multimaster.heartbeat_send_timeout
и multimaster.heartbeat_recv_timeout
, соответственно.
Например, предположим, что кластер с пятью ведущими узлами в результате сетевого сбоя разделился на две изолированных подсети так, что в одной оказалось два, а в другой — три узла кластера. На основе информации о доступности узлов multimaster
продолжит принимать запросы на запись на всех узлах в большем разделе и запретит запись в меньшем. Таким образом, кластер, состоящий из 2N
+ 1 узлов может справиться с отказом N
узлов и продолжать функционировать пока будут работать и связаны друг с другом N
+ 1 узлов. Вы также можете организовать кластер из двух узлов с дополнительным легковесным узлом-рефери (не содержащим данные), который будет устранять неопределённость при симметричном разделении узлов. За подробностями обратитесь к Подразделу F.35.3.3.
В случае частичного разделения сети, когда разные узлы связаны с другими по-разному, multimaster
находит подмножество полностью связанных узлов и отключает все узлы вне этого подмножества. Например, в кластере с тремя узлами, если узел A может связаться и с B, и с C, а узел B не может связаться с C, multimaster
изолирует узел C, чтобы A и B могли полноценно работать дальше.
Чтобы сохранить порядок транзакций на разных узлах и, как следствие, целостность данных, решение об исключении или возвращении узлов в кластер должно приниматься согласованно. Для принятия таких решений введены поколения — подмножества узлов, считающихся рабочими. На техническом уровне поколением является пара <n, представители>
, где n
— уникальный номер, а представители
— подмножество настроенных узлов кластера. Узел всегда относится к какому-либо поколению и переходит в поколение со следующим номером как только узнаёт о его существовании; номера поколений работают здесь как логические часы/сроки/эпохи. В каждой транзакции при фиксировании отмечается текущее поколение узла, на котором она выполняется. Транзакция может быть предложена для окончательного фиксирования только после того, как она будет подготовлена на всех представителях поколения. Это позволяет разработать протокол восстановления так, чтобы порядок конфликтующих фиксируемых транзакций был на всех узлах одинаковым. Узлы существуют в поколении в одном из трёх состояний (текущее показывает функция mtm.status()
):
ONLINE
: узел является представителем поколения и выполняет транзакции штатным образом;RECOVERY
: узел является представителем поколения, но для перехода в рабочее состояние (ONLINE
) он должен применить в режиме восстановления транзакции из предыдущих поколений.;DEAD
: узел уже никогда не перейдёт в состояниеONLINE
в данном поколении;
Работающие узлы не имеют возможности отличить отказавший узел, переставший обрабатывать запросы, от узла в недоступной сети, к которому могут обращаться пользователи БД, но не другие узлы. Если во время фиксирования или записи транзакции некоторые из представителей текущего поколения отключаются, транзакция отменяется в соответствии с правилами поколений. Для предотвращения бесполезных действий соединение проверяется и в начале транзакции; если вы попытаетесь обратиться к изолированному узлу, multimaster
выдаст сообщение об ошибке, говорящее о текущем состоянии узла. Во избежание чтения неактуальных данных на нём запрещаются также запросы только на чтение. Таким образом, если вы захотите продолжить использовать отключённый узел вне кластера в независимом режиме, вам нужно будет удалить на этом узле расширение multimaster
, как описано в Подразделе F.35.4.5.
Каждый узел поддерживает свою структуру данных, в которой учитывает состояние всех узлов относительно него самого. Вы можете получить эту информацию, воспользовавшись функциями mtm.status()
и mtm.nodes()
.
Когда ранее отказавший узел возвращается в кластер, multimaster
начинает автоматическое восстановление:
Вновь подключённый узел выбирает случайный узел, который имеет состояние
ONLINE
в последнем поколении и называется узлом-донором, и начинает навёрстывать текущее состояние кластера, используя WAL.Достигнув нужного состояния, узел баллотируется для включения в следующее поколение. Когда новое поколение будет выбрано, при фиксировании очередных транзакций они должны будут применяться и на присоединившемся узле.
По завершении применения транзакций, оставшихся до точки перехода к новому поколению, вновь подключённый узел переходит в рабочее состояние и включается в схему репликации.
Корректность протокола восстановления была проверена по модели TLA+. Модель с подробным описанием вы можете найти в doc/specs в каталоге исходного кода multimaster
.
Для автоматического восстановления требуется наличие всех файлов WAL, сгенерированных после отказа узла. Если узел был отключён долгое время и сохранить больший объём WAL невозможно, вам придётся исключить этот узел из кластера и вручную восстановить его с одного из работающих узлов, используя pg_basebackup. За подробностями обратитесь к Подразделу F.35.4.3.
F.35.2.3. Служебные процессы расширения multimaster #
- mtm-monitor #
Запускает все остальные служебные процессы для базы данных под управлением расширения multimaster. Это первый служебный процесс, который multimaster запускает при загрузке. На каждом узле кластера multimaster работает один служебный процесс
mtm-monitor
. Когда добавляется новый узел,mtm-monitor
запускает процессыmtm-logrep-receiver
иmtm-dmq-receiver
, осуществляющие репликацию на этот узел. Если узел удаляется,mtm-monitor
останавливает процессыmtm-logrep-receiver
иmtm-dmq-receiver
, обслуживающие данный узел. Процессmtm-monitor
управляет служебными процессами только на собственном узле.- mtm-logrep-receiver #
Получает поток логической репликации с заданного узла-партнёра. При работе в штатном режиме процесс
mtm-logrep-receiver
передаёт реплицируемые транзакции пулу динамических процессов (см. mtm-logrep-receiver-dynworker). При навёрстывании в зависимости от значения параметра конфигурации multimaster.catchup_algorithm процессmtm-logrep-receiver
применяет реплицируемые транзакции на повторно подключившемся узле или передаёт их пулу динамических процессов. Число процессовmtm-logrep-receiver
на каждом узле равняется числу узлов, с которыми он взаимодействует.- mtm-dmq-receiver #
Получает подтверждения транзакций, переданных узлам-партнёрам, и контролирует соединения с этими узлами. Число процессов
mtm-logrep-receiver
на каждом узле равняется числу узлов, с которыми он взаимодействует.- mtm-dmq-sender #
Собирает уведомления о транзакциях, применяемых на текущем узле, и передаёт их соответствующим процессам mtm-dmq-receiver на узлах-партнёрах. Для каждого экземпляра Postgres Pro Enterprise запускается один такой процесс.
- mtm-logrep-receiver-dynworker #
Динамический процесс из пула для mtm-logrep-receiver. Применяет реплицированные транзакции, получаемые при работе в штатном режиме или при навёрстывании. Вы можете указать максимальное число динамических процессов с помощью параметра конфигурации multimaster.max_workers.
- mtm-resolver #
Реализует алгоритм Паксос для разрешения незавершённых транзакций. Этот процесс работает только в ходе восстановления или при потере соединения с другими узлами. Для каждого экземпляра Postgres Pro Enterprise запускается один такой процесс.
- mtm-campaigner #
Организует баллотирование для добавления текущего узла в новые поколения или для исключения других узлов. Для каждого экземпляра Postgres Pro Enterprise запускается один такой процесс.
- mtm-replier #
Отвечает на запросы процессов mtm-campaigner и mtm-resolver.
F.35.3. Установка и подготовка #
Чтобы использовать multimaster
, необходимо установить Postgres Pro Enterprise на всех узлах кластера. В состав Postgres Pro Enterprise включены все необходимые зависимости и расширения. Процедура сборки и установки multimaster
в PostgreSQL описана в отдельной инструкции.
F.35.3.1. Подготовка кластера #
Предположим, что вам нужно организовать кластер из трёх узлов с именами node1
, node2
и node3
. Установив Postgres Pro Enterprise на всех узлах, вы должны проинициализировать каталог данных на каждом узле, как описано в Разделе 18.2. Если вы хотите настроить multimaster для уже существующей базы данных mydb
, вы можете загрузить данные из mydb
на один из узлов после инициализации кластера либо загрузить данные на все узлы до инициализации, используя любое удобное средство, например, pg_basebackup или pg_dump.
Когда каталог данных будет подготовлен, выполните следующие действия на всех узлах кластера:
Измените файл конфигурации
postgresql.conf
следующим образом:Добавьте
multimaster
в переменнуюshared_preload_libraries
:shared_preload_libraries = 'multimaster'
Подсказка
Если переменная
shared_preload_libraries
уже определена вpostgresql.auto.conf
, вам потребуется изменить её значение с помощью команды ALTER SYSTEM. За подробностями обратитесь к Подразделу 19.1.2. Заметьте, что в кластере с несколькими ведущими командаALTER SYSTEM
влияет только на конфигурацию того узла, на котором запускается.Настройте параметры Postgres Pro Enterprise, связанные с репликацией:
wal_level = logical max_connections = 100 max_prepared_transactions = 300 # max_connections * N max_wal_senders = 10 # как минимум N max_replication_slots = 10 # как минимум 2N wal_sender_timeout = 0
здесь
N
— число узлов в вашем кластере.Вы должны сменить уровень репликации на
logical
, так как работаmultimaster
построена на логической репликации. Для кластера сN
узлами разрешите минимумN
передающих WAL процессов и слотов репликации. Так какmultimaster
неявно добавляет фазуPREPARE
кCOMMIT
каждой транзакции, в качестве разрешённого количества подготовленных транзакций задайтеN
*max_connections
. Параметрwal_sender_timeout
следует отключить, так как multimaster использует собственную логику для обнаружения сбоев.Убедитесь в том, что на каждом узле выделено достаточно фоновых рабочих процессов:
max_worker_processes = 250 # (N - 1) * (multimaster.max_workers + 1) + 5
Например, для кластера с тремя узлами и ограничением
multimaster.max_workers
= 100, механизмуmultimaster
в пиковые моменты может потребоваться до 207 фоновых рабочих процессов: пять всегда работающих служебных процессов (monitor, resolver, dmq-sender, campaigner, replier), по одному процессу walreceiver на каждый узел в кластере и до 200 динамических процессов, осуществляющих репликацию. При выборе значения этого параметра не забывайте, что фоновые рабочие процессы могут в то же время требоваться и другим модулям.В зависимости от вашей схемы использования и конфигурации сети может потребоваться настроить и другие параметры
multimaster
. За подробностями обратитесь к Подразделу F.35.3.2.
Запустите Postgres Pro Enterprise на всех узлах.
Создайте базу данных
mydb
и пользователяmtmuser
на каждом узле:CREATE USER mtmuser WITH SUPERUSER PASSWORD 'mtmuserpassword'; CREATE DATABASE mydb OWNER mtmuser;
Если вы хотите использовать аутентификацию по паролю, вам может быть полезен файл паролей.
Вы можете опустить этот шаг, если у вас уже есть база данных, которую вы хотите реплицировать, но тем не менее для репликации рекомендуется создать отдельного пользователя с правами суперпользователя. В примерах ниже предполагается, что вы будете реплицировать базу
mydb
от имени пользователяmtmuser
.Разрешите репликацию базы
mydb
на каждый узел кластера для пользователяmtmuser
, как описывается в Разделе 20.1. При этом важно использовать метод аутентификации, удовлетворяющий вашим требованиям безопасности. Например,pg_hba.conf
на узлеnode1
может содержать следующие строки:host replication mtmuser node2 md5 host mydb mtmuser node2 md5 host replication mtmuser node3 md5 host mydb mtmuser node3 md5
Подключитесь к любому узлу от имени пользователя БД
mtmuser
, создайте расширениеmultimaster
в базе данныхmydb
и выполните функциюmtm.init_cluster()
, указав первым аргументом строку подключения для текущего узла, а вторым — массив строк подключения для всех остальных узлов.Например, если вы хотите подключиться к узлу
node1
, выполните:CREATE EXTENSION multimaster; SELECT mtm.init_cluster('dbname=mydb user=mtmuser host=node1', '{"dbname=mydb user=mtmuser host=node2", "dbname=mydb user=mtmuser host=node3"}');
Чтобы убедиться, что расширение
multimaster
активно, вы можете вызвать функцииmtm.status()
иmtm.nodes()
:SELECT * FROM mtm.status(); SELECT * FROM mtm.nodes();
Если в поле
status
появилось значениеonline
и функцияmtm.nodes
показывает все узлы, значит ваш кластер успешно настроен и готов к использованию.
Подсказка
Если какие-либо данные должны присутствовать только на одном из узлов кластера, вы можете исключить таблицу с ними из репликации следующим образом:
SELECT mtm.make_table_local('table_name')
F.35.3.2. Настройка параметров конфигурации #
Хотя вы можете использовать multimaster
и в стандартной конфигурации, для более быстрого обнаружения сбоев и более надёжного автоматического восстановления может быть полезно скорректировать несколько параметров.
F.35.3.2.1. Установка тайм-аута для обнаружения сбоев #
Для проверки доступности партнёров multimaster
периодически опрашивает все узлы. Тайм-аут для обнаружения сбоев можно регулировать с помощью следующих переменных:
Переменная
multimaster.heartbeat_send_timeout
определяет интервал между опросами. По умолчанию её значение равно 200ms.Переменная
multimaster.heartbeat_recv_timeout
определяет интервал для ответа. Если за указанное время ответ от какого-то узла не будет получен, он считается отключённым и исключается из кластера. По умолчанию её значение равно 2000ms.
Значение multimaster.heartbeat_send_timeout
имеет смысл выбирать, исходя из типичных задержек ping между узлами. С уменьшением отношения значений recv/send сокращается время обнаружения сбоев, но увеличивается вероятность ложных срабатываний. При установке этого параметра учтите также типичный процент потерь пакетов между узлами кластера.
F.35.3.3. Режим 2+1: установка отдельного узла-рефери #
По умолчанию multimaster
определяет состояние кворума в подмножестве узлов, учитывая состояние большинства: кластер может продолжать работать, только если функционирует большинство узлов и эти узлы могут связаться друг с другом. Подход с выбором большинства не имеет смысла для кластера с двумя узлами: если один узел отключается, второй тоже перестаёт работать. Однако имеется особый режим с рефери (2+1), который требует меньше аппаратных ресурсов, но и менее отказоустойчив — два узла содержат полностью одинаковые данные, а отдельный узел-рефери нужен только, чтобы отдать голос одному из узлов.
Если один узел отключается, другой запрашивает у рефери исключительное разрешение на работу (формирует с одобрения рефери новое поколение с одним узлом). Получив разрешение, он продолжает работать в обычном режиме. Если отключённый ранее узел возвращается, он восстанавливается и формирует новое поколение для двух узлов, по сути аннулируя данное другому разрешение, с тем, чтобы тот включился в это поколение. Исключительное разрешение может перейти к другому узлу, только если будет сформировано новое поколение, в котором отключённый узел восстановится. В такой схеме гарантируется целостность данных, но страдает доступность — два узла (обычный и рефери) могут функционировать нормально, а кластер при этом будет недоступным, если выбранный рефери узел отключён. В классической схеме с тремя узлами такая ситуация невозможна.
Узел-рефери не хранит никакие данные кластера, поэтому он не создаёт значительную нагрузку и может быть размещён практически в любой системе, где установлен Postgres Pro Enterprise.
Во избежание «раздвоения» в кластере должен быть только один рефери.
Чтобы настроить рефери в своём кластере:
Установите Postgres Pro Enterprise на узле, который вы планируете сделать рефери, и создайте расширение
referee
:CREATE EXTENSION referee;
Разрешите в файле
pg_hba.conf
доступ к узлу-рефери.Настройте узлы, на которых будут находиться данные кластера, следуя указаниям в Подраздел F.35.3.1.
На всех узлах кластера укажите строку подключения к рефери в файле
postgresql.conf
:multimaster.referee_connstring =
строка_подключения
Здесь
строка_подключения
задаёт параметры libpq, необходимые для обращения к рефери.
Первое подмножество узлов, которому удаётся подключиться к рефери, получает выигрышный голос и начинает работать. Другие узлы должны произвести процедуру восстановления, чтобы нагнать выигравших и присоединиться к кластеру. При очень большой нагрузке продолжительность восстановления может быть непредсказуемой, поэтому при создании нового кластера рекомендуется дождаться перехода в активное состояние всех узлов с данными, прежде чем подавать полную нагрузку. Как только последние узлы завершают восстановление, рефери аннулирует результат голосования, и все узлы кластера начинают работать вместе.
В случае какого-либо сбоя процедура голосования вызывается снова. При этом все узлы могут оказаться недоступными на короткое время, пока рефери не выберет новое выигрышное подмножество. В этот момент при попытке подключения к кластеру вы можете получить следующее сообщение: [multimaster] node is not online: current status is "disabled"
([multimaster] узел не работает: текущее состояние — «выключен»).
F.35.4. Администрирование кластера multimaster #
F.35.4.1. Наблюдение за состоянием кластера #
В составе расширения multimaster
есть несколько функций, позволяющих наблюдать за текущим состоянием кластера.
Для проверки свойств определённого узла воспользуйтесь функцией mtm.status()
:
SELECT * FROM mtm.status();
Для получения списка всех узлов в кластере и их состояния вызовите функцию mtm.nodes()
:
SELECT * FROM mtm.nodes();
Выдаваемая ими информация подробно описана в Подразделе F.35.5.2.
F.35.4.2. Обращение к отключённым узлам #
Если узел кластера отключён, при любой попытке записать или прочитать данные на этом узле по умолчанию выдаётся ошибка. Если вы хотите обращаться к данным на отключённом узле, это поведение можно переопределить при подключении, передав параметр application_name со значением mtm_admin
. Таким образом, вы сможете выполнять на этом узле запросы на чтение и запись без контроля multimaster.
F.35.4.3. Добавление узлов в кластер #
Используя multimaster
, вы можете добавлять или удалять узлы кластера. Прежде чем добавить узел, снимите нагрузку и убедитесь (воспользовавшись функцией mtm.status()
) в том, что все узлы, которые должны быть в кластере, находятся в рабочем состоянии (online
). Чтобы добавить новый узел, на него нужно загрузить все данные, выполнив pg_basebackup на любом узле кластера, а затем запустить его.
Предположим, что у нас есть работающий кластер с тремя узлами с именами node1
, node2
и node3
. Чтобы добавить node4
, следуйте этим указаниям:
Определите, какая строка подключения будет использоваться для обращения к новому узлу. Например, для базы данных
mydb
, пользователяmtmuser
и нового узлаnode4
строка подключения может быть такой:"dbname=mydb user=mtmuser host=node4"
.В
psql
, подключённом к любому из работающих узлов, выполните:SELECT mtm.add_node('dbname=mydb user=mtmuser host=node4');
Эта команда меняет конфигурацию кластера на всех узлах и создаёт слоты репликации для нового узла. Она также возвращает идентификатор
node_id
для нового узла, который потребуется для завершения настройки.Перейдите к новому узлу и скопируйте на него все данные с одного из работающих узлов:
pg_basebackup -D
каталог_данных
-h node1 -U mtmuser -c fast -vpg_basebackup копирует весь каталог данных с
node1
, вместе с конфигурацией, и выводит последний LSN, воспроизведённый из WAL, например'0/12D357F0'
. Это значение потребуется для завершения подключения.Установите на новом узле
recovery_target=immediate
, чтобы при запуске он не применил транзакции после точки, в которой начнётся репликация. Добавьте вpostgresql.conf
:restore_command = 'false' recovery_target = 'immediate' recovery_target_action = 'promote'
И создайте файл
recovery.signal
в каталоге данных.Запустите Postgres Pro Enterprise на новом узле.
На узле, с которого вы снимали базовую копию, выполните в
psql
:SELECT mtm.join_node(4, '0/12D357F0');
здесь
4
— идентификаторnode_id
, возвращённый функциейmtm.add_node()
, а'0/12D357F0'
— значение LSN, выданное программой pg_basebackup.
F.35.4.4. Удаление узлов из кластера #
Перед удалением узлов снимите нагрузку и убедитесь (воспользовавшись функцией mtm.status()
), что все узлы, за исключением удаляемых, находятся в рабочем состоянии (online
). Отключите узлы, которые вы намерены удалить. Удалите узлы из кластера:
Вызовите функцию
mtm.nodes()
, чтобы узнать идентификатор узла, который нужно удалить:SELECT * FROM mtm.nodes();
Вызовите функцию
mtm.drop_node()
, передав ей этот идентификатор в качестве параметра:SELECT mtm.drop_node(3);
В результате будут удалены слоты репликации для узла 3 на всех узлах кластера и репликация на этот узел будет прекращена.
Если вы позже захотите возвратить узел в кластер, вам придётся добавить его как новый узел. За подробностями обратитесь к Подразделу F.35.4.3.
F.35.4.5. Удаление расширения multimaster #
Если вы хотели бы продолжить использование узла, который был удалён из кластера, в независимом режиме, вам нужно удалить расширение multimaster
на этом узле и очистить все относящиеся к multimaster
подписки и незавершённые транзакции, чтобы этот узел больше не был связан с кластером.
Уберите
multimaster
из shared_preload_libraries и перезапустите Postgres Pro Enterprise.Удалите расширение
multimaster
и публикацию:DROP EXTENSION multimaster; DROP PUBLICATION multimaster;
Просмотрите список существующих подписок с помощью команды
\dRs
и удалите те, имена которых начинаются с префиксаmtm_sub_
:\dRs DROP SUBSCRIPTION mtm_sub_
имя_подписки
;Просмотрите список существующих слотов репликации и удалите те, имена которых начинаются с префикса
mtm_
:SELECT * FROM pg_replication_slots; SELECT pg_drop_replication_slot('mtm_
имя_слота
');Просмотрите список существующих источников репликации и удалите те, имена которых начинаются с префикса
mtm_
:SELECT * FROM pg_replication_origin; SELECT pg_replication_origin_drop('mtm_
имя_источника
');Просмотрите список оставшихся подготовленных транзакций:
SELECT * FROM pg_prepared_xacts;
Вы должны зафиксировать или прервать эти транзакции, выполнив
ABORT PREPARED
илиид_транзакции
COMMIT PREPARED
, соответственно.ид_транзакции
Выполнив все эти действия, вы можете использовать этот узел в независимом режиме, если это требуется.
F.35.4.6. Проверка согласованности данных на узлах кластера #
Вы можете убедиться в том, что данные на всех узлах кластера одинаковые, воспользовавшись функцией mtm.check_query(query_text).
В качестве параметра эта функция принимает текст запроса, который вы хотите выполнить для сравнения данных. Когда вы вызываете эту функцию, она получает согласованные снимки данных на всех узлах кластера и выполняет в них этот запрос. Полученные на разных узлах результаты сравниваются попарно, и если они совпадают, эта функция возвращает true
. В противном случае она выдаёт предупреждение с первым расхождением и возвращает false
.
Чтобы избежать ложных срабатываний, необходимо добавить в тестовый запрос ORDER BY
. Например, предположим, что вы хотите убедиться в том, что содержимое таблицы my_table
на всех узлах кластера одинаковое. Посмотрите на результаты следующих запросов:
postgres=# SELECT mtm.check_query('SELECT * FROM my_table ORDER BY id'); check_query ------------- t (1 row)
postgres=# SELECT mtm.check_query('SELECT * FROM my_table'); WARNING: mismatch in column 'b' of row 0: 256 on node0, 255 on node1 check_query ------------- f (1 row)
Даже когда данные одинаковые, второй запрос сообщает о расхождении, так как разные узлы кластера возвращают данные в разном порядке.
F.35.4.7. Отложенная фиксация транзакций #
Когда отстающий узел навёрстывает состояние узла-донора и не может быстро применять изменения, можно замедлить выполнение транзакций на узле-доноре с помощью параметра конфигурации multimaster.tx_delay_on_slow_catchup
. Для этого задайте этому параметру значение on
в файле конфигурации postgresql.conf
на узле-доноре, но не на отстающем узле-партнёре. Если вы редактируете файл на работающем сервере, нужно передать сигнал postmaster перечитать файл (за подробностями обратитесь к Главе 19). При необходимости также можно указать максимально возможную задержку выполнения транзакций в необязательном параметре multimaster.max_tx_delay_on_slow_catchup
(в миллисекундах). При значении 0
максимальная задержка отключена. В настоящее время задержка может варьироваться от 1 мс до приблизительно 4 секунд. Значения за пределами допустимого диапазона будут усечены до ближайшего допустимого значения.
По умолчанию эта функциональность отключена.
F.35.5. Справка #
F.35.5.1. Параметры конфигурации #
multimaster.heartbeat_recv_timeout
#Тайм-аут, в миллисекундах. Если за это время не поступит ответ на контрольные сообщения, узел будет исключён из кластера.
По умолчанию: 2000 мс
multimaster.heartbeat_send_timeout
#Интервал между контрольными обращениями, в миллисекундах. Процесс-арбитр рассылает широковещательные контрольные сообщения всем узлам для выявления проблем с соединениями.
По умолчанию: 200 мс
multimaster.max_workers
#Максимальное число рабочих процессов
walreceiver
для каждого узла-партнёра.Важно
Манипулируя этим параметром, проявляйте осторожность. Если число одновременных транзакций во всём кластере превышает заданное значение, это может приводить к необнаруживаемым взаимоблокировкам.
По умолчанию: 100
multimaster.monotonic_sequences
#Определяет режим генерирования последовательностей для уникальных идентификаторов. Эта переменная может принимать следующие значения:
false
(по умолчанию) — идентификаторы на каждом узле генерируются, начиная с номера узла, и увеличиваются на число узлов. Например, в кластере с тремя узлами идентификаторы 1, 4 и 7 выделяются для объектов, создаваемых на первом узле, а 2, 5 и 8 резервируются для второго узла. Если число узлов в кластере изменяется, величина прироста идентификаторов корректируется соответственно.true
— генерируемая последовательность увеличивается монотонно во всём кластере. Идентификаторы узлов на каждом узле генерируются, начиная с номера узла, и увеличиваются на число узлов, но если очередное значение меньше идентификатора, уже сгенерированного на другом узле, оно пропускается. Например, в кластере с тремя узлами, если идентификаторы 1, 4 и 7 уже выделены на первом узле, идентификаторы 2 и 5 будут пропущены на втором. В этом случае первым идентификатором на втором узле будет 8. Таким образом следующий сгенерированный идентификатор всегда больше предыдущего, вне зависимости от узла кластера.
По умолчанию:
false
multimaster.referee_connstring
#Строка подключения для обращения к узлу-рефери. Если вы используете рефери, этот параметр нужно задать на всех узлах кластера.
multimaster.remote_functions
#Содержит разделённый запятыми список имён функций, которые должны выполняться удалённо на всех узлах кластера вместо выполнения на одном и последующий репликации результатов.
multimaster.trans_spill_threshold
#Максимальный размер транзакции, в килобайтах. При достижении этого предела транзакция записывается на диск.
По умолчанию: 100 МБ
multimaster.break_connection
#Разрывать соединения клиентов, подключённых к узлу, при отключении данного узла от кластера. Если этот параметр равен
false
, клиенты остаются подключёнными к узлу, но получают ошибку с сообщением о том, что узел отключён.По умолчанию:
false
multimaster.connect_timeout
#Максимальное время ожидания при подключении (в секундах). Ноль, отрицательное значение или отсутствие значения означают бесконечное ожидание. Минимально допустимый тайм-аут составляет 2 секунды, поэтому значение
1
интерпретируется как2
.По умолчанию:
0
multimaster.ignore_tables_without_pk
#Не реплицировать таблицы без первичного ключа. При значении
false
такие таблицы реплицируются.По умолчанию:
false
multimaster.syncpoint_interval
#Объём WAL, сгенерированный между точками синхронизации.
По умолчанию:
10 MB
multimaster.binary_basetypes
#Отправлять данные встроенных типов в двоичном формате.
По умолчанию:
true
multimaster.wait_peer_commits
#Дождаться, пока все узлы-партнёры зафиксируют транзакцию, прежде чем команда сообщит клиенту об успешном завершении.
По умолчанию:
true
multimaster.deadlock_prevention
#Управляет предотвращением взаимоблокировок транзакций, которые могут возникать при одновременном обновлении или удалении одного и того же кортежа на разных узлах. Если установлено значение
off
, предотвращение взаимоблокировок отключено.Если установлено значение
simple
, конфликтующие транзакции отклоняются. Этот параметр можно использовать для любой схемы кластера multimaster.Если установлено значение
smart
, для улучшения доступности ресурсов специальный алгоритм выбирает, какие транзакции фиксировать, а какие отклонить. Его рекомендуется использовать для схемы два узла и рефери. В схемах с тремя узлами всё ещё возможны взаимоблокировки. Если используется больше четырёх узлов, все конфликтующие транзакции отклоняются, как при значенииsimple
.По умолчанию:
off
multimaster.tx_delay_on_slow_catchup
#Включает задержку фиксации транзакций на узле-доноре, когда узлы-партнёры навёрстывают его состояние. Этот параметр следует устанавливать только на узле-доноре.
По умолчанию:
off
multimaster.max_tx_delay_on_slow_catchup
#Если
multimaster.tx_delay_on_slow_catchup
включён, этот параметр определяет максимальную задержку выполнения транзакций в миллисекундах. Допустимые значения: положительные целые числа, но не более 4 секунд.По умолчанию: 0
multimaster.enable_async_3pc_on_catchup
#Включает асинхронную фиксацию (
PREPARE
,PRECOMMIT
иCOMMIT PREPARED
) на отстающем узле, который догоняет узел-донор. Это значительно ускоряет навёрстывание. Механизм создания точек синхронизации обеспечивает целостность данных: во время навёрстывания система периодически создаёт точки синхронизации, во время которых все предыдущие транзакции безопасно записываются на диск. Таким образом, даже в случае перезапуска отстающего узла транзакции не будут утеряны.По умолчанию: true
multimaster.catchup_algorithm
#Режим навёрстывания для повторно подключившихся узлов. Определяет, как реплицируемые транзакции применяются на повторно подключившемся узле, который находится в процессе навёрстывания. Этот параметр конфигурации может принимать одно из следующих значений:
sequential
— процессmtm-logrep-receiver
применяет реплицируемые транзакции по очереди в порядке получения.parallel
— процессmtm-logrep-receiver
передаёт реплицируемые транзакции пулу динамических процессов (см. mtm-logrep-receiver-dynworker). Динамические процессы применяют неконфликтующие транзакции параллельно, а конфликтующие — по очереди в порядке получения, как в режиме навёрстыванияsequential
. Можно указать максимальное число динамических процессов, которые могут принимать реплицируемые транзакции в этом режиме навёрстывания, с помощью параметра конфигурации multimaster.parallel_catchup_workers.
По умолчанию:
sequential
multimaster.parallel_catchup_workers
#Максимальное число динамических процессов, которые могут применять реплицируемые транзакции на повторно подключившемся узле в режиме навёрстывания
parallel
. Значение этого параметра конфигурации не может превышать значение параметра конфигурации multimaster.max_workers. Можно указать режим навёрстывания для повторно подключившихся узлов с помощью параметра конфигурации multimaster.catchup_algorithm.По умолчанию:
8
F.35.5.2. Функции #
-
mtm.init_cluster(
#my_conninfo
text
,peers_conninfo
text[]
) Инициализирует конфигурацию кластера на всех узлах. Эта функция подключает текущий узел ко всем узлам, перечисленным в строке
peers_conninfo
, и создаёт расширение multimaster, слоты репликации и источники репликации на каждом узле. Запускайте эту функцию после того, как все узлы будут готовы к работе и смогут принимать подключения.Аргументы:
my_conninfo
— строка подключения для узла, на котором вы выполняете эту функцию. Используя эту строку, узлы-партнёры будут подключаться к данному узлу.peers_conninfo
— массив строк подключения для всех остальных узлов, которые будут добавляться в кластер.
-
mtm.add_node(
#connstr
text
) Добавляет новый узел в кластер. Эта функция должна вызываться до того, как на этот узел будут загружаться данные с помощью pg_basebackup.
mtm.add_node
создаёт нужные слоты репликации для нового узла, так что с её помощью можно добавить узел в кластер под нагрузкой.Аргументы:
connstr
— строка подключения для нового узла. Например, для базы данныхmydb
, пользователяmtmuser
и нового узлаnode4
строка подключения будет такой:"dbname=mydb user=mtmuser host=node4"
.
-
mtm.join_node(
#node_id
int
,backup_end_lsn
pg_lsn
) Завершает настройку кластера после добавления нового узла. Эта функция должна вызываться после того, как добавленный узел будет запущен.
Аргументы:
node_id
— идентификатор узла, добавляемого в кластер. Этот идентификатор выдаёт функцияmtm.nodes()
в полеid
.backup_end_lsn
— последний LSN базовой резервной копии, с которой инициализируется новый узел. Этот LSN будет отправной точкой для репликации данных после включения узла в кластер.
-
mtm.drop_node(
#node_id
integer
) Исключает узел из кластера.
Если вы захотите продолжить использование этого узла вне кластера в независимом режиме, вам нужно будет удалить на этом узле расширение
multimaster
, как описано в Подразделе F.35.4.5.Аргументы:
node_id
— идентификатор удаляемого узла. Этот идентификатор выдаёт функцияmtm.nodes()
в полеid
.
-
mtm.alter_sequences()
# Исправляет уникальные идентификаторы на всех узлах кластера. Это может потребоваться после восстановления всех узлов из одной базовой копии.
-
mtm.status()
# Показывает состояние расширения
multimaster
на текущем узле. Возвращает кортеж со следующими значениями:my_node_id
,int
— идентификатор этого узла.status
,text
— состояние узла. Возможные значения:online
(работает),recovery
(восстановление),catchup
(навёрстывание),disabled
(отключён, требуется восстановление, но ещё не известно, с какого узла),isolated
(работает в текущем поколении, но некоторые его партнёры недоступны).connected
,int[]
— массив идентификаторов узлов-партнёров, соединённых с данным узлом.gen_num
,int8
— номер текущего поколения.gen_members
,int[]
— массив идентификаторов узлов в текущем поколении.gen_members_online
,int[]
— массив идентификаторов узлов, относящихся к текущему поколению, в рабочем состоянии (online
).gen_configured
,int[]
— массив идентификаторов узлов, относящихся к текущему поколению.
-
mtm.nodes()
# Выдаёт информацию обо всех узлах в кластере. Возвращает кортеж со следующими значениями:
id
,integer
— идентификатор узла.conninfo
,text
— строка подключения для этого узла.is_self
,boolean
— признак текущего узла.enabled
,boolean
— данный узел является рабочим в текущем поколении?connected
,boolean
— показывает, подключён ли данный узел к текущему узлу.sender_pid
,integer
— идентификатор процесса, передающего WAL.receiver_pid
,integer
— идентификатор процесса, принимающего WAL.n_workers
,text
— количество запущенных на этом узле динамических процессов применения транзакций.receiver_mode
,text
— режим, в котором работает приёмник на этом узле. Возможные значения:disabled
,recovery
,normal
.
-
mtm.make_table_local(
#relation
regclass
) Останавливает репликацию для указанной таблицы.
Аргументы:
relation
— таблица, которую вы хотели бы исключить из схемы репликации.
mtm.check_query(
#query_text
text
)Проверяет согласованность данных между узлами кластера. Эта функция получает на всех узлах снимки текущего состояния, выполняет в них заданный запрос и сравнивает результаты. Если между какими-либо двумя узлами результаты различаются, она выводит предупреждение с первым различием и возвращает
false
. В случае отсутствия различий она возвращаетtrue
.Аргументы:
query_text
— текст запроса, который вы хотите выполнить на всех узлах для сравнения данных. Чтобы избежать ложных срабатываний, обязательно добавьте в этот запрос предложениеORDER BY
.
mtm.get_snapshots()
#Делает снимок данных на каждом узле кластера и возвращает идентификатор снимка. Снимки сохраняются до вызова
mtm.free_snapshots()
или до завершения текущего сеанса. Данную функцию вызывает mtm.check_query(query_text), отдельно вызывать её нет необходимости.mtm.free_snapshots()
#Удаляет снимки данных, сделанные функцией
mtm.get_snapshots()
. Данную функцию вызывает mtm.check_query(query_text), отдельно вызывать её нет необходимости.
F.35.6. Совместимость #
F.35.6.1. Локальные и глобальные операторы DDL #
По умолчанию все операторы DDL выполняются на всех узлах кластера, за исключением следующих, которые могут воздействовать только на локальный узел:
ALTER SYSTEM
CREATE DATABASE
DROP DATABASE
REINDEX
CHECKPOINT
CLUSTER
LOAD
LISTEN
CHECKPOINT
NOTIFY
F.35.7. Авторы #
Postgres Professional, Москва, Россия.
F.35.7.1. Благодарности #
Механизм репликации основан на логическом декодировании и предыдущей версии расширения pglogical
, которым поделилась с сообществом команда 2ndQuadrant.
Алгоритм консенсуса Паксос описан в статье:
Leslie Lamport. The Part-Time Parliament
Механизм параллельной репликации и восстановления реализован по принципам, изложенным в работе:
Odorico M. Mendizabal, Parisa Jalili Marandi, Fernando Luís Dotti, Fernando Pedone. Checkpointing in Parallel State-Machine Replication.