Физическая и логическая репликация, переключение, PgBouncer и исходящая очередь
Последний урок связывает эксплуатационные механизмы. Он не предполагает, что вы уже администратор: каждый новый термин сначала получает простое определение, а затем ограничения.
Высокая доступность — способность продолжить работу после отказа узла за оговорённое время и с допустимой потерей данных. Одной резервной копии сервера для этого мало. Нужен проверенный порядок переключения и защита от двух одновременно пишущих ведущих узлов.
Секционированная таблица выглядит для запросов как одна, но хранит строки в отдельных физических таблицах по диапазонам:
CREATE TABLE app_events (
id bigint GENERATED ALWAYS AS IDENTITY,
org_id bigint NOT NULL,
occurred_at timestamptz NOT NULL,
payload jsonb NOT NULL,
PRIMARY KEY (id, occurred_at)
) PARTITION BY RANGE (occurred_at);
CREATE TABLE app_events_2026_07 PARTITION OF app_events
FOR VALUES FROM ('2026-07-01 00:00+00') TO ('2026-08-01 00:00+00');
CREATE TABLE app_events_default PARTITION OF app_events DEFAULT;Если WHERE содержит совместимое условие по occurred_at, PostgreSQL отбрасывает
лишние секции. Это называется исключением секций (partition pruning).
Запрос без ключа может читать их все.
Секции также упрощают срок хранения: заранее создать следующий месяц,
отсоединить старый, архивировать его и удалить позже. Операции ATTACH,
DETACH и DROP всё равно требуют плана блокировок. Секцию по умолчанию нужно
проверять, чтобы в ней не копились строки будущих диапазонов.
Репликация — поддержание копии данных на другом сервере. При физической
репликации ведущий сервер (primary) передаёт WAL, а резервный (standby)
записывает и воспроизводит его. Резервный хранит копию всего кластера совместимой
основной версии, а не выбранные таблицы.
Состояние отправки видно на ведущем:
SELECT
application_name,
client_addr,
state,
sync_state,
sent_lsn,
write_lsn,
flush_lsn,
replay_lsn,
pg_wal_lsn_diff(sent_lsn, replay_lsn) AS replay_lag_bytes
FROM pg_stat_replication;Позиции означают:
sent_lsn — сколько WAL отправлено;write_lsn — сколько записано на резервном сервере;flush_lsn — сколько надёжно сохранено там;replay_lsn — сколько уже применено к его данным.Время отставания без новых транзакций может быть пустым или вводить в заблуждение. Смотрите также разницу в байтах, скорость WAL и задержку приложения.
При асинхронной репликации ведущий может подтвердить COMMIT до надёжного
получения WAL резервным узлом. Если ведущий потерян, несколько последних
подтверждённых транзакций могут исчезнуть.
При синхронной репликации фиксация ждёт выбранный резервный сервер:
synchronous_standby_names = 'ANY 1 (standby_a, standby_b)'
synchronous_commit = onРежим on ждёт надёжной записи на синхронном резервном узле. remote_apply
дополнительно ждёт применения WAL. Это уменьшает возможную потерю, но увеличивает
задержку. Если обязательные резервные узлы недоступны, фиксации могут остановиться.
Настройка не решает выбор правильного узла при аварии и не предотвращает двойную запись сама по себе.
Переключение при отказе (failover) — назначение резервного узла новым
ведущим. Ограждение (fencing) — гарантия, что старый ведущий больше не
может принимать запись.
До переключения система должна доказать:
Если два ведущих принимают изменения независимо, возникает раздвоение
кластера (split brain). Простая проверка TCP-порта не доказывает, что старый
узел перестал писать.
Регулярная тренировка отказа измеряет RTO, потерянные и повторные операции, переподключение клиентов и поведение приложения.
Логическая репликация передаёт изменения выбранных таблиц в виде логических операций. На источнике создаётся публикация, на получателе — подписка:
CREATE PUBLICATION marketplace_pub
FOR TABLE marketplace.orders, marketplace.payments;
CREATE SUBSCRIPTION marketplace_sub
CONNECTION 'host=primary.internal dbname=course user=replicator sslmode=verify-full passfile=/run/secrets/replication.pgpass'
PUBLICATION marketplace_pub;Пароль не записывают в урок или репозиторий. Защищённый файл доставляет система секретов.
Встроенная логическая репликация не переносит автоматически DDL, роли и текущее
значение последовательностей. Нужны совместимая структура, идентификатор строки
для UPDATE и DELETE, правила конфликтов, наблюдение и план начального
копирования.
Она помогает переходить между основными версиями, но нулевой простой требует отдельной репетиции переключения.
Слот репликации хранит позицию получателя и удерживает нужный ему WAL:
SELECT
slot_name,
slot_type,
active,
restart_lsn,
confirmed_flush_lsn,
pg_size_pretty(
pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)
) AS retained_wal
FROM pg_replication_slots;Если получатель остановился, удерживаемый WAL может заполнить диск ведущего. Оповещение учитывает скорость роста и свободное место. Удаление слота теряет позицию и требует согласованного восстановления получателя.
Пул соединений держит ограниченное число подключений к PostgreSQL и обслуживает большее число клиентов.
COMMIT или
ROLLBACK. Следующая транзакция клиента может попасть в другой процесс.Для пула транзакций проверьте код на сеансовый SET, временные таблицы,
сеансовые рекомендательные блокировки, LISTEN/NOTIFY и ожидания драйвера от
подготовленных запросов.
[pgbouncer]
pool_mode = transaction
auth_type = scram-sha-256
max_client_conn = 1000
default_pool_size = 20
server_connect_timeout = 5
query_wait_timeout = 10Эти числа — пример для нагрузочного опыта, а не универсальная настройка. Для очереди пула нужны метрики ожидания и отказов. В недоверенной сети используют TLS и управляемое хранилище секретов.
Репликация не делает атомарными изменение таблицы и отправку в брокер сообщений. Транзакционная исходящая очередь записывает бизнес-изменение и будущее событие в одной транзакции:
BEGIN;
INSERT INTO payment_inbox (external_id, org_id, order_id, amount)
VALUES ($1, $2, $3, $4)
ON CONFLICT (external_id) DO NOTHING
RETURNING external_id;
UPDATE orders
SET status = 'paid'
WHERE id = $3 AND org_id = $2;
INSERT INTO outbox (topic, payload)
VALUES ('payment.accepted', jsonb_build_object('order_id', $3));
COMMIT;В полном варианте заказ и исходящее событие меняются только если входящее
событие действительно добавлено. Отдельный процесс читает outbox и публикует
сообщение после COMMIT.
Отправитель может повторить публикацию после сбоя между отправкой и отметкой
published_at, поэтому получатель тоже должен обрабатывать повторы по ключу
идемпотентности.
В лаборатории 28 вы свяжете входящее событие, изменение заказа и исходящую очередь.
Итоговый проект
объединяет модель данных, идемпотентность, транзакции, планы, индексы, RLS,
миграцию, секции, восстановление, пул соединений и отказоустойчивость. Файлы
rubric.md и evidence-template.md перечисляют проверяемые результаты.
Пример модуля показывает только минимальную связь входящего события, заказа и исходящего события. Решения по ёмкости, безопасности, копиям и аварийной инструкции нужно обосновать отдельно.