# Занятие 21: Отказоустойчивые решения / Кластер multimaster

> Исходная страница в нативном Markdown: Отказоустойчивые решения / Кластер multimaster

---

Индекс LLMS: [llms.txt](/ru/llms.txt)

---

<!-- Generated by scripts/generate_skeleton.py. -->

> [!NOTE]
> Эта страница извлечена из официального PDF в нативный Markdown. Текст слайдов, код и комментарии сохранены в порядке чтения; исходные PDF и HTML остаются эталоном макета.
> **HTML source:** [pgpro_21_multimaster.html](https://edu.postgrespro.ru/16/pgpro-16/pgpro_21_multimaster.html)
> **Archive:** [PGPRO-handouts-16.zip](https://edu.postgrespro.ru/16/PGPRO-handouts-16.zip)
> **Страниц PDF:** 27

## Page 1: Отказоустойчивые решения / Кластер multimaster {#source-page-001}

### Авторские права

© Postgres Professional, 2023–2026 Авторы: Алексей Береснев, Илья Баштанов, Павел Толмачев, Игорь Гнатюк Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
### Использование материалов курса

Некоммерческое использование материалов курса (презентации, демонстрации) разрешается без ограничений. Коммерческое использование возможно только с письменного разрешения компании Postgres Professional. Запрещается внесение изменений в материалы курса.
### Обратная связь

Отзывы, замечания и предложения направляйте по адресу: edu@postgrespro.ru
### Отказ от ответственности

Компания Postgres Professional не несет никакой ответственности за любые повреждения и убытки, включая потерю дохода, нанесенные прямым или непрямым, специальным или случайным использованием материалов курса. Компания Postgres Professional не предоставляет каких-либо гарантий на материалы курса. Материалы курса предоставляются на основе принципа «как есть» и компания Postgres Professional не обязана предоставлять сопровождение, поддержку, обновления, расширения и изменения.

## Page 2: Темы {#source-page-002}

- Возможности и ограничения
- Архитектура и принцип работы
- Распределенные транзакции
- Поколения узлов
- Проверка состояния узлов
- Отказ узла
- Режим 2+1

## Page 3: Возможности {#source-page-003}

**Синхронный симметричный кластер**
  - на всех узлах одинаковые данные, все узлы ведущие
- Распределенные транзакции
  - все узлы работают как единая СУБД с ACID-гарантиями
- Отказоустойчивость и высокая доступность
- Масштабируемость чтения

### Комментарии преподавателя

Расширение multimaster позволяет преобразовать сервер в синхронный симметричный кластер без разделения ресурсов — данные будут одинаковы на всех узлах кластера, клиенты могут читать и писать данные на любой из узлов. В кластере поддерживаются распределенные транзакции, дающие обычные гарантии ACID, поэтому приложения могут работать с кластером как с единой СУБД.

Multimaster обеспечивает отказоустойчивость за счет автоматического восстановления данных на вернувшихся в строй узлах; кластер имеет высокую доступность — у него нулевое время простоя. Менять конфигурацию (в том числе и обновлять в рамках одной и той же основной версии) узлов можно без остановки обслуживания. Перенаправляя на разные узлы читающие транзакции, можно повысить пропускную способность таких операций.

<https://postgrespro.ru/docs/enterprise/16/multimaster>

## Page 4: Ограничения {#source-page-004}

**Не поддерживаются**
  - ОС Windows и решения 1С
  - репликация нескольких баз данных
  - большие объекты (Large Objects)
  - операции неблокирующего построения и перестроения индексов
  - и работы с табличными пространствами
- Особенности работы с данными
  - транзакции могут занимать длительное время
  - на уровне Read Committed могут происходить сбои сериализации
  - меняется поведение последовательностей
  - OID могут отличаться на разных узлах
- Некоторые другие особенности и ограничения

### Комментарии преподавателя

У расширения есть ряд ограничений.

Не поддерживается ОС Windows и продукты 1С.

Можно реплицировать только одну базу данных (но можно сделать несколько кластеров для разных баз данных).

Не поддерживаются большие объекты (Large Objects).

Не поддерживаются операции CREATE INDEX и REINDEX с предложением CONCURRENTLY, CREATE и DROP TABLESPACE.

Так как каждая транзакция подтверждается несколькими узлами, это занимает некоторое время. Чем больше узлов в кластере, тем дольше будет происходить фиксация изменений.

На уровне изоляции Read Committed транзакции могут завершаться ошибкой сериализации.

Последовательности по умолчанию выдают немонотонные значения, чтобы избежать конфликтов уникальных идентификаторов между узлами.

Идентификаторы OID могут быть различными на разных узлах.

Существуют и некоторые другие ограничения использования мультимастера.

<https://postgrespro.ru/docs/enterprise/16/multimaster#MULTIMASTER-LIMIT> ATIONS

## Page 5: Подготовка сервера {#source-page-005}

Остановим основной сервер.
```sh
student$ sudo systemctl stop postgrespro-ent-16.service
```
В этой демонстрации часть команд выполняется от имени пользователя ОС postgres. Обращайте внимание на приглашения перед командами.
Для текущей демонстрации инициализированы и настроены три экземпляра Postgres Pro Enterprise. Каталоги данных и порты:
/var/lib/pgpro/ent-16-MM-1, порт 5001 /var/lib/pgpro/ent-16-MM-2, порт 5002 /var/lib/pgpro/ent-16-MM-3, порт 5003
Для самостоятельной настройки кластеров обратитесь к решению практики.
Дополнительные параметры первого экземпляра:
```text
/var/lib/pgpro/ent-16-MM-1/conf.d/multimaster.conf
```
```text
cluster_name = 'node1'
port = 5001
shared_preload_libraries = 'multimaster'
wal_level = logical
max_connections = 100           # значение по умолчанию
max_prepared_transactions = 300
max_wal_senders = 10            # значение по умолчанию
max_replication_slots = 10      # значение по умолчанию
wal_sender_timeout = 0
max_worker_processes = 320
```
Остальные экземпляры настроены аналогично.
### Инициализация кластера multimaster
Узлы кластера уже подготовлены, теперь необходимо инициализировать весь кластер multimaster из трех узлов:
```sh
student$ psql -p 5001 -U postgres
```
```sh
student$ psql -p 5002 -U postgres
```
```sh
student$ psql -p 5003 -U postgres
```
Подготовим роль и базу данных для работы мультимастера на каждом из узлов.
Для первого узла:
```sql
=> CREATE USER multimaster_user WITH SUPERUSER;
```
```sql
CREATE ROLE
```
```sql
=> CREATE DATABASE pgpro_multimaster OWNER multimaster_user;
```
```sql
CREATE DATABASE
```
```sql
=> \c pgpro_multimaster multimaster_user
```
```text
You are now connected to database "pgpro_multimaster" as user "multimaster_user".
```
Для второго узла:
```sql
=> CREATE USER multimaster_user WITH SUPERUSER;
```
```sql
CREATE ROLE
```
```sql
=> CREATE DATABASE pgpro_multimaster OWNER multimaster_user;
```
```sql
CREATE DATABASE
```
```sql
=> \c pgpro_multimaster multimaster_user
```
```text
You are now connected to database "pgpro_multimaster" as user "multimaster_user".
```
И для третьего узла:
```sql
=> CREATE USER multimaster_user WITH SUPERUSER;
```

## Page 6: Page 6 {#source-page-006}

```sql
CREATE ROLE
```
```sql
=> CREATE DATABASE pgpro_multimaster OWNER multimaster_user;
```
```sql
CREATE DATABASE
```
```sql
=> \c pgpro_multimaster multimaster_user
```
```text
You are now connected to database "pgpro_multimaster" as user "multimaster_user".
```
Теперь объединим узлы в кластер. Для этого на любом из узлов добавим расширение multimaster в БД pgpro\_multimaster...
```sql
=> CREATE EXTENSION multimaster;
```
```sql
CREATE EXTENSION
```
- ... и запустим функцию инициализации кластера. Первый параметр — это строка подключения к текущему узлу, второй — массив строк подключения к остальным узлам:
```sql
=> SELECT mtm.init_cluster(
     'dbname=pgpro_multimaster user=multimaster_user port=5001',
   '{"dbname=pgpro_multimaster user=multimaster_user port=5002",
     "dbname=pgpro_multimaster user=multimaster_user port=5003"}');
```
```text
 init_cluster
--------------
(1 row)
```
Функция возвращает пустой ответ в случае успеха, либо ошибку инициализации.

## Page 7: Архитектура {#source-page-007}

![Source slide page 7: Архитектура](images/page-007.png){#source-figure-007 caption="Source slide page 7: Архитектура"}


### Комментарии преподавателя

Расширение multimaster состоит из патчей ядра СУБД (входят в Postgres Pro) и непосредственно кода расширения.

Данные передаются между узлами с помощью доработанного механизма логической репликации.

Для обеспечения согласованности данных подтверждение распределенных транзакций выполняется с помощью улучшенного трехфазного протокола фиксации (E3PC). Для этого в кластере изначально должно быть не менее трех узлов.

Высокая степень доступности достигается тем, что узлы можно выводить из кластера и добавлять к кластеру «на лету». При этом клиенты могут быть подключены к любому узлу — все узлы принимают и читающую, и пишущую нагрузку.

<https://postgrespro.ru/docs/enterprise/16/multimaster#MULTIMASTER-ARC> HITECTURE

## Page 8: Процессы {#source-page-008}

![Source slide page 8: Процессы](images/page-008.png){#source-figure-008 caption="Source slide page 8: Процессы"}


### Комментарии преподавателя

За разные задачи в мультимастере отвечают разные процессы:

● Процесс mtm-monitor запускается на каждом узле и управляет остальными процессами кластера.

● Процесс mtm-logrep-receiver получает поток логической репликации от walsender. Изменения применяются пулом процессов mtm-logrep-receiver-dynworker.

● Процесс mtm-dmq-sender посылает mtm-dmq-receiver служебную информацию, необходимую для реализации распределенных транзакций, а также выполняет периодический опрос доступности узлов (DMQ — distributed message queue, распределенная очередь сообщений).

● Процессы mtm-resolver и mtm-campaigner служат для согласованного исключения узлов из кластера и возвращения узлов в кластер; им отвечает процесс mtm-replier.

На рисунке показаны только процессы-источники одного узла и соответствующие им процессы-приемники другого узла. На самом деле и те процессы, и другие работают на каждом узле. Более того, их количество зависит от числа узлов в кластере: например, на каждом узле будет запущен процесс walsender для каждого из остальных узлов кластера.

<https://postgrespro.ru/docs/enterprise/16/multimaster#MULTIMASTER-BGW> ORKERS

## Page 9: Процессы {#source-page-009}

Посмотрим на процессы, которые запускаются на каждом узле кластера (postmaster.pid находится в первой строке файла postmaster.pid, расположенного в каталоге PGDATA):
```sh
student$ ps -o pid,command --ppid 852417
```
```text
    PID COMMAND
 852421 postgres: node1: checkpointer
 852422 postgres: node1: background writer
 852424 postgres: node1: walwriter
 852425 postgres: node1: autovacuum launcher
 852426 postgres: node1: cfs-worker-0
 852428 postgres: node1: mtm-dmq-sender
 852429 postgres: node1: logical replication launcher
 853698 postgres: node1: multimaster_user pgpro_multimaster [local] idle
 853972 postgres: node1: mtm-monitor
 854152 postgres: node1: mtm-replier
 854153 postgres: node1: mtm-campaigner
 854154 postgres: node1: mtm-resolver
 854155 postgres: node1: mtm-dmq-receiver-1-3
 854157 postgres: node1: mtm-dmq-receiver-1-2
 854377 postgres: node1: walsender multimaster_user pgpro_multimaster [local]
START_REPLICATION
 854462 postgres: node1: mtm-logrep-receiver-1-2
 854463 postgres: node1: walsender multimaster_user pgpro_multimaster [local]
START_REPLICATION
 854493 postgres: node1: mtm-logrep-receiver-1-3
```
Количество процессов довольно велико, следует обеспечить их достаточными ресурсами.

## Page 10: Фиксация изменений {#source-page-010}

![Source slide page 10: Фиксация изменений](images/page-010.png){#source-figure-010 caption="Source slide page 10: Фиксация изменений"}


### Комментарии преподавателя

Протокол трехфазной фиксации расширяет стандартный протокол двухфазной фиксации, реализованный в PostgreSQL, и работает следующим образом.

1. Prepare. Когда на узле (который считается координатором транзакции) выполняется оператор COMMIT, транзакция подготавливается (PREPARE TRANSACTION) и передается на остальные доступные узлы. Эти узлы отвечают подтверждением или отказом.

Если какой-либо узел ответил отказом, транзакция обрывается.

2. Precommit. Узел-координатор рассылает остальным узлам команду Precommit, которая выражает намерение зафиксировать транзакцию.

Для продолжения достаточно, чтобы подтверждением ответило большинство узлов (кворум). Если достаточного количества подтверждений не набирается, транзакция обрывается (ROLLBACK PREPARED).

3. Commit. Транзакция фиксируется на всех узлах (COMMIT PREPARED).

В отличие от двухфазной фиксации, в трехфазную добавлен этап Precommit. Если после него произойдет сбой одного из узлов и третий этап Commit не будет выполнен на сбойном узле, остальные узлы, не дождавшись подтверждения, самостоятельно зафиксируют транзакцию.

<https://www.sciencedirect.com/science/article/pii/S0022000098915665>

## Page 11: Поколения узлов {#source-page-011}

![Source slide page 11: Поколения узлов](images/page-011.png){#source-figure-011 caption="Source slide page 11: Поколения узлов"}


### Комментарии преподавателя

Подмножество узлов, которые кластер считает в данный момент рабочими, называется поколением. Поколения нумеруются последовательными целыми числами.

Узел всегда входит в какое-либо поколение. Если узел узнает, что существует поколение с бо́льшим номером, он старается перейти в него.

Новое поколение формируется большинством узлов при любом изменении списка доступных узлов кластера: в момент его запуска, при сбое узла и при добавлении в кластер нового узла.

Транзакцию можно зафиксировать только после того, как она будет подготовлена на всех представителях поколения.

На слайде показано поколение 1 в кластере из трех узлов.

## Page 12: Проверка состояния кластера {#source-page-012}

Проверим статус узла кластера:
```sql
=> SELECT * FROM mtm.status() \gx
```
```text
-[ RECORD 1 ]------+--------
my_node_id         | 1
status             | online
connected          | {1,2,3}
gen_num            | 1
gen_members        | {1,2,3}
gen_members_online | {1,2,3}
gen_configured     | {1,2,3}
```
На любом из узлов кластера можно получить список всех узлов:
```sql
=> SELECT id, conninfo, enabled, connected FROM mtm.nodes() \gx
```
```text
-[ RECORD 1 ]-------------------------------------------------------
id        | 1
conninfo  | dbname=pgpro_multimaster user=multimaster_user port=5001
enabled   | t
connected | t
-[ RECORD 2 ]-------------------------------------------------------
id        | 2
conninfo  | dbname=pgpro_multimaster user=multimaster_user port=5002
enabled   | t
connected | t
-[ RECORD 3 ]-------------------------------------------------------
id        | 3
conninfo  | dbname=pgpro_multimaster user=multimaster_user port=5003
enabled   | t
connected | t
```
Проверим работу кластера — создадим таблицу на первом узле. Рекомендуется использовать таблицы с явно определенным первичным ключом — мы так и поступим:
```sql
=> CREATE TABLE multimaster_tbl (id integer PRIMARY KEY);
```
```sql
CREATE TABLE
```
Добавим в таблицу несколько строк:
```sql
=> INSERT INTO multimaster_tbl VALUES (1),(2),(3);
```
```sql
INSERT 0 3
```
Проверим доступность таблицы multimaster\_tbl на втором узле:
```sql
=> SELECT * FROM multimaster_tbl;
```
```text
 id
----
  1
  2
  3
(3 rows)
```
Таблица и строки доступны на втором узле — кластер работает.

## Page 13: Отказ узла {#source-page-013}

![Source slide page 13: Отказ узла](images/page-013.png){#source-figure-013 caption="Source slide page 13: Отказ узла"}


### Комментарии преподавателя

При отказе одного из узлов (выключение узла, недоступность узла по сети) оставшиеся узлы формируют новое поколение и в дальнейшем транзакции подтверждаются большинством из оставшихся узлов.

<https://postgrespro.ru/docs/enterprise/16/multimaster#MULTIMASTER-FAIL> URE-DETECTION-AND-RECOVERY

## Page 14: Отказ узла {#source-page-014}

Для демонстрации отказа узла остановим экземпляр Postgres Pro на третьем сервере:
```sh
postgres$ /opt/pgpro/ent-16/bin/pg_ctl \
  -w \
  -D /var/lib/pgpro/ent-16-MM-3 \
  stop
```
```text
waiting for server to shut down........ done
server stopped
```
Проверим доступные узлы кластера:
```sql
=> SELECT * FROM mtm.status() \gx
```
```text
-[ RECORD 1 ]------+--------
my_node_id         | 1
status             | online
connected          | {1,2}
gen_num            | 2
gen_members        | {1,2}
gen_members_online | {1,2}
gen_configured     | {1,2,3}
```
Номер поколения увеличился.
Запишем еще три строки в таблицу, теперь на втором узле:
```sql
=> INSERT INTO multimaster_tbl VALUES (11),(22),(33);
```
```sql
INSERT 0 3
```
Проверим корректную работу кластера — прочитаем все строки на первом узле:
```sql
=> SELECT * FROM multimaster_tbl;
```
```text
 id
----
  1
  2
  3
 11
 22
 33
(6 rows)
```
Добавленные строки видны — кластер продолжает работать даже при отсутствии одного из узлов.

## Page 15: Восстановление узла {#source-page-015}

![Source slide page 15: Восстановление узла](images/page-015.png){#source-figure-015 caption="Source slide page 15: Восстановление узла"}


### Комментарии преподавателя

После восстановления третьего узла он определяет номер текущего поколения кластера, получает список узлов, находящихся в этом поколении и выполняет восстановление с одного из этих узлов.

После окончания восстановления третий узел предлагает включить его в кластер, формируя новое поколение 3.

## Page 16: Восстановление узла {#source-page-016}

Текущее состояние узлов кластера:
```sql
=> SELECT * FROM mtm.status() \gx
```
```text
-[ RECORD 1 ]------+--------
my_node_id         | 1
status             | online
connected          | {1,2}
gen_num            | 2
gen_members        | {1,2}
gen_members_online | {1,2}
gen_configured     | {1,2,3}
```
Вернем третий узел в состав кластера. Для этого запустим экземпляр Postgres Pro на третьем узле:
```sh
postgres$ /opt/pgpro/ent-16/bin/pg_ctl \
  -w \
  -D /var/lib/pgpro/ent-16-MM-3 \
  -l /var/lib/pgpro/ent-16-MM-3/pgpro-ent.log \
  start
```
```text
waiting for server to start.... done
server started
```
Сразу после подключения видим, что третий узел пока что не готов к работе:
```sh
student$ psql -p 5003 -U postgres
```
```sql
=> SELECT * FROM mtm.status() \gx
```
```text
-[ RECORD 1 ]------+--------
my_node_id         | 1
status             | online
connected          | {1,2,3}
gen_num            | 2
gen_members        | {1,2}
gen_members_online | {1,2}
gen_configured     | {1,2,3}
```
После запуска узел подключается к кластеру, получает все необходимые изменения...
...и возвращается в строй. Номер поколения снова увеличивается:
```sql
=> SELECT * FROM mtm.status() \gx
```
```text
-[ RECORD 1 ]------+--------
my_node_id         | 1
status             | online
connected          | {1,2,3}
gen_num            | 3
gen_members        | {1,2,3}
gen_members_online | {1,2,3}
gen_configured     | {1,2,3}
```
Все добавленные ранее строки видны на третьем узле.
```sql
=> \c pgpro_multimaster multimaster_user
```
```text
You are now connected to database "pgpro_multimaster" as user "multimaster_user".
```
```sql
=> SELECT * FROM multimaster_tbl;
```
```text
 id
----
  1
  2
  3
 11
 22
 33
(6 rows)
```

## Page 17: Page 17 {#source-page-017}


## Page 18: Режим 2+1 {#source-page-018}

![Source slide page 18: Режим 2+1](images/page-018.png){#source-figure-018 caption="Source slide page 18: Режим 2+1"}


### Комментарии преподавателя

Для работы синхронного симметричного кластера требуется, чтобы все узлы кластера были примерно одинаковы по их аппаратным характеристикам.

Помимо этого, существует режим работы кластера мультимастера 2+1 (его еще называют режимом работы с рефери). В этом случае в кластер входят два полноценных узла, а третий узел работает только как голосующий — он принимает решения о подтверждении и отмене транзакций.

На узле-рефери достаточно установить Postgres Pro Enterprise и добавить расширение referee. Узел-рефери не хранит никакие данные кластера, поэтому нагрузка на узел невелика и он не требователен к ресурсам.

<https://postgrespro.ru/docs/enterprise/16/multimaster#SETTING-UP-A-REF> EREE

## Page 19: Режим 2+1 {#source-page-019}

![Source slide page 19: Режим 2+1](images/page-019.png){#source-figure-019 caption="Source slide page 19: Режим 2+1"}

Для перехода в режим работы 2+1 удалим из кластера один из узлов (третий):
```sql
=> SELECT mtm.drop_node(3);
```
```text
 drop_node
-----------
(1 row)
```
На удаленном узле добавим расширение referee:
```sql
=> CREATE EXTENSION referee;
```
```sql
CREATE EXTENSION
```
Остановим остальные два узла:
```sh
postgres$ /opt/pgpro/ent-16/bin/pg_ctl \
  -w \
  -D /var/lib/pgpro/ent-16-MM-1 \
  stop
```
```text
waiting for server to shut down........ done
server stopped
```
```sh
postgres$ /opt/pgpro/ent-16/bin/pg_ctl \
  -w \
  -D /var/lib/pgpro/ent-16-MM-2 \
  stop
```
```text
waiting for server to shut down........ done
server stopped
```
На каждом из двух узлов в файл конфигурации необходимо добавить строку подключения к узлу-рефери:
```text
/var/lib/pgpro/ent-16-MM-1/conf.d/referee.conf
```
```text
multimaster.referee_connstring = 'dbname=pgpro_multimaster user=multimaster_user
port=5003'
```
```text
/var/lib/pgpro/ent-16-MM-2/conf.d/referee.conf
```
```text
multimaster.referee_connstring = 'dbname=pgpro_multimaster user=multimaster_user
port=5003'
```
Запускаем любой из основных узлов. Например второй:
```sh
postgres$ /opt/pgpro/ent-16/bin/pg_ctl \
  -w \
  -D /var/lib/pgpro/ent-16-MM-2 \
  -l /var/lib/pgpro/ent-16-MM-2/pgpro-ent.log \
  start
```
```text
waiting for server to start.... done
server started
```
```sh
student$ psql -p 5002 -U multimaster_user -d pgpro_multimaster
```
Запускаем другой узел:
```sh
postgres$ /opt/pgpro/ent-16/bin/pg_ctl \
  -w \
  -D /var/lib/pgpro/ent-16-MM-1 \
  -l /var/lib/pgpro/ent-16-MM-1/pgpro-ent.log \
  start
```
```text
waiting for server to start.... done
server started
```
```sh
student$ psql -p 5001 -U multimaster_user -d pgpro_multimaster
```
Проверим статус кластера:
```sql
=> SELECT * FROM mtm.status() \gx
```

## Page 20: Page 20 {#source-page-020}

```text
-[ RECORD 1 ]------+-------
my_node_id         | 2
status             | online
connected          | {1,2}
gen_num            | 6
gen_members        | {1,2}
gen_members_online | {1,2}
gen_configured     | {1,2}
```
Проверим также, что ранее созданная таблица на месте:
```sql
=> SELECT * FROM multimaster_tbl;
```
```text
 id
----
  1
  2
  3
 11
 22
 33
(6 rows)
```
Снова добавим строки в таблицу multimaster\_tbl:
```sql
=> INSERT INTO multimaster_tbl VALUES (111),(222),(333);
```
```sql
INSERT 0 3
```
```sql
=> SELECT * FROM multimaster_tbl;
```
```text
 id
-----
   1
   2
   3
  11
  22
  33
 111
 222
 333
(9 rows)
```
Кластер продолжает работать. Но он уже состоит из двух узлов и одного рефери.
Убедиться в том, что узлы корректно взаимодействуют с рефери, можно, посмотрев их журналы сообщений. Второй узел является основным в данной конфигурации кластера:
```sh
student$ sudo grep referee /var/lib/pgpro/ent-16-MM-2/pgpro-ent.log
```
```text
2026-07-28 00:01:13.730 MSK [856270] LOG:  [MTM] [mtm-campaigner] got referee grant in
generation num=5
2026-07-28 00:01:13.912 MSK [856272] LOG:  [MTM] [mtm-resolver] resolving as referee
winner
```
Первый узел сообщает, что не может получить выигрышный голос (grant) при голосовании, так как такой голос уже выдан второму узлу:
```sh
student$ sudo grep referee /var/lib/pgpro/ent-16-MM-1/pgpro-ent.log
```
```sql
2026-07-28 00:01:14.458 MSK [857526] WARNING:  [MTM] [mtm-campaigner] 'select
referee.request_grant(1, 5)' failed: ERROR:  grant was already issued to node 2 in
generation 5
CONTEXT:  PL/pgSQL function referee.request_grant(integer,bigint) line 19 at RAISE
2026-07-28 00:01:14.541 MSK [857526] WARNING:  [MTM] [mtm-campaigner] 'select
referee.request_grant(1, 5)' failed: ERROR:  grant was already issued to node 2 in
generation 5
CONTEXT:  PL/pgSQL function referee.request_grant(integer,bigint) line 19 at RAISE
```

## Page 21: Итоги {#source-page-021}

**Postgres Pro Multimaster — это кластерное решение со всеми ведущими узлами, обеспечивающее автоматическое восстановление узлов после сбоев**
- Не требует установки дополнительного ПО
- Сфера применения — системы высокой доступности

## Page 22: Практика {#source-page-022}

1. Настройте кластер для режима работы 2+1. 2. Имитируйте отказ одного из двух узлов и проверьте работоспособность кластера. 3. Верните отключенный узел в строй.

### Комментарии преподавателя

1. Подготовьте три дополнительных экземпляра Postgres Pro Enterprise: для каждого из них инициализируйте каталог данных, настройте аутентификацию и задайте параметры. Подробности можно посмотреть в решении практики.

## Page 23: 1. Настройка кластера для режима работы 2+1 {#source-page-023}

Остановим основной сервер.
```sh
student$ sudo systemctl stop postgrespro-ent-16.service
```
Инициализируем кластер из двух узлов и третий узел для рефери.
Сначала подготовим три дополнительных экземпляра сервера Postgres Pro.
Создаем каталог PGDATA (если экземпляр работает, его предварительно надо остановить):
```sh
student$ sudo rm -rf /var/lib/pgpro/ent-16-MM-1
```
```sh
student$ sudo mkdir /var/lib/pgpro/ent-16-MM-1
```
```sh
student$ sudo chown postgres: -R /var/lib/pgpro/ent-16-MM-1
```
Инициализируем каталог данных:
```sh
postgres$ /opt/pgpro/ent-16/bin/initdb -U postgres -k -D /var/lib/pgpro/ent-16-MM-1
```
```text
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.
```
```text
initdb: --icu-locale not specified, using lc_collate
Using language tag "en-US" for ICU locale "en_US.UTF-8".
The database cluster will be initialized with this locale configuration:
  provider:    icu
  ICU locale:  en-US
  LC_COLLATE:  en_US.UTF-8
  LC_CTYPE:    en_US.UTF-8
  LC_MESSAGES: en_US.UTF-8
  LC_MONETARY: ru_RU.UTF-8
  LC_NUMERIC:  ru_RU.UTF-8
  LC_TIME:     ru_RU.UTF-8
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".
```
```text
Data page checksums are enabled.
```
```text
fixing permissions on existing directory /var/lib/pgpro/ent-16-MM-1 ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting default time zone ... Europe/Moscow
creating configuration files ... ok
performing automatic tuning ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok
```
```text
initdb: warning: enabling "trust" authentication for local connections
initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or
--auth-local and --auth-host, the next time you run initdb.
```
```text
Success. You can now start the database server using:
```
```text
    /opt/pgpro/ent-16/bin/pg_ctl -D /var/lib/pgpro/ent-16-MM-1 -l logfile start
```
Роль multimaster\_user должна иметь право использовать протокол репликации и подключаться к базе pgpro\_multimaster, настройки аутентификации это разрешают:
```sh
postgres$ grep ^local /var/lib/pgpro/ent-16-MM-1/pg_hba.conf
```
```text
local   all             all                                     trust
local   replication     all                                     trust
```
Конфигурационный файл:
```sh
postgres$ echo include_dir \'conf.d\' >> /var/lib/pgpro/ent-16-MM-1/postgresql.conf
```
```sh
postgres$ mkdir /var/lib/pgpro/ent-16-MM-1/conf.d
```
```text
/var/lib/pgpro/ent-16-MM-1/conf.d/multimaster.conf
```
```text
cluster_name = 'node1'
shared_preload_libraries = 'multimaster'
```

## Page 24: Page 24 {#source-page-024}

```text
wal_level = logical
max_connections = 100           # значение по умолчанию
max_prepared_transactions = 300
max_wal_senders = 10            # значение по умолчанию
max_replication_slots = 10      # значение по умолчанию
wal_sender_timeout = 0
max_worker_processes = 320
port = 5001
```
Запускаем экземпляр:
```sh
postgres$ /opt/pgpro/ent-16/bin/pg_ctl \
  -w \
  -D /var/lib/pgpro/ent-16-MM-1 \
  -l /var/lib/pgpro/ent-16-MM-1/pgpro-ent.log \
  start
```
```text
waiting for server to start.... done
server started
```
Создаем роль и базу данных для мультимастера:
```sh
student$ psql -p 5001 -U postgres
```
```sql
=> CREATE USER multimaster_user WITH SUPERUSER;
```
```sql
CREATE ROLE
```
```sql
=> CREATE DATABASE pgpro_multimaster OWNER multimaster_user;
```
```sql
CREATE DATABASE
```
```sql
=> \c pgpro_multimaster multimaster_user
```
```text
You are now connected to database "pgpro_multimaster" as user "multimaster_user".
```
Аналогично нужно подготовить еще два экземпляра на портах 5002 и 5003.
На третьем узле добавим расширение referee:
```sh
student$ psql -p 5003 -U multimaster_user -d pgpro_multimaster
```
```sql
=> CREATE EXTENSION referee;
```
```sql
CREATE EXTENSION
```
На каждом из двух основных узлов добавим в файл конфигурации строку подключения к узлу-рефери:
```text
/var/lib/pgpro/ent-16-MM-1/conf.d/referee.conf
```
```text
multimaster.referee_connstring = 'dbname=pgpro_multimaster user=multimaster_user
port=5003'
```
```text
/var/lib/pgpro/ent-16-MM-2/conf.d/referee.conf
```
```text
multimaster.referee_connstring = 'dbname=pgpro_multimaster user=multimaster_user
port=5003'
```
Перезапустим первый и второй узлы, чтобы применить настройки:
```sh
postgres$ /opt/pgpro/ent-16/bin/pg_ctl \
  -w \
  -D /var/lib/pgpro/ent-16-MM-1 \
  stop
```
```text
waiting for server to shut down........ done
server stopped
```
```sh
postgres$ /opt/pgpro/ent-16/bin/pg_ctl \
  -w \
  -D /var/lib/pgpro/ent-16-MM-2 \
  stop
```
```text
waiting for server to shut down....... done
server stopped
```
```sh
postgres$ /opt/pgpro/ent-16/bin/pg_ctl \
  -w \
  -D /var/lib/pgpro/ent-16-MM-1 \
  -l /var/lib/pgpro/ent-16-MM-1/pgpro-ent.log \
  start
```

## Page 25: Page 25 {#source-page-025}

```text
waiting for server to start.... done
server started
```
```sh
student$ psql -p 5001 -U multimaster_user -d pgpro_multimaster
```
```sh
postgres$ /opt/pgpro/ent-16/bin/pg_ctl \
  -w \
  -D /var/lib/pgpro/ent-16-MM-2 \
  -l /var/lib/pgpro/ent-16-MM-2/pgpro-ent.log \
  start
```
```text
waiting for server to start.... done
server started
```
```sh
student$ psql -p 5002 -U multimaster_user -d pgpro_multimaster
```
Инициализируем кластер мультимастера.
```sql
=> CREATE EXTENSION multimaster;
```
```sql
CREATE EXTENSION
```
Теперь запустим функцию инициализации кластера. Первый параметр — это строка подключения к текущему узлу, второй — подключение ко второму узлу:
```sql
=> SELECT mtm.init_cluster(
     'dbname=pgpro_multimaster user=multimaster_user port=5001',
   '{"dbname=pgpro_multimaster user=multimaster_user port=5002"}');
```
```text
 init_cluster
--------------
(1 row)
```
```sql
=> SELECT * FROM mtm.status() \gx
```
```text
-[ RECORD 1 ]------+-------
my_node_id         | 1
status             | online
connected          | {1,2}
gen_num            | 3
gen_members        | {1,2}
gen_members_online | {1,2}
gen_configured     | {1,2}
```
Создадим таблицу с первичным ключом multimaster\_tbl и добавим в нее несколько строк:
```sql
=> CREATE TABLE multimaster_tbl (id integer PRIMARY KEY);
```
```sql
CREATE TABLE
```
```sql
=> INSERT INTO multimaster_tbl VALUES (1),(2),(3);
```
```sql
INSERT 0 3
```
Проверим доступность таблицы multimaster\_tbl на втором узле:
```sql
=> SELECT * FROM multimaster_tbl;
```
```text
 id
----
  1
  2
  3
(3 rows)
```
### 2. Имитация сбоя и проверка работоспособности
Имитируем отказ узла. Для этого отключим один из узлов, например, второй:
```sh
postgres$ /opt/pgpro/ent-16/bin/pg_ctl \
  -w \
  -D /var/lib/pgpro/ent-16-MM-2 \
  stop
```
```text
waiting for server to shut down..... done
server stopped
```
```sql
=> SELECT * FROM mtm.status() \gx
```

## Page 26: Page 26 {#source-page-026}

```text
-[ RECORD 1 ]------+-------
my_node_id         | 1
status             | online
connected          | {1}
gen_num            | 4
gen_members        | {1}
gen_members_online | {1}
gen_configured     | {1,2}
```
Добавим еще несколько строк в таблицу:
```sql
=> INSERT INTO multimaster_tbl VALUES (11),(22),(33);
```
```sql
INSERT 0 3
```
Оставшийся узел сохранил работоспособность.
### 3. Возвращение узла в строй
Вернем в строй второй узел:
```sh
postgres$ /opt/pgpro/ent-16/bin/pg_ctl \
  -w \
  -D /var/lib/pgpro/ent-16-MM-2 \
  -l /var/lib/pgpro/ent-16-MM-2/pgpro-ent.log \
  start
```
```text
waiting for server to start.... done
server started
```
```sh
student$ psql -p 5002 -U multimaster_user -d pgpro_multimaster
```
На втором узле проверим, что таблица доступна:
```sql
=> SELECT * FROM multimaster_tbl;
```
```text
 id
----
  1
  2
  3
 11
 22
 33
(6 rows)
```
Кластер мультимастера продолжает работать в режиме 2+1.
### Уборка
Остановим и удалим экземпляры multimaster и запустим основной.
```sh
postgres$ /opt/pgpro/ent-16/bin/pg_ctl \
  -w \
  -D /var/lib/pgpro/ent-16-MM-1 \
  stop
```
```text
waiting for server to shut down..... done
server stopped
```
```sh
postgres$ /opt/pgpro/ent-16/bin/pg_ctl \
  -w \
  -D /var/lib/pgpro/ent-16-MM-2 \
  stop
```
```text
waiting for server to shut down.... done
server stopped
```
```sh
postgres$ /opt/pgpro/ent-16/bin/pg_ctl \
  -w \
  -D /var/lib/pgpro/ent-16-MM-3 \
  stop
```
```text
waiting for server to shut down....... done
server stopped
```
```sh
student$ sudo rm -rf /var/lib/pgpro/ent-16-MM-1 /var/lib/pgpro/ent-16-MM-2 /var/lib/pgpro/ent-16-MM-3
```
```sh
student$ sudo systemctl start postgrespro-ent-16.service
```

## Page 27: Page 27 {#source-page-027}
