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

27. PostgreSQL HA, Replication, pgbouncer

Зачем знать на Middle 3: Senior отвечает за SLA БД. На уровне Middle 3 ты должен понимать, что такое streaming/logical replication, synchronous_commit trade-offs, как поднимается Patroni в Kubernetes/etcd, чем transaction pooling отличается от session pooling в pgbouncer и какие фичи Postgres он ломает. Без этих знаний делать zero-downtime upgrade, миграцию между мажорными версиями, multi-AZ HA или просто переживать промоушн реплики — невозможно.


  1. Концепция
  2. Production-deep dive
  3. Gotchas
  4. Real cases
  5. Вопросы
  6. Practice
  7. Источники

PostgreSQL предлагает два независимых вида репликации:

  • Physical (streaming) replication — побайтовая копия WAL на реплику. Реплика — идентичный кластер, может работать как hot standby (read-only) или warm standby.
  • Logical replication — декодирование WAL в логический поток (INSERT/UPDATE/DELETE), publisher/subscriber. Реплика — другая БД, можно реплицировать выборочно, между разными мажорными версиями.

Поверх них строятся HA-tooling’и: Patroni, repmgr, pg_auto_failover. Они отвечают за health check, leader election, автоматический failover и реконфигурацию.

pgbouncer — connection pooler, который сидит между приложением и Postgres. Постгрес не любит тысячи коннектов (1 backend = 1 процесс = 5-10MB RAM + контекстные переключения). Pool позволяет 10k клиентов работать через 100 серверных коннектов.

┌──────────────┐
│ App pods │ (200 × Go-сервис)
└──────┬───────┘
│ 10k pgx-коннектов
┌──────────────┐
│ pgbouncer │ transaction pool
└──────┬───────┘
│ 100 реальных backend'ов
┌─────────────┴─────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ primary │ ──WAL──► │ standby 1 │
│ │ ──WAL──► │ standby 2 │
└──────────────┘ └──────────────┘
│ DCS (etcd/consul)
┌──────────────┐
│ Patroni │ leader election, failover
└──────────────┘

┌───────────────── Primary ─────────────────┐
│ │
│ Backend → WAL buffer → WAL writer ──┐ │
│ ▼ │
│ walsender (per replica)
│ │
└───────────────────────────────────────│────┘
│ TCP
┌───────────────── Standby ─────────────│────┐
│ ▼ │
│ walreceiver │
│ │ │
│ ▼ │
│ WAL on disk │
│ │ │
│ ▼ │
│ startup (recovery)
│ redoим WAL → буфера
└────────────────────────────────────────────┘
# postgresql.conf
wal_level = replica # или logical (включает оба режима)
max_wal_senders = 10 # сколько standby одновременно
max_replication_slots = 10
wal_keep_size = 1024 # MB (PG 13+, иначе wal_keep_segments)
synchronous_commit = on
synchronous_standby_names = 'ANY 1 (s1, s2)'
hot_standby = on

pg_hba.conf:

host replication repl_user 10.0.0.0/8 scram-sha-256
Окно терминала
# на standby
pg_basebackup -h primary -D /var/lib/postgresql/16/main \
-U repl_user -P -X stream -R -S standby_1
# -R создаст standby.signal и пропишет primary_conninfo
# -S зарегистрирует replication slot
systemctl start postgresql

Без слота primary может удалить WAL, нужный standby (если тот отстал). Со слотом WAL сохраняется до тех пор, пока standby его не подтвердит.

⚠️ Опасность слота: если standby «умер» и не подтверждает, WAL накапливается на primary до заполнения диска. Мониторинг:

SELECT slot_name, active, restart_lsn,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS lag
FROM pg_replication_slots;
ЗначениеЧто значитLatencyDurability
offНе ждём даже своего WAL fsyncminrisk lost
localЖдём fsync на primary, не ждём standbyfastlocal OK
remote_writeЖдём, что standby получил WAL (но мог ещё не fsync)mediumOK при HA
on (default)= local, если sync_standby_names пуст. Иначе ждём fsync на standbyhighstrong
remote_applyЖдём, что standby реально применил WAL и видит измененияhigheststrongest

synchronous_standby_names:

synchronous_standby_names = 'ANY 1 (s1, s2, s3)' # quorum: хотя бы один из трёх
synchronous_standby_names = 'FIRST 2 (s1, s2, s3)' # приоритетный список: s1, s2

⚠️ Только sync replication даёт zero data loss. Async — может потерять последние транзакции при сбое primary.

На standby могут идти долгие read-only запросы. Если primary за это время VACUUM’нул нужные строки — standby отменит запрос с ошибкой «canceling statement due to conflict with recovery».

# на standby
hot_standby_feedback = on

Standby сообщает primary, какой XID он держит. Primary не вакуумит «живые для standby» строки.

⚠️ Risk: на primary копится bloat, если standby долго держит снапшот. Мониторь pg_stat_database.xact_commit/rollback vs возраст snapshot на standby.

PG 10+. Декодирует WAL → INSERT/UPDATE/DELETE по логическим объектам.

-- на publisher
CREATE PUBLICATION my_pub FOR TABLE users, orders;
-- ALTER PUBLICATION my_pub ADD TABLE invoices;
-- на subscriber (другая БД, возможно другая мажорная версия)
CREATE SUBSCRIPTION my_sub
CONNECTION 'host=primary dbname=app user=repl password=...'
PUBLICATION my_pub;

Поведение:

  • Replica identity нужна для UPDATE/DELETE: либо PRIMARY KEY, либо REPLICA IDENTITY USING INDEX, либо FULL.
  • DDL не реплицируется (только DML).
  • Sequence — нет (PG 16+ — частично).
  • TRUNCATE — реплицируется опционально.

Use cases:

  1. Zero-downtime major upgrade:
    PG 14 primary ──logical──► PG 16 subscriber
    Cut over: переключаем приложение
    Stop publisher, promote subscriber
  2. Partial replication: аналитика отдельной БД хочет только нужные таблицы.
  3. CDC — Debezium стоит на logical replication.
  4. Multi-master шардинг — самописные / pglogical.

⚠️ Initial sync копирует всю таблицу — на больших таблицах долго и нагрузочно. Для огромных можно copy_data = false + руками pg_dump/pg_restore под одним снапшотом и CREATE SUBSCRIPTION ... WITH (copy_data = false).

Patroni — open-source HA-template от Zalando.

Архитектура:

┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ patroni 1 │ │ patroni 2 │ │ patroni 3 │
│ +pg │ │ +pg │ │ +pg │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└────────────┬────┴────────────┬────┘
▼ ▼
┌────────────────────────┐
│ DCS: etcd / consul │
│ / zookeeper / k8s │
└────────────────────────┘
leader election lease

Flow:

  1. Каждый Patroni-агент периодически (ttl/loop_wait) пишет в DCS heartbeat.
  2. Один из агентов держит leader-lock (TTL key в etcd). Он управляет primary Postgres.
  3. Если leader не продлевает lock в TTL — другой агент берёт его. Делает pg_rewind / pg_basebackup (если нужно), pg_ctl promote.
  4. Patroni также рулит конфигурацией: глобальный YAML в DCS = source of truth.

Конфиг (фрагмент patroni.yml):

scope: pg-cluster
namespace: /db/
name: pg01
restapi:
listen: 0.0.0.0:8008
etcd:
hosts: etcd1:2379,etcd2:2379,etcd3:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
synchronous_mode: true
postgresql:
parameters:
wal_level: replica
max_wal_senders: 10
max_replication_slots: 10
synchronous_commit: 'on'
synchronous_standby_names: '*'
postgresql:
data_dir: /var/lib/postgresql/data
bin_dir: /usr/lib/postgresql/16/bin
authentication:
replication: { username: repl, password: 'repl_pass' }
superuser: { username: postgres, password: 'pg_pass' }

HAproxy + Patroni REST:

Patroni даёт HTTP-эндпоинт /master, /replica, /standby-leader. HAProxy опрашивает их (HTTP check) и роутит трафик на primary / replica.

listen postgres_master
bind *:5432
option httpchk OPTIONS /master
server pg01 10.0.0.1:5432 check port 8008
server pg02 10.0.0.2:5432 check port 8008
server pg03 10.0.0.3:5432 check port 8008

Patroni в Kubernetes: есть operator (Zalando postgres-operator, Crunchy PGO). Используют PVC + StatefulSet + ConfigMap, etcd может быть встроенный или managed.

  • repmgr — старее, требует ручного failover (repmgr standby switchover) или с auto-failover daemon. Проще, меньше зависимостей, но менее популярен.
  • pg_auto_failover (Citus / Microsoft) — monitor + nodes, simpler setup, без DCS.
  • Stolon — etcd/consul-based, по сути аналог Patroni.
ModeКогда возвращает коннект в poolUse caseЧто ломается
sessionПосле disconnect клиентаДолгие сессии, prepared statementsПочти ничего
transactionПосле COMMIT / ROLLBACKWeb/API workloadPrepared statements*, advisory locks, LISTEN
statementПосле каждого SQL-statementАналитика, read-onlyТранзакции вообще

* PG 14+ + pgbouncer 1.21+ поддерживает server-side prepared statements в transaction mode.

В transaction mode коннект может оказаться у другого клиента между транзакциями:

  • SET work_mem = '100MB'; — потеряется в следующем коннекте.
  • LISTEN ... — не будет работать.
  • pg_advisory_lock (session-level) — текущий клиент потеряет лок.
  • Серверные cursor’ы вне транзакции — недоступны.
  • WITH HOLD cursors — недоступны.
  • Temp tables — теряются после COMMIT (в transaction mode они общие на коннект).

Решение для SET: SET LOCAL внутри транзакции — работает.

Решение для prepared statements (PG 14+):

pgbouncer.ini
server_prepared_statements = true
max_prepared_statements = 256

И в Go (pgx):

config.DefaultQueryExecMode = pgx.QueryExecModeCacheStatement
[databases]
app = host=pg-master.local port=5432 dbname=app
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 10000
default_pool_size = 50
reserve_pool_size = 10
reserve_pool_timeout = 5
server_idle_timeout = 600
server_lifetime = 3600
query_wait_timeout = 120
stats_users = stats_user
admin_users = admin

Параметры:

  • default_pool_size — сколько серверных коннектов на (user, db). 50 — типично; на больше — нужен max_connections на самом Postgres ≥ N_users × pool_size + резерв.
  • max_client_conn — лимит клиентских коннектов к pgbouncer. Не путать с pool_size.
  • reserve_pool_size — экстра-коннекты при перегрузке.
  • server_lifetime — насильное закрытие коннекта раз в N секунд (помогает с накоплением state в одной сессии и обновлением DNS).

Админ-команды (через psql на 6432):

SHOW POOLS; -- состояние пулов
SHOW STATS; -- запросы / время
SHOW CLIENTS; -- кто подключён
RELOAD; -- перезагрузить конфиг
PAUSE; RESUME; -- остановить/возобновить новые запросы

Часто слышишь «PG14 теперь сам умеет pooling». На самом деле — поддержка server-side prepared statements в pgbouncer transaction mode улучшилась, плюс есть proxy в EDB / Aurora. Полноценного встроенного pool в community PG до сих пор нет. pgbouncer и PgPool-II остаются стандартом.

АспектpgbouncerPgPool-II
PoolДа (transaction/session/statement)Да
Load balancingНет (надо HAProxy)Да, на основе SQL parse (read → replica)
FailoverНет (внешний)Да (свой watchdog)
Query rewritingНетДа
СложностьНизкая, легкийВысокая, тяжёлый
Production winnerЧаще выбирают, прощеКому нужны его доп. фичи

В Go-приложении проще всего:

type DB struct {
Write *pgxpool.Pool // primary
Read *pgxpool.Pool // replica (через HAProxy /replica)
}
// в репозитории:
func (r *OrdersRepo) GetByID(ctx context.Context, id int64) (*Order, error) {
return query(ctx, r.db.Read, ...)
}
func (r *OrdersRepo) Create(ctx context.Context, o *Order) error {
return exec(ctx, r.db.Write, ...)
}

⚠️ Replica lag = stale reads. После INSERT на primary — реплика может ещё не догнать. Решения:

  1. «Read your writes» на primary, остальное — на replica.
  2. Использовать pg_current_wal_lsn() после INSERT и ждать на реплике pg_last_wal_replay_lsn() >= lsn.
  3. synchronous_commit = remote_apply + ANY 1 — медленнее, но гарантия видимости.
Окно терминала
# planned maintenance
patronictl -c /etc/patroni.yml switchover --master pg01 --candidate pg02

Patroni:

  1. Активирует quorum sync на кандидате.
  2. Ждёт catch-up.
  3. PROMOTE pg02, demote pg01 (rewind при необходимости).
  4. Обновляет DCS lease.
  5. HAProxy подхватывает (/master теперь на pg02).

Primary падает (kernel panic).

  1. Patroni-агент на pg01 не продлевает lease.
  2. Через ttl (30s) lease истекает.
  3. Patroni на pg02 (sync standby с наименьшим лагом) забирает leader-lock.
  4. PROMOTE.
  5. pg03 reconfigured на новый primary (через recovery.conf / primary_conninfo).

Downtime ≈ ttl + promote + HAProxy retry. Обычно 15-60 секунд.

Если pg01 «жив», но изолирован от etcd (network partition):

  • Через failsafe_mode (Patroni 3.0+) или внешний fence (например, STONITH в k8s) primary форсированно демотируется.
  • Без fencing — split-brain: два primary параллельно пишут. Самый страшный сценарий.

Удалённая реплика без слота не страшна. Со слотом — primary копит WAL бесконечно. Мониторь pg_replication_slots.active = false + lag.

⚠️ 2. synchronous_standby_names = ’*’ и единственный standby

Заголовок раздела «⚠️ 2. synchronous_standby_names = ’*’ и единственный standby»

Если standby упадёт, primary будет ждать ack бесконечно — запросы зависнут. Решение: ANY 1 (s1, s2) с минимум двумя standby или FIRST 1 (s1) s2 (приоритет, fallback).

Без -X stream WAL стримится последовательно; на больших БД standby может отстать. Всегда используй -X stream или -X fetch с retention.

Physical replication требует точно той же мажорной версии, минорная может отличаться. Logical replication — между мажорными работает.

Без PK или REPLICA IDENTITY: UPDATE/DELETE на subscriber молча игнорируются.

ALTER TABLE my_tbl REPLICA IDENTITY FULL; -- worst case, по всей строке

COPY под snapshot держит долгий снапшот на primary → bloat. Большие таблицы — лучше pre-load через pg_dump.

До PG 14 и/или pgbouncer < 1.21 — prepared statements в transaction mode ломаются. Go-драйвер pgx по умолчанию использует prepared. Решение: QueryExecModeSimpleProtocol или обновиться.

pgbouncer max_client_conn = 10000
pgbouncer default_pool_size = 50, databases = 4, users = 2 → ~400 server conn
postgres max_connections должно быть ≥ 400 + резерв (auto_vacuum, repl, superuser)

Долгие read’ы на standby блокируют VACUUM на primary. Если есть аналитические запросы по часу — primary распухает.

Если все standby отстают больше порога — Patroni НЕ сделает failover (защита от потерь). Кластер останется без primary. Мониторь и держи лаг низким.

При async-репликации promote standby = потеря последних async-транзакций. Только sync mode (или semi-sync) даёт zero data loss.

Чтобы старый primary вернулся в кластер как standby после promote, нужен pg_rewind. Требует wal_log_hints = on или data checksums. Без этого — pg_basebackup (полная пересинхронизация).

При transaction mode application_name меняется при каждом коннекте. Если используешь его для роутинга/логирования — путаница. Решение: SET LOCAL application_name = '...' в начале каждой транзакции.

HAProxy с TCP-режимом не закрывает коннект при смерти бэкенда мгновенно. option clitcpka + option srvtcpka + timeout server 30s помогут.

RDS Proxy ≠ pgbouncer (не поддерживает все фичи). Aurora имеет встроенный pooling, но в transaction mode тоже свои ограничения. Уточнять под конкретный stack.


Контекст: БД 2TB, downtime < 1 минуты по SLA.

Шаги:

  1. Поднят PG 16 instance параллельно.
  2. На PG 13 включён wal_level = logical.
  3. CREATE PUBLICATION app_pub FOR ALL TABLES;
  4. На PG 16: schema dump+restore, CREATE SUBSCRIPTION app_sub ….
  5. Initial sync ~ 8 часов в фоне.
  6. Ждали catch-up replication lag → 0.
  7. App в maintenance mode (30 секунд), последняя транзакция допроцессилась.
  8. Promote PG 16 (просто переключили DNS / pgbouncer target).

Risk: sequences не реплицируются. Решение — ручной SELECT setval(...) после catch-up.

Симптом: pgx (Go) кидает «prepared statement S_1 does not exist».

Расследование: pool_mode = transaction, pgx использует prepared. Между транзакциями коннект достался другому клиенту, prepare потерян.

Решение:

config.DefaultQueryExecMode = pgx.QueryExecModeExec // или SimpleProtocol

Или (лучше) обновили pgbouncer до 1.21 + PG 14:

server_prepared_statements = true

Симптом: реплика отстаёт на десятки минут.

Причина: на primary долгий autovacuum (1B строк), много WAL генерится. Standby последовательно reploit’ит — single-threaded.

Решение: max_wal_size поднят, на primary autovacuum_vacuum_cost_limit = 2000 (быстрее), на standby parallel apply (recovery_min_apply_delay = 0, max_parallel_workers_per_gather на репликах не помогает — replay однопоточный). Реально помогли:

  • партиционирование (vacuum партиций параллелится отдельно)
  • archive_command — на S3, чтобы standby мог дотягивать после паузы.

Контекст: Patroni 2.x, etcd на тех же узлах. Network partition. Старый primary не получил сигнал demote, продолжил принимать writes.

Решение:

  • Перевели etcd на отдельные nodes.
  • Включили failsafe_mode: on (Patroni 3.0+).
  • Pod на старом primary шёл с liveness probe pg_isready + Patroni REST status.
  • Добавили watchdog (systemd-watchdog).

Главный кластер в Москве, аналитика в Питере. Latency между ДЦ ~10ms. Synchronous replication — слишком дорого. Сделали:

  • async streaming replica в том же ДЦ (HA).
  • logical subscription в Питере на read-only БД (с урезанной схемой — без security-чувствительных таблиц).
  • Lag мониторится, app в Питере знает, что данные «свежие на 30 секунд».

  1. Чем отличается physical (streaming) replication от logical?
  2. Что такое WAL и какова его роль в репликации?
  3. Что такое replication slot и какие риски с ним связаны?
  4. Какие есть режимы synchronous_commit и какие у них trade-offs?
  5. Что значит synchronous_standby_names = 'ANY 2 (s1, s2, s3)'?
  6. Что такое hot standby и какие у него ограничения?
  7. Зачем нужен hot_standby_feedback и каковы его побочные эффекты?
  8. Как работает pg_basebackup? Когда -X stream обязателен?
  9. Как сделать zero-downtime major upgrade Postgres?
  10. Какие требования у logical replication к таблицам?
  11. Что такое REPLICA IDENTITY и какие у неё варианты?
  12. Что такое Patroni и какие у него зависимости?
  13. Как Patroni выбирает нового primary при failover?
  14. Что такое DCS и какие реализации (etcd, consul, k8s) есть?
  15. Что такое split-brain и как Patroni его предотвращает?
  16. Что такое pg_rewind и когда без него не обойтись?
  17. Какие есть pool mode в pgbouncer и в чём разница?
  18. Какие фичи Postgres не работают в transaction pooling?
  19. Как заставить prepared statements работать в transaction mode?
  20. Как соотносятся max_client_conn pgbouncer и max_connections Postgres?
  21. Что такое reserve_pool_size?
  22. Что нужно в HAProxy для роутинга на primary через Patroni REST?
  23. Какие риски при чтении с read-replica?
  24. Что такое semi-sync vs sync vs async и что выбрать для финтеха?
  25. Какие минимальные метрики мониторить для pgbouncer и репликации?

version: "3.9"
services:
primary:
image: postgres:16
environment:
POSTGRES_PASSWORD: pass
command: >
postgres -c wal_level=replica -c max_wal_senders=5
-c hot_standby=on -c synchronous_commit=on
ports: ["5432:5432"]
standby:
image: postgres:16
depends_on: [primary]
environment:
POSTGRES_PASSWORD: pass
entrypoint: bash -c "
until pg_basebackup -h primary -U postgres -D /var/lib/postgresql/data -R -P; do sleep 2; done;
exec docker-entrypoint.sh postgres"
ports: ["5433:5432"]
pgbouncer:
image: edoburu/pgbouncer:latest
environment:
DATABASE_URL: postgres://postgres:pass@primary:5432/postgres
POOL_MODE: transaction
MAX_CLIENT_CONN: 1000
ports: ["6432:5432"]
Окно терминала
# на standby (5433)
pg_ctl promote
# теперь standby = новый primary, старый primary надо привести обратно как standby:
pg_rewind --target-pgdata=/var/lib/postgresql/data --source-server="host=new_primary user=postgres password=pass"
echo "primary_conninfo = 'host=new_primary user=repl'" >> postgresql.auto.conf
touch /var/lib/postgresql/data/standby.signal
pg_ctl start

Подними etcd, два узла Patroni, сделай switchover, посмотри patronictl list.

cfg, _ := pgxpool.ParseConfig("postgres://app:pass@pgbouncer:6432/app?sslmode=disable")
cfg.MaxConns = 30
cfg.MinConns = 5
cfg.MaxConnLifetime = 5 * time.Minute
// Для transaction mode без PG14 prepared statements:
cfg.ConnConfig.DefaultQueryExecMode = pgx.QueryExecModeExec
pool, _ := pgxpool.NewWithConfig(ctx, cfg)

Подними два PG instance, сделай publication/subscription, проверь, что:

  • INSERT доезжает.
  • DDL не доезжает.
  • DELETE без PK не работает (поставь REPLICA IDENTITY FULL).
-- на primary
INSERT INTO t (id) VALUES (1) RETURNING pg_current_wal_lsn();
-- получишь, например, 1/2D000028
-- на standby:
SELECT pg_last_wal_replay_lsn() >= '1/2D000028';
-- false → подожди → true → читай.

  1. PostgreSQL Documentation — High Availability, Load Balancing, and Replication. https://www.postgresql.org/docs/current/high-availability.html
  2. PostgreSQL Documentation — Logical Replication. https://www.postgresql.org/docs/current/logical-replication.html
  3. Patroni Documentation. https://patroni.readthedocs.io/
  4. Patroni GitHub (Zalando). https://github.com/zalando/patroni
  5. pgbouncer documentation. https://www.pgbouncer.org/config.html
  6. pgbouncer prepared statements (1.21+). https://www.pgbouncer.org/2023/10/release-1.21.0
  7. HAProxy + Patroni recipe. https://patroni.readthedocs.io/en/latest/watchdog.html
  8. Zalando Postgres Operator (k8s). https://github.com/zalando/postgres-operator
  9. Crunchy Postgres Operator. https://access.crunchydata.com/documentation/postgres-operator/
  10. «PostgreSQL High Performance» — Gregory Smith. Книга по tuning и репликации.
  11. «Mastering PostgreSQL 15» — Hans-Jürgen Schönig. Главы по репликации и HA.
  12. Citus blog: «Why does pgbouncer break X». https://www.citusdata.com/blog/
  13. pgcasts.com — PostgreSQL Cast (видео по репликации).
  14. PGConf.EU talks (2024-2025) — Patroni 3.0, logical replication improvements.
  15. «The Internals of PostgreSQL» — Hironobu Suzuki, Chapter 11 (Streaming Replication). http://www.interdb.jp/pg/pgsql11.html

Streaming replication health:

-- На primary
SELECT pid, application_name, client_addr, state, sync_state, sent_lsn, write_lsn, flush_lsn, replay_lsn,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)) AS lag
FROM pg_stat_replication;
-- На standby
SELECT pg_is_in_recovery(), pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn(),
pg_last_xact_replay_timestamp();

Logical replication health:

SELECT subname, received_lsn, latest_end_lsn,
extract(epoch from now() - latest_end_time) AS seconds_lag
FROM pg_stat_subscription;

Replication slot lag:

SELECT slot_name, active, restart_lsn,
pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS bytes_lag
FROM pg_replication_slots ORDER BY bytes_lag DESC;

pgbouncer SHOW POOLS (через psql на 6432):

SHOW POOLS; -- cl_active, cl_waiting, sv_active, sv_idle ...
SHOW STATS; -- total_xact_count, total_query_count, avg_query_time
SHOW CLIENTS;
SHOW SERVERS;

Patroni operations:

Окно терминала
patronictl -c /etc/patroni.yml list
patronictl -c /etc/patroni.yml switchover --master pg01 --candidate pg02
patronictl -c /etc/patroni.yml failover --master pg01
patronictl -c /etc/patroni.yml reinit pg-cluster pg03 # reinit standby
patronictl -c /etc/patroni.yml pause pg-cluster # отключить failover на manual ops
patronictl -c /etc/patroni.yml resume pg-cluster

Чеклист пред prod-promote:

  1. ☐ Все standby в ISR / synchronous?
  2. ☐ Replication lag = 0?
  3. ☐ Бэкап перед операцией свежий?
  4. ☐ Application имеет circuit breaker / retry?
  5. ☐ HAProxy / pgbouncer обновят endpoint автоматически?
  6. ☐ Алерты включены?
  7. ☐ Rollback план готов?