Перейти к содержимому

04 Занятие 04: Репликация / Физическая репликация

Исходная страница в нативном Markdown: Репликация / Физическая репликация
Примечание

Эта страница извлечена из официального PDF в нативный Markdown. Текст слайдов, код и комментарии сохранены в порядке чтения; исходные PDF и HTML остаются эталоном макета. HTML source: dba3_04_replica_physical.html Archive: DBA3-handouts-16.zip Страниц PDF: 39

Page 1: Репликация / Физическая репликация

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

© Postgres Professional, 2017–2025 Авторы: Егор Рогов, Павел Лузанов, Илья Баштанов, Алексей Береснев Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)

Использование материалов курса

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

Обратная связь

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

Отказ от ответственности

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

Page 2: Темы

  • Задачи репликации
  • Схема работы физической репликации
  • Способы доставки журнальных записей
  • Особенности и ограничения использования реплики
  • Синхронная и асинхронная репликация
  • Мониторинг репликации
  • Возможные проблемы и способы их решения

Page 3: Задачи репликации

Репликация

  • процесс синхронизации нескольких копий кластера баз данных
  • на разных серверах
  • Базовый механизм для решения ряда задач
    • отказоустойчивость
    • при возникновении сбоя система
    • должна сохранить работоспособность
    • масштабируемость
    • распределение нагрузки между серверами

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

Одиночный сервер, управляющий базами данных, может не удовлетворять требованиям, предъявляемым к системе. Есть две основных задачи, для решения которых требуется наличие нескольких серверов.

Во-первых, отказоустойчивость: один физический сервер — это возможная точка отказа. Если сервер выходит из строя, система становится недоступной.

Во-вторых, производительность. Один сервер может не справляться с нагрузкой. Зачастую желательна возможность не вертикального, а горизонтального масштабирования — распределения нагрузки на несколько серверов. Дело может быть как в стоимости аппаратуры, так и в необходимости различных настроек для разных типов нагрузки (например, для OLTP и отчетности).

В случае PostgreSQL распределенные системы строятся по принципу «shared nothing»: несколько серверов работают независимо друг от друга и не имеют ни общей оперативной памяти, ни общих дисков. Следовательно, если серверы должны работать с одними и теми же данными, то эти данные требуется синхронизировать между ними. Механизм синхронизации и называется репликацией.

Page 4: Мастер→реплика

Записи WAL передаются на реплику и применяются

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

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

При физической репликации серверы имеют назначенные роли: один является ведущим («мастер»), а остальные — ведомыми («реплики»).

Мастер передает на реплику журнальные записи, а реплика применяет эти записи к своим файлам данных. Применение происходит чисто механически, без «понимания смысла» изменений, и выполняется быстрее, чем работало бы повторное выполнение операторов SQL. Но критична двоичная совместимость между серверами — они должны работать на одной и той же программно-аппаратной платформе и на них должны быть запущены одинаковые основные версии PostgreSQL.

https://postgrespro.ru/docs/postgresql/16/hot-standby#HOT-STANDBY-ADM IN

Журнал — общий для всего кластера, поэтому реплицировать можно только кластер целиком, а не отдельные базы данных или таблицы. Возможность «отфильтровать» журнальные записи отсутствует.

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

Page 5: Потоковая репликация

Source slide page 5: Потоковая репликация{#source-figure-005 caption=“Source slide page 5: Потоковая репликация”}

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

При наличие файла recovery.signal сервер восстанавливается из резервной копии: проигрывает журнальные записи, восстанавливает согласованность и завершает восстановление. Запуск реплики выполняется точно так же, но восстановление не завершается: реплика продолжает читать и воспроизводить журнальные записи мастера. Сигналом к этому служит файл standby.signal.

В теме «Базовая резервная копия» мы рассматривали два способа доставки журнальных записей от сервера к клиенту: потоковая и файловая. Они же применяются и для доставки журнальных записей реплике. На практике в основном используется потоковая репликация.

В этом случае процесс wal receiver реплики подключается к мастеру по протоколу репликации, используя строку подключения из параметра primary_conninfo, и читает поток записей WAL. За счет этого при потоковой репликации отставание реплики от мастера минимально (и при необходимости может быть сведено к нулю). Полученные записи пишутся на диск в виде сегментов WAL так же, как это происходит на мастере. Далее процесс startup — тот же самый, что обеспечивает восстановление после сбоев — применяет записи к файлам данных, только теперь он работает постоянно.

Мастер запускает процесс wal sender, обслуживающий соединение с репликой. Потоковая репликация может, если нужно, использовать слот репликации. Имя слота передается в параметре primary_slot_name.

Page 6: Настройка потоковой репликации

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

α=> SELECT pg_create_physical_replication_slot('replica');
 pg_create_physical_replication_slot
-------------------------------------
 (replica,)
(1 row)

Посмотрим на созданный слот:

α=> SELECT * FROM pg_replication_slots \gx
-[ RECORD 1 ]-------+---------
slot_name           | replica
plugin              |
slot_type           | physical
datoid              |
database            |
temporary           | f
active              | f
active_pid          |
xmin                |
catalog_xmin        |
restart_lsn         |
confirmed_flush_lsn |
wal_status          |
safe_wal_size       |
two_phase           | f
conflicting         |

Вначале слот не инициализирован (restart_lsn и wal_status пустые). Сформируем автономную резервную копию в каталоге данных сервера beta, используя созданный слот. При запуске с ключом -R утилита добавит в копию файлы, необходимые для запуска будущей реплики.

student$ sudo pg_ctlcluster 16 beta stop
Cluster is not running.
postgres$ rm -rf /var/lib/postgresql/16/beta
postgres$ pg_basebackup \
  --checkpoint=fast \
  --pgdata=/var/lib/postgresql/16/beta \
  -R --slot=replica

Снова проверим слот:

α=> SELECT * FROM pg_replication_slots \gx
-[ RECORD 1 ]-------+----------
slot_name           | replica
plugin              |
slot_type           | physical
datoid              |
database            |
temporary           | f
active              | f
active_pid          |
xmin                |
catalog_xmin        |
restart_lsn         | 0/2000000
confirmed_flush_lsn |
wal_status          | reserved
safe_wal_size       |
two_phase           | f
conflicting         |

Page 7: Page 7

После выполнения резервной копии слот инициализировался, и мастер теперь хранит все файлы журнала с начала копирования (restart_lsn, wal_status). Сравните со строкой в backup_label:

postgres$ head -n 1 /var/lib/postgresql/16/beta/backup_label
START WAL LOCATION: 0/2000028 (file 000000010000000000000002)

Файл postgresql.auto.conf был подготовлен утилитой pg_basebackup, поскольку мы указали ключ -R. Этот файл содержит информацию для подключения к мастеру и имя слота репликации:

postgres$ cat /var/lib/postgresql/16/beta/postgresql.auto.conf
# Do not edit this file manually!
# It will be overwritten by the ALTER SYSTEM command.
primary_conninfo = 'user=postgres passfile=''/var/lib/postgresql/.pgpass''
channel_binding=prefer host=''/var/run/postgresql'' port=5432 sslmode=prefer
sslnegotiation=postgres sslcompression=0 sslcertmode=allow sslsni=1
ssl_min_protocol_version=TLSv1.2 gssencmode=prefer krbsrvname=postgres gssdelegation=0
target_session_attrs=any load_balance_hosts=disable'
primary_slot_name = 'replica'

По умолчанию реплика будет «горячей», то есть сможет выполнять запросы во время восстановления. Если такая возможность не нужна, реплику можно сделать «теплой» (hot_standby = off). Утилита также создала сигнальный файл standby.signal, наличие которого указывает серверу войти в режим постоянного восстановления.

postgres$ ls -l /var/lib/postgresql/16/beta/standby.signal
-rw------- 1 postgres postgres 0 апр 10 18:04
/var/lib/postgresql/16/beta/standby.signal

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

student$ sudo pg_ctlcluster 16 beta start

Page 8: Репликация с архивом

Source slide page 8: Репликация с архивом{#source-figure-008 caption=“Source slide page 8: Репликация с архивом”}

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

Другой способ доставки журнальных записей состоит в том, чтобы предоставить реплике доступ к архиву журналов — точно так же, как при обычном восстановлении из архива (этот механизм рассмотрен в модуле «Резервное копирование»).

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

В принципе, репликация может работать и с одним только архивом, без потоковой репликации. Но в этом случае:

● реплика вынужденно отстает от мастера на время заполнения сегмента;

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

Page 9: Процессы

Мастер Реплика

  • wal sender
  • wal receiver
  • startup
  • archiver
  • archiver
  • (archive_mode = always)
  • checkpointer
  • checkpointer
  • (контрольные точки)
  • (точки перезапуска)
  • background writer
  • background writer
  • wal writer
  • autovacuum

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

На мастере и на реплике выполняется разный набор процессов. Кроме уже рассмотренных wal sender (на мастере) и wal receiver и startup (на реплике), работают также другие процессы.

Общие процессы:

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

● checkpointer — выполняет точки перезапуска (restart point, см. тему «Архив журналов предзаписи»). Если в процессе восстановления случится сбой, реплика сможет продолжить с последней точки перезапуска, а не с самого начала.

Процессы только на мастере:

● wal writer — реплика не создает журналы упреждающей записи, а только получает их с мастера.

● autovacuum launcher/worker — реплика не выполняет очистку, ее выполнение на мастере передается реплике через журнал.

Есть также процесс, наличие которого зависит от настройки:

● archiver — при значении archive_mode = on выполняется только на мастере, а при archive_mode = always также и на реплике (подробнее рассматривается в теме «Переключение на реплику»).

Page 10: Процессы реплики

Сравним процессы на мастере:

student$ ps f -C postgres -o pid,command | grep alpha
  69430 /usr/lib/postgresql/16/bin/postgres -D /var/lib/postgresql/16/alpha -c
config_file=/etc/postgresql/16/alpha/postgresql.conf
  69431  \_ postgres: 16/alpha: checkpointer
  69432  \_ postgres: 16/alpha: background writer
  69434  \_ postgres: 16/alpha: walwriter
  69435  \_ postgres: 16/alpha: autovacuum launcher
  69436  \_ postgres: 16/alpha: logical replication launcher
  69479  \_ postgres: 16/alpha: student student [local] idle
  69980  \_ postgres: 16/alpha: walsender postgres [local] streaming 0/3000060

И на реплике:

student$ ps f -C postgres -o pid,command | grep beta
  69975 /usr/lib/postgresql/16/bin/postgres -D /var/lib/postgresql/16/beta -c
config_file=/etc/postgresql/16/beta/postgresql.conf
  69976  \_ postgres: 16/beta: checkpointer
  69977  \_ postgres: 16/beta: background writer
  69978  \_ postgres: 16/beta: startup recovering 000000010000000000000003
  69979  \_ postgres: 16/beta: walreceiver streaming 0/3000060

Page 11: Очистка архива

Source slide page 11: Очистка архива{#source-figure-011 caption=“Source slide page 11: Очистка архива”}

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

В точках перезапуска процесс checkpointer будут удалять ненужные сегменты журнала в локальном каталоге pg_wal.

Если архив используется только для синхронизации одной реплики, можно и его очищать автоматически. Для этого в параметре archive_cleanup_command задается команда, которая будет выполняться по окончании каждой точки перезапуска. В команде удобно вызывать утилиту pg_archivecleanup (вместо %r сервер подставит имя сегмента, содержащего точку):

archive_cleanup_command = ‘pg_archivecleanup /path/to/archive %r’

https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#RUNTIME-CO NFIG-WAL-ARCHIVE-RECOVERY

https://postgrespro.ru/docs/postgresql/16/pgarchivecleanup

Page 12: Использование реплики

Допускаются

  • запросы на чтение данных (select, copy to, курсоры)
  • установка параметров сервера (set, reset)
  • управление транзакциями (begin, commit, rollback…)
  • создание резервной копии (pg_basebackup)
  • Не допускаются
    • любые изменения (insert, update, delete, truncate, nextval…)
    • блокировки, предполагающие изменение (select for update…)
    • команды DDL (create, drop…), в том числе создание временных таблиц
    • команды сопровождения (vacuum, analyze, reindex…)
    • управление доступом (grant, revoke…)

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

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

● изменения таблиц, материализованных представлений, последовательностей;

● блокировки (так как для этого требуется изменение страниц данных);

● команды DDL, включая создание временных таблиц;

● такие команды, как VACUUM и ANALYZE;

● команды управления доступом.

При этом реплика может выполнять запросы на чтение данных, если установлен параметр hot_standby = on. Также будет работать установка параметров сервера и команды управления транзакциями — например, можно начать (читающую) транзакцию с нужным уровнем изоляции.

Кроме того, реплику можно использовать и для изготовления резервных копий.

Page 13: Использование реплики

Выполним несколько команд на мастере:

α=> CREATE DATABASE replica_physical;
CREATE DATABASE
α=> \c replica_physical
You are now connected to database "replica_physical" as user "student".
α=> CREATE TABLE test(s text);
CREATE TABLE
α=> INSERT INTO test VALUES ('Привет, мир!');
INSERT 0 1

Проверим реплику:

student$ psql -p 5433
β=> \c replica_physical
You are now connected to database "replica_physical" as user "student".
β=> SELECT * FROM test;
      s
--------------
 Привет, мир!
(1 row)

При этом изменения на реплике не допускаются:

β=> INSERT INTO test VALUES ('Replica');
ERROR:  cannot execute INSERT in a read-only transaction

Вообще реплику от мастера можно отличить с помощью функции:

β=> SELECT pg_is_in_recovery();
 pg_is_in_recovery
-------------------
 t
(1 row)

Page 14: Ограничения

Изоляция и многоверсионность

  • большое число точек сохранения задерживает выполнение запросов
  • не поддерживается уровень изоляции Serializable
  • Триггеры и блокировки
    • триггеры и рекомендательные блокировки не работают
  • Резервное копирование с реплики
    • параметр full_page_writes должен быть заранее включен на мастере
    • задержки из-за ожидания контрольной точки и переключения сегментов
  • Порядок изменений
    • каталоги табличных пространств нужно сначала создавать на реплике
    • значения параметров max_… нужно сначала увеличивать на реплике

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

У репликации есть ряд ограничений и особенностей.

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

Не поддерживается уровень изоляции Serializable на реплике.

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

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

Создание табличных пространств реплицируется, но если у пользователя ОС, под которым работает СУБД, недостаточно прав для создания каталога, произойдет ошибка. Лучше всего сначала создать каталоги на мастере и на всех репликах, а уже затем выполнять команду CREATE TABLESPACE.

Если на мастере увеличить параметр, задающий ограничение какого-либо ресурса, репликация может приостановиться. Это относится к параметрам max_connections, max_prepared_transactions, max_locks_per_transaction, max_wal_senders и max_worker_processes.

Page 15: Мониторинг

Source slide page 15: Мониторинг{#source-figure-015 caption=“Source slide page 15: Мониторинг”}

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

В ходе репликации возможны проблемы, о которых должен предупредить заранее настроенный мониторинг. Основной информацией являются позиции в журнале предзаписи. Можно выделить пять важных точек:

  1. появление журнальной записи на мастере; 2) передача записи процессом wal sender; 3) получение записи процессом wal receiver; 4) синхронизация записи с диском (flush); 5) применение полученной записи процессом startup.

Все эти точки обычно отслеживаются на мастере. Первую точку дает функция pg_current_wal_lsn, остальные — представление pg_stat_replication. Реплика передает мастеру статус репликации при каждой записи на диск, но как минимум раз в интервал времени, указанный в wal_receiver_status_interval (по умолчанию — 10 секунд).

Если используется слот репликации, то информацию о нем можно получить из представления pg_replication_slots.

Часть информации о ходе репликации можно получить и на реплике в представлении pg_stat_wal_receiver и с помощью функций pg_last_wal_receive_lsn и pg_last_wal_replay_lsn.

Состояние репликации показывают функции pg_is_in_recovery и pg_get_wal_replay_pause_state. Параметр log_replication_commands включает вывод в журнал сообщений информации о выполняющихся командах протокола репликации.

Page 16: Мониторинг репликации

Сейчас реплика находится в режиме восстановления и применяет журнальные записи:

β=> SELECT pg_is_in_recovery(), pg_get_wal_replay_pause_state();
 pg_is_in_recovery | pg_get_wal_replay_pause_state
-------------------+-------------------------------
 t                 | not paused
(1 row)

Текущее значение max_connections на мастере:

α=> SHOW max_connections;
 max_connections
-----------------
 100
(1 row)

Если количество пользователей выросло, мы захотим увеличить это ограничение:

α=> ALTER SYSTEM SET max_connections = 150;
ALTER SYSTEM
student$ sudo pg_ctlcluster 16 alpha restart

А что с репликой?

β=> SELECT pg_is_in_recovery(), pg_get_wal_replay_pause_state();
 pg_is_in_recovery | pg_get_wal_replay_pause_state
-------------------+-------------------------------
 t                 | paused
(1 row)

Репликация остановилась. В журнале видим причину:

student$ tail -n 7 /var/log/postgresql/postgresql-16-beta.log
2026-04-10 18:04:40.731 MSK [69978] WARNING:  hot standby is not possible because of
insufficient parameter settings
2026-04-10 18:04:40.731 MSK [69978] DETAIL:  max_connections = 100 is a lower setting
than on the primary server, where its value was 150.
2026-04-10 18:04:40.731 MSK [69978] CONTEXT:  WAL redo at 0/3433088 for
XLOG/PARAMETER_CHANGE: max_connections=150 max_worker_processes=8 max_wal_senders=10
max_prepared_xacts=0 max_locks_per_xact=64 wal_level=replica wal_log_hints=off
track_commit_timestamp=off
2026-04-10 18:04:40.731 MSK [69978] LOG:  recovery has paused
2026-04-10 18:04:40.731 MSK [69978] DETAIL:  If recovery is unpaused, the server will
shut down.
2026-04-10 18:04:40.731 MSK [69978] HINT:  You can then restart the server after making
the necessary configuration changes.
2026-04-10 18:04:40.731 MSK [69978] CONTEXT:  WAL redo at 0/3433088 for
XLOG/PARAMETER_CHANGE: max_connections=150 max_worker_processes=8 max_wal_senders=10
max_prepared_xacts=0 max_locks_per_xact=64 wal_level=replica wal_log_hints=off
track_commit_timestamp=off

Последуем рекомендации: увеличим значение параметра на реплике.

β=> ALTER SYSTEM SET max_connections = 150;
ALTER SYSTEM
student$ sudo pg_ctlcluster 16 beta restart

Проверим реплику:

student$ psql -p 5433 -d replica_physical
β=> SELECT pg_is_in_recovery(), pg_get_wal_replay_pause_state();

Page 17: Page 17

 pg_is_in_recovery | pg_get_wal_replay_pause_state
-------------------+-------------------------------
 t                 | not paused
(1 row)

Репликация возобновилась. Чтобы избежать остановки репликации, ограничения нужно сначала увеличивать на реплике. Состояние репликации можно смотреть в специальном представлении на мастере. Чтобы пользователь получил доступ к этой информации, он должен входить в групповую роль pg_read_all_stats или быть суперпользователем.

student$ psql -d replica_physical
α=> \drg student|pg_monitor
                      List of role grants
 Role name  |       Member of        |   Options    | Grantor
------------+------------------------+--------------+----------
 pg_monitor | pg_read_all_settings   | INHERIT, SET | postgres
 pg_monitor | pg_read_all_stats      | INHERIT, SET | postgres
 pg_monitor | pg_stat_scan_tables    | INHERIT, SET | postgres
 student    | pg_checkpoint          | INHERIT, SET | postgres
 student    | pg_create_subscription | INHERIT, SET | postgres
 student    | pg_monitor             | INHERIT, SET | postgres
 student    | pg_read_server_files   | INHERIT, SET | postgres
 student    | pg_write_server_files  | INHERIT, SET | postgres
(8 rows)

Роль pg_monitor входит в роль pg_read_all_stats и наследует ее привилегии.

α=> SELECT * FROM pg_stat_replication \gx
-[ RECORD 1 ]----+------------------------------
pid              | 70966
usesysid         | 10
usename          | postgres
application_name | 16/beta
client_addr      |
client_hostname  |
client_port      | -1
backend_start    | 2026-04-10 18:04:46.571317+03
backend_xmin     |
state            | streaming
sent_lsn         | 0/34375E0
write_lsn        | 0/34375E0
flush_lsn        | 0/34375E0
replay_lsn       | 0/34375E0
write_lag        | 00:00:00.000249
flush_lag        | 00:00:00.007946
replay_lag       | 00:00:00.00842
sync_priority    | 0
sync_state       | async
reply_time       | 2026-04-10 18:04:49.464114+03

Обратите внимание на поля *_lsn (и *_lag) — они показывают отставание реплики на разных этапах. Сейчас все позиции совпадают, отставание нулевое. Теперь вставим в таблицу большое количество строк, чтобы увидеть репликацию в процессе работы.

α=> INSERT INTO test SELECT 'Just a line' FROM generate_series(1,1_000_000);
INSERT 0 1000000
α=> SELECT *, pg_current_wal_lsn() from pg_stat_replication \gx

Page 18: Page 18

-[ RECORD 1 ]------+------------------------------
pid                | 70966
usesysid           | 10
usename            | postgres
application_name   | 16/beta
client_addr        |
client_hostname    |
client_port        | -1
backend_start      | 2026-04-10 18:04:46.571317+03
backend_xmin       |
state              | streaming
sent_lsn           | 0/7320000
write_lsn          | 0/72C0000
flush_lsn          | 0/7220000
replay_lsn         | 0/721FFD0
write_lag          | 00:00:00.358711
flush_lag          | 00:00:00.397451
replay_lag         | 00:00:00.397454
sync_priority      | 0
sync_state         | async
reply_time         | 2026-04-10 18:04:54.015211+03
pg_current_wal_lsn | 0/7915350

Видно, что возникла небольшая задержка. Проверим реплику:

β=> SELECT count(*) FROM test;
  count
---------
 1000001
(1 row)

Все строки успешно доставлены. И еще раз проверим состояние репликации:

α=> SELECT *, pg_current_wal_lsn() from pg_stat_replication \gx
-[ RECORD 1 ]------+------------------------------
pid                | 70966
usesysid           | 10
usename            | postgres
application_name   | 16/beta
client_addr        |
client_hostname    |
client_port        | -1
backend_start      | 2026-04-10 18:04:46.571317+03
backend_xmin       |
state              | streaming
sent_lsn           | 0/7917360
write_lsn          | 0/7917360
flush_lsn          | 0/7917360
replay_lsn         | 0/7917360
write_lag          | 00:00:00.054624
flush_lag          | 00:00:00.067903
replay_lag         | 00:00:00.074573
sync_priority      | 0
sync_state         | async
reply_time         | 2026-04-10 18:04:54.336274+03
pg_current_wal_lsn | 0/7917360

Все позиции выровнялись.

Page 19: Режимы репликации

Асинхронный

  • synchronous_commit
  • = off
  • local
  • Синхронный
    • фиксация с ожиданием подтверждения от синхронной реплики
    • обеспечивает надежность, но не согласованность
    • synchronous_commit
    • = remote_write
    • on
    • remote_apply
    • synchronous_standby_names = список реплик
    • FIRST N (список реплик)
    • ANY N (список реплик)

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

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

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

При значении on мастер ожидает подтверждения о попадании журнальной записи на диск реплики (это надежный режим; однако приложение, обратившись к реплике, может не увидеть изменений).

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

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

Page 20: Потеря нужного сегмента

Мастер может удалить файл журнала, нужный реплике

  • если реплика не успеет его получить
  • (например, будет остановлена на некоторое время)
  • Решение 1. Слот репликации
    • вводит зависимость мастера от реплики, требует мониторинга
    • max_slot_wal_keep_size = −1
  • Решение 2. Архив журнала предзаписи
    • позволяет обойтись без слота

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

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

Сервер PostgreSQL периодически удаляет файлы журнала, которые не требуются для восстановления. При этом мастер может удалить файл, который содержит данные, еще не переданные реплике.

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

Но если архива нет, реплика не сможет продолжать восстановление и ее придется пересоздавать из новой резервной копии. Чтобы этого избежать, можно использовать слот репликации. Однако надо понимать, что создание слота ставит мастер в зависимость от реплики: если реплика не будет получать журнальные записи, они будут накапливаться на мастере, что может привести к заполнению файловой системы. Чтобы не допустить этого, стоит установить предел накапливаемого объема параметром max_slot_wal_keep_size. При превышении этого ограничения слот перестанет функционировать и удерживать журнальные файлы от удаления.

В любом случае слот — еще одна точка, которую необходимо включать в мониторинг.

Page 21: Влияние слота репликации

Остановим реплику.

student$ sudo pg_ctlcluster 16 beta stop

Ограничим размер WAL двумя сегментами.

α=> ALTER SYSTEM SET min_wal_size = '32MB';
ALTER SYSTEM
α=> ALTER SYSTEM SET max_wal_size = '32MB';
ALTER SYSTEM
student$ sudo pg_ctlcluster 16 alpha reload

Слот неактивен, но помнит номер последней записи, полученной репликой:

α=> SELECT active, restart_lsn, wal_status FROM pg_replication_slots \gx
-[ RECORD 1 ]----------
active      | f
restart_lsn | 0/7917360
wal_status  | reserved

Вставим еще строки в таблицу.

α=> INSERT INTO test SELECT 'Just a line' FROM generate_series(1,1_000_000);
INSERT 0 1000000

Номер LSN в слоте не изменился:

α=> SELECT active, restart_lsn, wal_status FROM pg_replication_slots \gx
-[ RECORD 1 ]----------
active      | f
restart_lsn | 0/7917360
wal_status  | extended

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

α=> CHECKPOINT;
CHECKPOINT

Каков теперь размер журнала?

α=> SELECT pg_size_pretty(sum(size)) FROM pg_ls_waldir();
 pg_size_pretty
----------------
 96 MB
(1 row)

Контрольная точка не удалила сегменты — их удерживает слот, несмотря на превышение ограничения. Если оставить слот без присмотра, дисковое пространство может быть исчерпано. Скорее всего, бесперебойная работа основного сервера важнее, чем синхронизация реплики. Чтобы предотвратить разрастание журнала, можно ограничить объем, удерживаемый слотом:

α=> ALTER SYSTEM SET max_slot_wal_keep_size = '16MB';
ALTER SYSTEM
student$ sudo pg_ctlcluster 16 alpha reload
α=> CHECKPOINT;
CHECKPOINT

Теперь журнал очищается контрольной точкой:

α=> SELECT pg_size_pretty(sum(size)) FROM pg_ls_waldir();

Page 22: Page 22

 pg_size_pretty
----------------
 32 MB
(1 row)

Но слот уже не обеспечивает наличие записей WAL (wal_status=lost):

α=> SELECT active, restart_lsn, wal_status FROM pg_replication_slots \gx
-[ RECORD 1 ]-----
active      | f
restart_lsn |
wal_status  | lost

А реплика не может синхронизироваться:

student$ sudo pg_ctlcluster 16 beta start
student$ sudo tail -n 2 /var/log/postgresql/postgresql-16-beta.log
2026-04-10 18:05:03.584 MSK [72247] FATAL:  could not receive data from WAL stream:
ERROR:  requested WAL segment 000000010000000000000007 has already been removed
2026-04-10 18:05:03.584 MSK [72242] LOG:  waiting for WAL to become available at 0/7002000

Для синхронизации реплики в таких случаях можно воспользоваться архивом, задав параметр restore_command. Если это невозможно, придется заново настроить репликацию, повторив формирование базовой копии.

Page 23: Конфликты: проблема

Записи WAL, несовместимые с запросами на реплике

  • очистка удаляет версии строк, нужные запросам на реплике
  • исключительные блокировки объектов
  • закрепление буферов
  • Мониторинг
    • pg_stat_database_conflicts

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

Вторая проблема возникает, если реплика используется для выполнения запросов. Мастер может выполнить действие, несовместимое с запросом на реплике:

● очистка может удалить версию строки, которая используется запросом, или потребовать блокировку, несовместимую с запросом;

● может прийти запись об исключительной блокировке объекта (например, при удалении таблицы);

● иногда конфликт возникает из-за закрепления буфера.

Возникновение таких конфликтов можно отслеживать с помощью представления pg_stat_database_conflicts.

Page 24: Конфликты: решения

Решение 0: избегать конфликтов

  • не выполнять команды, вызывающие блокировки
  • отменить усечение файла в конце очистки: параметр хранения
  • vacuum_truncate = off
  • Решение 1: откладывать применение конфликтующих записей
    • max_standby_streaming_delay
    • max_standby_archive_delay
    • кроме взаимоблокировок, DROP TABLESPACE, DROP DATABASE

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

Один из путей борьбы с конфликтами — избегать действий на мастере, приводящих к исключительным блокировкам. Например, можно не выполнять команды DROP TABLE, TRUNCATE, LOCK, DROP INDEX, DROP TRIGGER и т. п. или выполнять их в то время, когда на реплике заведомо нет выполняющихся запросов.

Некоторых неявных блокировок можно избежать, отключая соответствующую функциональность на мастере. Например, если в результате очистки освободились страницы в конце файла, при уменьшении его размера устанавливается исключительная блокировка таблицы. Но можно отменить усечение файла, задав параметр хранения таблицы vacuum_truncate = off. Однако отменять или надолго откладывать очистку нельзя, поэтому избежать конфликта в случае очистки нужных запросу версий строк таким образом не получится.

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

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

Page 25: Конфликты: решения

Решение 2: обратная связь по протоколу репликации

  • снимок на реплике предотвращает удаление версий строк
  • так же, как и локальный
  • hot_standby_feedback = on
  • только для конфликта очистки с запросом на реплике
  • Мониторинг
    • pg_stat_replication.backend_xmin
    • pg_replication_slots.xmin

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

Еще один путь решения — откладывать несовместимые действия на мастере. Для этого включается обратная связь.

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

Каждый снимок данных определяет так называемый горизонт — это значение счетчика транзакций, по которому проходит граница между ненужными и нужными версиями строк. Минимальный горизонт по всем снимкам реплики определяет возможность очистки. Подробности обсуждаются в теме «Снимки данных» курса DBA2.

Благодаря обратной связи мастер понимает, какие версии строк нужны реплике. В таком случае (с точки зрения очистки) запрос на реплике ничем не отличается от запроса на самом мастере.

Обратная связь позволяет отслеживать горизонт очистки на стороне мастера. Если слот репликации не используется, горизонт виден в поле pg_stat_replication.backend_xmin, а при использовании слота — в pg_replication_slots.xmin. Нужно учитывать, что при включенной обратной связи слот, даже неактивный, будет задерживать очистку, что может привести к увеличению размеров файлов данных. Этот эффект будет исследован в практике.

Page 26: Итоги

Механизм репликации основан на передаче журнальных записей на реплику и их применении

  • основной режим: потоковая репликация
  • может быть поддержана архивом журнальных записей
  • Реплика — точная копия мастера
    • не генерирует собственные записи WAL, доступна только для чтения
  • Сложный механизм, требующий настройки и мониторинга
    • настройка существенно зависит от решаемых задач

Page 27: Практика

  1. Настройте потоковую репликацию между двумя серверами в синхронном режиме, со слотом, без обратной связи. 2. Проверьте работу репликации. Убедитесь, что при остановленной реплике фиксация не завершается. 3. Воспроизведите ситуацию, при которой запрос на реплике прерывается из-за очистки версий строк на мастере. 4. Запретите откладывать применение конфликтующих изменений; проверьте, что запрос отменяется сразу. 5. Включите обратную связь и убедитесь, что очистка на мастере не прерывает выполнение запроса. 6. Остановите реплику и убедитесь, что слот препятствует очистке. Удалите слот, чтобы очистка возобновилась.

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

  1. Для синхронного режима установите на мастере параметры:

● synchronous_commit = on

● synchronous_standby_names = ‘“16/beta”’

  1. Для этого на реплике можно начать транзакцию с уровнем изоляции Repeatable Read и периодически выполнять запросы к таблице, а на мастере изменить эту таблицу и выполнить очистку.

Учтите, что параметр max_standby_streaming_delay по умолчанию имеет значение 30 секунд.

  1. Установите параметр max_standby_streaming_delay в ноль.

  2. Установите на реплике параметр hot_standby_feedback = on. Максимальный интервал оповещений устанавливается в параметре wal_receiver_status_interval (по умолчанию 10 секунд).

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

Не забудьте отменить синхронную фиксацию, иначе при остановленной реплике не удастся зафиксировать транзакцию. Не устанавливайте synchronous_commit = off. Вместо этого установите synchronous_commit = ’local’ или сбросьте значение параметра synchronous_standby_names.

Page 28: 1. Физическая потоковая репликация в синхронном режиме

α=> CREATE DATABASE replica_physical;
CREATE DATABASE

Создаем и запускаем реплику точно так же, как в демонстрации.

α=> \c replica_physical
You are now connected to database "replica_physical" as user "student".

Резервная копия:

student$ sudo pg_ctlcluster 16 beta stop
Cluster is not running.
postgres$ rm -rf /var/lib/postgresql/16/beta
postgres$ pg_basebackup \
  --pgdata /var/lib/postgresql/16/beta \
  --checkpoint=fast \
  -R --create-slot --slot=replica
student$ sudo pg_ctlcluster 16 beta start

Для того чтобы основной сервер мог идентифицировать реплику, в основной конфигурации уже установлен параметр cluster_name:

student$ psql -p 5433 -c 'SHOW cluster_name;'
 cluster_name
--------------
 16/beta
(1 row)

Другой вариант — добавить атрибут application_name в строку соединения:

primary_conninfo='user=student port=5432 application_name=16/beta'

Теперь включаем на мастере режим синхронной репликации.

α=> ALTER SYSTEM SET synchronous_commit = on;
ALTER SYSTEM
α=> ALTER SYSTEM SET synchronous_standby_names = '"16/beta"';
ALTER SYSTEM

Если все реплики синхронные, можно задать значение, соответствующее любому имени:

synchronous_standby_names = '*'

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

student$ sudo pg_ctlcluster 16 alpha reload

2. Проверка физической репликации

α=> SELECT * FROM pg_stat_replication \gx

Page 29: Page 29

-[ RECORD 1 ]----+------------------------------
pid              | 93766
usesysid         | 10
usename          | postgres
application_name | 16/beta
client_addr      |
client_hostname  |
client_port      | -1
backend_start    | 2026-04-10 18:17:47.327757+03
backend_xmin     |
state            | streaming
sent_lsn         | 0/3000C50
write_lsn        | 0/3000C50
flush_lsn        | 0/3000C50
replay_lsn       | 0/3000C50
write_lag        | 00:00:00.000472
flush_lag        | 00:00:00.012854
replay_lag       | 00:00:00.013517
sync_priority    | 1
sync_state       | sync
reply_time       | 2026-04-10 18:17:50.171064+03

sync_state: sync говорит о том, что репликация работает в синхронном режиме; application_name: 16/beta — имя, под которым мастер знает реплику. Остановим реплику.

student$ sudo pg_ctlcluster 16 beta stop
α=> BEGIN;
BEGIN
α=> CREATE TABLE test(id integer);
CREATE TABLE
α=> COMMIT;

Фиксация ждет появления синхронной реплики…

student$ sudo pg_ctlcluster 16 beta start

После старта реплики фиксация завершается.

COMMIT

3. Отмена запроса из-за очистки на мастере

α=> INSERT INTO test VALUES (1);
INSERT 0 1
student$ psql -p 5433 -d replica_physical

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

β=> \dconfig deadlock_timeout
List of configuration parameters
    Parameter     | Value
------------------+-------
 deadlock_timeout | 1s
(1 row)

Для этого установим параметр log_recovery_conflict_waits.

β=> ALTER SYSTEM SET log_recovery_conflict_waits = on;
ALTER SYSTEM

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

β=> ALTER SYSTEM SET max_standby_streaming_delay = '9s';
ALTER SYSTEM
student$ sudo pg_ctlcluster 16 beta reload

Начинаем транзакцию с уровнем изоляции Repeatable Read: первый оператор создаст снимок данных, который будут

Page 30: Page 30

использовать все операторы транзакции.

β=> BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN
β=> SELECT * FROM test;
 id
----
  1
(1 row)

На мастере изменяем единственную строку и выполняем очистку. При этом первая версия строки будет удалена.

α=> UPDATE test SET id = 2;
UPDATE 1
α=> VACUUM test;
VACUUM

Через 5 секунд запрос на реплике еще сработает — реплика задерживает применение конфликтующей журнальной записи:

β=> SELECT * FROM test;
 id
----
  1
(1 row)

А в журнале отчета сервера уже есть сообщение об ожидании разрешения конфликта:

postgres$ tail -3 /var/log/postgresql/postgresql-16-beta.log
2026-04-10 18:17:57.155 MSK [94207] LOG:  recovery still waiting after 1024.273 ms:
recovery conflict on snapshot
2026-04-10 18:17:57.155 MSK [94207] DETAIL:  Conflicting process: 94293.
2026-04-10 18:17:57.155 MSK [94207] CONTEXT:  WAL redo at 0/301B8A0 for Heap2/PRUNE:
snapshotConflictHorizon: 738, nredirected: 1, ndead: 0, nunused: 0, redirected: [1->2],
dead: [], unused: []; blkref #0: rel 1663/16409/16410, blk 0

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

β=> SELECT * FROM test;
FATAL:  terminating connection due to conflict with recovery
DETAIL:  User query might have needed to see row versions that must be removed.
HINT:  In a moment you should be able to reconnect to the database and repeat your
command.
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost

4. Запрет откладывания применения конфликтующей записи

Выставим нулевое время задержки применения конфликтующих изменений.

student$ psql -p 5433 -d replica_physical
β=> ALTER SYSTEM SET max_standby_streaming_delay = 0;
ALTER SYSTEM
student$ sudo pg_ctlcluster 16 beta reload

Снова начинаем транзакцию…

β=> BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN
β=> SELECT * FROM test;

Page 31: Page 31

 id
----
  2
(1 row)

На мастере изменяем строку и выполняем очистку…

α=> UPDATE test SET id = 3;
UPDATE 1
α=> VACUUM test;
VACUUM

Запрос на реплике прерывается тут же:

β=> SELECT * FROM test;
FATAL:  terminating connection due to conflict with recovery
DETAIL:  User query might have needed to see row versions that must be removed.
HINT:  In a moment you should be able to reconnect to the database and repeat your
command.
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost

5. Обратная связь

Установим обратную связь и, чтобы не ждать долго, небольшой интервал оповещений.

student$ psql -p 5433 -d replica_physical
β=> ALTER SYSTEM SET hot_standby_feedback = on;
ALTER SYSTEM
β=> ALTER SYSTEM SET wal_receiver_status_interval = '1s';
ALTER SYSTEM
student$ sudo pg_ctlcluster 16 beta reload

Снова начинаем транзакцию…

β=> BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN
β=> SELECT * FROM test;
 id
----
  3
(1 row)

На мастере изменяем строку и выполняем очистку…

α=> UPDATE test SET id = 4;
UPDATE 1
α=> VACUUM VERBOSE test;
INFO:  vacuuming "replica_physical.public.test"
INFO:  finished vacuuming "replica_physical.public.test": index scans: 0
pages: 0 removed, 1 remain, 1 scanned (100.00% of total)
tuples: 0 removed, 2 remain, 1 are dead but not yet removable
removable cutoff: 740, which was 1 XIDs old when operation ended
frozen: 0 pages from table (0.00% of total) had 0 tuples frozen
index scan not needed: 0 pages from table (0.00% of total) had 0 dead item identifiers
removed
avg read rate: 0.000 MB/s, avg write rate: 0.000 MB/s
buffer usage: 10 hits, 0 misses, 0 dirtied
WAL usage: 1 records, 0 full page images, 188 bytes
system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
VACUUM

Благодаря обратной связи, очистка не может удалить старую версию строки (tuples: 0 removed, 2 remain, 1 are dead but not

Page 32: Page 32

yet removable).

β=> SELECT * FROM test;
 id
----
  3
(1 row)
β=> COMMIT;
COMMIT
α=> VACUUM VERBOSE test;
INFO:  vacuuming "replica_physical.public.test"
INFO:  finished vacuuming "replica_physical.public.test": index scans: 0
pages: 0 removed, 1 remain, 1 scanned (100.00% of total)
tuples: 1 removed, 1 remain, 0 are dead but not yet removable
removable cutoff: 741, which was 0 XIDs old when operation ended
new relfrozenxid: 740, which is 1 XIDs ahead of previous value
frozen: 0 pages from table (0.00% of total) had 0 tuples frozen
index scan not needed: 0 pages from table (0.00% of total) had 0 dead item identifiers
removed
avg read rate: 0.000 MB/s, avg write rate: 0.000 MB/s
buffer usage: 11 hits, 0 misses, 0 dirtied
WAL usage: 3 records, 0 full page images, 308 bytes
system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
VACUUM

После завершения транзакции на реплике мастер может выполнить очистку (tuples: 1 removed, 1 remain, 0 are dead but not yet removable). Журнальные записи реплицируются, но ничего не сломают.

6. Влияние слота на очистку при остановленной реплике

Теперь отменим синхронизацию и остановим реплику:

α=> ALTER SYSTEM RESET synchronous_standby_names;
ALTER SYSTEM
student$ sudo pg_ctlcluster 16 alpha reload
student$ sudo pg_ctlcluster 16 beta stop

Еще раз изменим строку таблицы:

α=> UPDATE test SET id = 5;
UPDATE 1

Слот неактивен, но помнит минимальный xmin снимков реплики:

α=> SELECT active, xmin FROM pg_replication_slots;
 active | xmin
--------+------
 f      |  741
(1 row)

Это значение xmin меньше, чем минимальный xmin локальных снимков:

α=> SELECT min(backend_xmin::text::numeric) FROM pg_stat_activity;
 min
-----
 742
(1 row)

Поэтому слот будет задерживать очистку (tuples: 0 removed, 2 remain, 1 are dead but not yet removable).

α=> VACUUM VERBOSE test;

Page 33: Page 33

INFO:  vacuuming "replica_physical.public.test"
INFO:  finished vacuuming "replica_physical.public.test": index scans: 0
pages: 0 removed, 1 remain, 1 scanned (100.00% of total)
tuples: 0 removed, 2 remain, 1 are dead but not yet removable
removable cutoff: 741, which was 1 XIDs old when operation ended
frozen: 0 pages from table (0.00% of total) had 0 tuples frozen
index scan not needed: 0 pages from table (0.00% of total) had 0 dead item identifiers
removed
avg read rate: 0.000 MB/s, avg write rate: 0.000 MB/s
buffer usage: 10 hits, 0 misses, 0 dirtied
WAL usage: 1 records, 0 full page images, 188 bytes
system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
VACUUM

Удалим слот.

α=> SELECT pg_drop_replication_slot('replica');
 pg_drop_replication_slot
--------------------------
(1 row)

Теперь очистка сработает (tuples: 1 removed).

α=> VACUUM VERBOSE test;
INFO:  vacuuming "replica_physical.public.test"
INFO:  finished vacuuming "replica_physical.public.test": index scans: 0
pages: 0 removed, 1 remain, 1 scanned (100.00% of total)
tuples: 1 removed, 1 remain, 0 are dead but not yet removable
removable cutoff: 742, which was 0 XIDs old when operation ended
new relfrozenxid: 741, which is 1 XIDs ahead of previous value
frozen: 0 pages from table (0.00% of total) had 0 tuples frozen
index scan not needed: 0 pages from table (0.00% of total) had 0 dead item identifiers
removed
avg read rate: 0.000 MB/s, avg write rate: 0.000 MB/s
buffer usage: 11 hits, 0 misses, 0 dirtied
WAL usage: 3 records, 0 full page images, 308 bytes
system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
VACUUM

Уборка

Удалим созданную базу данных и сбросим настройки параметров конфигурации.

α=> \c postgres
You are now connected to database "postgres" as user "student".
α=> DROP DATABASE replica_physical;
DROP DATABASE
α=> ALTER SYSTEM RESET synchronous_commit;
ALTER SYSTEM
α=> ALTER SYSTEM RESET synchronous_standby_names;
ALTER SYSTEM
student$ sudo pg_ctlcluster 16 alpha restart

Page 34: Практика+

  1. Настройте потоковую физическую репликацию вместе с архивацией сегментов WAL, использовав для потока репликации сетевой интерфейс виртуальной машины. 2. Проверьте работу потоковой репликации через этот сетевой интерфейс. 3. Сымитируйте отказ сети, выключив сетевой интерфейс виртуальной машины. 4. Убедитесь, что потоковая репликация остановилась, при этом реплика получает изменения благодаря архивации сегментов WAL, но с задержками. 5. Заново запустите сетевой интерфейс виртуальной машины, проверив затем, что потоковая репликация автоматически восстановилась.

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

  1. Проверить состояние сетевых интерфейсов можно командой: ip a

Проверить доступность сетевого адреса можно командой: ping -c1 <IP адрес>

Внесите изменения в файл pg_hba.conf для разрешения подключений к серверу alpha через любые сетевые интерфейсы, а также измените конфигурацию этого сервера так, чтобы он прослушивал все сетевые интерфейсы.

Процесс wal_receiver завершается, если сетевое соединение отсутствует в течение времени wal_receiver_timeout. Задайте небольшое значение этого параметра.

  1. Отказ сети смоделируйте, выключив сетевой интерфейс: sudo ip l set enp0s3 down

  2. Убедитесь, что после переключения сегмента WAL реплика получает изменения, сделанные до переключения.

  3. Включить сетевой интерфейс можно командой: sudo ip l set enp0s3 up

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

Page 35: 1. Физическая потоковая репликация + архив WAL

Имя основного сетевого интерфейса виртуальной машины enp0s3:

student$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen
1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group
default qlen 1000
    link/ether 08:00:27:62:bf:96 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute enp0s3
       valid_lft 69519sec preferred_lft 69519sec
    inet6 fd17:625c:f037:2:f831:c519:5f7e:335c/64 scope global temporary dynamic
       valid_lft 86345sec preferred_lft 14345sec
    inet6 fd17:625c:f037:2:a00:27ff:fe62:bf96/64 scope global dynamic mngtmpaddr
       valid_lft 86345sec preferred_lft 14345sec
    inet6 fe80::a00:27ff:fe62:bf96/64 scope link
       valid_lft forever preferred_lft forever

Проверим доступность основного сетевого интерфейса виртуальной машины.

student$ ping -c1 10.0.2.15
PING 10.0.2.15 (10.0.2.15) 56(84) bytes of data.
64 bytes from 10.0.2.15: icmp_seq=1 ttl=64 time=0.027 ms
--- 10.0.2.15 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.027/0.027/0.027/0.000 ms

Разрешим подключения к серверу alpha с адресов IPv4, принадлежащих сетям интерфейсов сервера.

postgres$ sed -i.bak 's/127\.0\.0\.1\/32/samenet/' /etc/postgresql/16/alpha/pg_hba.conf

Включим прослушивание всех интерфейсов сервером и настроим архивирование WAL.

α=> ALTER SYSTEM SET listen_addresses = '*';
ALTER SYSTEM
α=> ALTER SYSTEM SET archive_mode = on;
ALTER SYSTEM
α=> ALTER SYSTEM SET archive_command =
'test ! -f /var/lib/postgresql/archive/%f && cp %p /var/lib/postgresql/archive/%f';
ALTER SYSTEM

Каталог для архива WAL.

postgres$ mkdir /var/lib/postgresql/archive
student$ sudo pg_ctlcluster 16 alpha restart

Резервная копия:

student$ sudo pg_ctlcluster 16 beta stop
Cluster is not running.
postgres$ rm -rf /var/lib/postgresql/16/beta
postgres$ pg_basebackup --pgdata=/var/lib/postgresql/16/beta --checkpoint=fast

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

postgres$ cat << EOF | tee /etc/postgresql/16/beta/conf.d/rest.conf
restore_command = 'cp /var/lib/postgresql/archive/%f %p'
primary_conninfo = 'user=postgres password=postgres host=10.0.2.15 port=5432'
wal_receiver_timeout = '2s'
EOF
restore_command = 'cp /var/lib/postgresql/archive/%f %p'

Page 36: 2. Проверка репликации

primary_conninfo = 'user=postgres password=postgres host=10.0.2.15 port=5432'
wal_receiver_timeout = '2s'
postgres$ touch /var/lib/postgresql/16/beta/standby.signal
student$ sudo pg_ctlcluster 16 beta start
student$ psql
α=> CREATE DATABASE replica_physical;
CREATE DATABASE
α=> \c replica_physical
You are now connected to database "replica_physical" as user "student".
α=> CREATE TABLE test(s text);
CREATE TABLE
α=> INSERT INTO test VALUES ('Привет, мир!');
INSERT 0 1

Реплика получает записи WAL через поток…

student$ psql -p 5433
β=> SELECT pid, status, written_lsn, flushed_lsn, latest_end_lsn, sender_host
FROM pg_stat_wal_receiver \gx
-[ RECORD 1 ]--+----------
pid            | 96630
status         | streaming
written_lsn    | 0/3486580
flushed_lsn    | 0/3486580
latest_end_lsn | 0/3486580
sender_host    | 10.0.2.15

…и сообщает мастеру о принятых записях без задержек:

α=> SELECT * FROM pg_stat_replication \gx
-[ RECORD 1 ]----+------------------------------
pid              | 96631
usesysid         | 10
usename          | postgres
application_name | 16/beta
client_addr      | 10.0.2.15
client_hostname  |
client_port      | 59474
backend_start    | 2026-04-10 18:18:50.909683+03
backend_xmin     |
state            | streaming
sent_lsn         | 0/3486580
write_lsn        | 0/3486580
flush_lsn        | 0/3486580
replay_lsn       | 0/3486580
write_lag        | 00:00:00.00072
flush_lag        | 00:00:00.008024
replay_lag       | 00:00:00.008634
sync_priority    | 0
sync_state       | async
reply_time       | 2026-04-10 18:18:57.480665+03
β=> \c replica_physical
You are now connected to database "replica_physical" as user "student".

Таблица реплицирована:

β=> SELECT * FROM test;
      s
--------------
 Привет, мир!
(1 row)

Page 37: 3. Отказ сети

Выключаем сетевой интерфейс — авария сети.

student$ sudo ip l set enp0s3 down

Сеть недоступна.

student$ ping -c1 10.0.2.15
ping: connect: Network is unreachable

Процесс wal receiver остановлен:

β=> SELECT * FROM pg_stat_wal_receiver \gx
(0 rows)

На мастере добавляем строку…

α=> INSERT INTO test VALUES ('Отказ сети.');
INSERT 0 1

…но реплика ее не получает:

β=> SELECT * FROM test;
      s
--------------
 Привет, мир!
(1 row)

Потоковая репликация остановилась:

α=> SELECT sent_lsn, write_lsn FROM pg_stat_replication \gx
-[ RECORD 1 ]--------
sent_lsn  | 0/34865F8
write_lsn | 0/3486580

4. Архивация

Однако архивация на мастере продолжает работать. Переключим сегмент WAL:

α=> SELECT pg_walfile_name(pg_current_wal_lsn()), pg_switch_wal();
     pg_walfile_name      | pg_switch_wal
--------------------------+---------------
 000000010000000000000003 | 0/3486660
(1 row)
α=> INSERT INTO test VALUES ('Сегмент WAL переключен.');
INSERT 0 1

Сегмент попал в архив:

α=> SELECT * FROM pg_stat_archiver \gx
-[ RECORD 1 ]------+------------------------------
archived_count     | 4
last_archived_wal  | 000000010000000000000003
last_archived_time | 2026-04-10 18:19:07.410852+03
failed_count       | 0
last_failed_wal    |
last_failed_time   |
stats_reset        | 2025-11-25 18:23:08.681015+03

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

β=> SELECT * FROM test;

Page 38: 5. Восстановление сети

      s
--------------
 Привет, мир!
 Отказ сети.
(2 rows)

Вставим еще одну строку в таблицу на мастере.

α=> INSERT INTO test VALUES ('Сеть еще неисправна...');
INSERT 0 1

На реплику она не попадает.

β=> SELECT * FROM test;
      s
--------------
 Привет, мир!
 Отказ сети.
(2 rows)

Теперь восстановим сетевой интерфейс — авария ликвидирована.

student$ sudo ip l set enp0s3 up
student$ ping -c1 10.0.2.15
PING 10.0.2.15 (10.0.2.15) 56(84) bytes of data.
64 bytes from 10.0.2.15: icmp_seq=1 ttl=64 time=0.018 ms
--- 10.0.2.15 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.018/0.018/0.018/0.000 ms

Опять работает потоковая репликация:

α=> SELECT * FROM pg_stat_replication \gx
-[ RECORD 1 ]----+------------------------------
pid              | 98407
usesysid         | 10
usename          | postgres
application_name | 16/beta
client_addr      | 10.0.2.15
client_hostname  |
client_port      | 57020
backend_start    | 2026-04-10 18:19:16.503782+03
backend_xmin     |
state            | streaming
sent_lsn         | 0/4000170
write_lsn        | 0/4000170
flush_lsn        | 0/4000170
replay_lsn       | 0/4000170
write_lag        |
flush_lag        |
replay_lag       |
sync_priority    | 0
sync_state       | async
reply_time       | 2026-04-10 18:19:16.632099+03

На мастере добавляем еще строку.

α=> INSERT INTO test VALUES ('Сеть восстановлена!');
INSERT 0 1

Реплика получила все строки.

β=> SELECT * FROM test;

Page 39: Уборка

Source slide page 39: Уборка{#source-figure-039 caption=“Source slide page 39: Уборка”}

            s
-------------------------
 Привет, мир!
 Отказ сети.
 Сегмент WAL переключен.
 Сеть еще неисправна...
 Сеть восстановлена!
(5 rows)

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

α=> \c postgres
You are now connected to database "postgres" as user "student".
α=> DROP DATABASE replica_physical;
DROP DATABASE
β=> \c postgres
You are now connected to database "postgres" as user "student".
β=> ALTER SYSTEM RESET listen_addresses;
ALTER SYSTEM
β=> ALTER SYSTEM RESET archive_mode;
ALTER SYSTEM
β=> ALTER SYSTEM RESET archive_command;
ALTER SYSTEM
student$ sudo pg_ctlcluster 16 beta stop -m immediate --skip-systemctl-redirect
α=> ALTER SYSTEM RESET listen_addresses;
ALTER SYSTEM
α=> ALTER SYSTEM RESET archive_mode;
ALTER SYSTEM
α=> ALTER SYSTEM RESET archive_command;
ALTER SYSTEM
student$ sudo pg_ctlcluster 16 alpha restart
postgres$ rm -f /etc/postgresql/16/beta/conf.d/rest.conf