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. Концепция
Заголовок раздела «1. Концепция»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 └──────────────┘2. Production-deep dive
Заголовок раздела «2. Production-deep dive»2.1. Streaming replication
Заголовок раздела «2.1. Streaming replication»Pipeline
Заголовок раздела «Pipeline» ┌───────────────── Primary ─────────────────┐ │ │ │ Backend → WAL buffer → WAL writer ──┐ │ │ ▼ │ │ walsender (per replica) │ │ └───────────────────────────────────────│────┘ │ TCP ┌───────────────── Standby ─────────────│────┐ │ ▼ │ │ walreceiver │ │ │ │ │ ▼ │ │ WAL on disk │ │ │ │ │ ▼ │ │ startup (recovery) │ redoим WAL → буфера └────────────────────────────────────────────┘Настройка primary
Заголовок раздела «Настройка primary»# postgresql.confwal_level = replica # или logical (включает оба режима)max_wal_senders = 10 # сколько standby одновременноmax_replication_slots = 10wal_keep_size = 1024 # MB (PG 13+, иначе wal_keep_segments)synchronous_commit = onsynchronous_standby_names = 'ANY 1 (s1, s2)'hot_standby = onpg_hba.conf:
host replication repl_user 10.0.0.0/8 scram-sha-256Создание реплики
Заголовок раздела «Создание реплики»# на standbypg_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 slotsystemctl start postgresqlReplication slot — must have
Заголовок раздела «Replication slot — must have»Без слота 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 lagFROM pg_replication_slots;synchronous_commit режимы
Заголовок раздела «synchronous_commit режимы»| Значение | Что значит | Latency | Durability |
|---|---|---|---|
off | Не ждём даже своего WAL fsync | min | risk lost |
local | Ждём fsync на primary, не ждём standby | fast | local OK |
remote_write | Ждём, что standby получил WAL (но мог ещё не fsync) | medium | OK при HA |
on (default) | = local, если sync_standby_names пуст. Иначе ждём fsync на standby | high | strong |
remote_apply | Ждём, что standby реально применил WAL и видит изменения | highest | strongest |
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.
hot_standby_feedback
Заголовок раздела «hot_standby_feedback»На standby могут идти долгие read-only запросы. Если primary за это время VACUUM’нул нужные строки — standby отменит запрос с ошибкой «canceling statement due to conflict with recovery».
# на standbyhot_standby_feedback = onStandby сообщает primary, какой XID он держит. Primary не вакуумит «живые для standby» строки.
⚠️ Risk: на primary копится bloat, если standby долго держит снапшот. Мониторь pg_stat_database.xact_commit/rollback vs возраст snapshot на standby.
2.2. Logical replication
Заголовок раздела «2.2. Logical replication»PG 10+. Декодирует WAL → INSERT/UPDATE/DELETE по логическим объектам.
-- на publisherCREATE 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:
- Zero-downtime major upgrade:
PG 14 primary ──logical──► PG 16 subscriber↓Cut over: переключаем приложениеStop publisher, promote subscriber
- Partial replication: аналитика отдельной БД хочет только нужные таблицы.
- CDC — Debezium стоит на logical replication.
- Multi-master шардинг — самописные / pglogical.
⚠️ Initial sync копирует всю таблицу — на больших таблицах долго и нагрузочно. Для огромных можно copy_data = false + руками pg_dump/pg_restore под одним снапшотом и CREATE SUBSCRIPTION ... WITH (copy_data = false).
2.3. Patroni
Заголовок раздела «2.3. Patroni»Patroni — open-source HA-template от Zalando.
Архитектура:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ patroni 1 │ │ patroni 2 │ │ patroni 3 │ │ +pg │ │ +pg │ │ +pg │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ └────────────┬────┴────────────┬────┘ ▼ ▼ ┌────────────────────────┐ │ DCS: etcd / consul │ │ / zookeeper / k8s │ └────────────────────────┘ │ ▼ leader election leaseFlow:
- Каждый Patroni-агент периодически (
ttl/loop_wait) пишет в DCS heartbeat. - Один из агентов держит leader-lock (TTL key в etcd). Он управляет primary Postgres.
- Если leader не продлевает lock в TTL — другой агент берёт его. Делает
pg_rewind/pg_basebackup(если нужно),pg_ctl promote. - Patroni также рулит конфигурацией: глобальный YAML в DCS = source of truth.
Конфиг (фрагмент patroni.yml):
scope: pg-clusternamespace: /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 8008Patroni в 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.
2.4. pgbouncer
Заголовок раздела «2.4. pgbouncer»Pool modes
Заголовок раздела «Pool modes»| Mode | Когда возвращает коннект в pool | Use case | Что ломается |
|---|---|---|---|
| session | После disconnect клиента | Долгие сессии, prepared statements | Почти ничего |
| transaction | После COMMIT / ROLLBACK | Web/API workload | Prepared statements*, advisory locks, LISTEN |
| statement | После каждого SQL-statement | Аналитика, read-only | Транзакции вообще |
* PG 14+ + pgbouncer 1.21+ поддерживает server-side prepared statements в transaction mode.
Transaction pooling gotchas
Заголовок раздела «Transaction pooling gotchas»В transaction mode коннект может оказаться у другого клиента между транзакциями:
SET work_mem = '100MB';— потеряется в следующем коннекте.LISTEN ...— не будет работать.pg_advisory_lock(session-level) — текущий клиент потеряет лок.- Серверные cursor’ы вне транзакции — недоступны.
WITH HOLDcursors — недоступны.- Temp tables — теряются после COMMIT (в transaction mode они общие на коннект).
Решение для SET: SET LOCAL внутри транзакции — работает.
Решение для prepared statements (PG 14+):
server_prepared_statements = truemax_prepared_statements = 256И в Go (pgx):
config.DefaultQueryExecMode = pgx.QueryExecModeCacheStatementКонфиг pgbouncer
Заголовок раздела «Конфиг pgbouncer»[databases]app = host=pg-master.local port=5432 dbname=app
[pgbouncer]listen_addr = 0.0.0.0listen_port = 6432auth_type = scram-sha-256auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transactionmax_client_conn = 10000default_pool_size = 50reserve_pool_size = 10reserve_pool_timeout = 5server_idle_timeout = 600server_lifetime = 3600query_wait_timeout = 120
stats_users = stats_useradmin_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; -- остановить/возобновить новые запросы2.5. PG14+ built-in pooling?
Заголовок раздела «2.5. PG14+ built-in pooling?»Часто слышишь «PG14 теперь сам умеет pooling». На самом деле — поддержка server-side prepared statements в pgbouncer transaction mode улучшилась, плюс есть proxy в EDB / Aurora. Полноценного встроенного pool в community PG до сих пор нет. pgbouncer и PgPool-II остаются стандартом.
PgPool-II vs pgbouncer
Заголовок раздела «PgPool-II vs pgbouncer»| Аспект | pgbouncer | PgPool-II |
|---|---|---|
| Pool | Да (transaction/session/statement) | Да |
| Load balancing | Нет (надо HAProxy) | Да, на основе SQL parse (read → replica) |
| Failover | Нет (внешний) | Да (свой watchdog) |
| Query rewriting | Нет | Да |
| Сложность | Низкая, легкий | Высокая, тяжёлый |
| Production winner | Чаще выбирают, проще | Кому нужны его доп. фичи |
2.6. Read-replica routing
Заголовок раздела «2.6. Read-replica routing»В 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 — реплика может ещё не догнать. Решения:
- «Read your writes» на primary, остальное — на replica.
- Использовать
pg_current_wal_lsn()после INSERT и ждать на репликеpg_last_wal_replay_lsn() >= lsn. synchronous_commit = remote_apply+ ANY 1 — медленнее, но гарантия видимости.
2.7. Failover scenarios
Заголовок раздела «2.7. Failover scenarios»Plan А: graceful switchover
Заголовок раздела «Plan А: graceful switchover»# planned maintenancepatronictl -c /etc/patroni.yml switchover --master pg01 --candidate pg02Patroni:
- Активирует quorum sync на кандидате.
- Ждёт catch-up.
- PROMOTE pg02, demote pg01 (rewind при необходимости).
- Обновляет DCS lease.
- HAProxy подхватывает (
/masterтеперь на pg02).
Plan B: автоматический failover
Заголовок раздела «Plan B: автоматический failover»Primary падает (kernel panic).
- Patroni-агент на pg01 не продлевает lease.
- Через
ttl(30s) lease истекает. - Patroni на pg02 (sync standby с наименьшим лагом) забирает leader-lock.
- PROMOTE.
- pg03 reconfigured на новый primary (через
recovery.conf/primary_conninfo).
Downtime ≈ ttl + promote + HAProxy retry. Обычно 15-60 секунд.
Plan C: split-brain prevention
Заголовок раздела «Plan C: split-brain prevention»Если pg01 «жив», но изолирован от etcd (network partition):
- Через
failsafe_mode(Patroni 3.0+) или внешний fence (например, STONITH в k8s) primary форсированно демотируется. - Без fencing — split-brain: два primary параллельно пишут. Самый страшный сценарий.
3. Gotchas (12+)
Заголовок раздела «3. Gotchas (12+)»⚠️ 1. Replication slot без consumer → диск primary
Заголовок раздела «⚠️ 1. Replication slot без consumer → диск 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).
⚠️ 3. pg_basebackup без -X stream
Заголовок раздела «⚠️ 3. pg_basebackup без -X stream»Без -X stream WAL стримится последовательно; на больших БД standby может отстать. Всегда используй -X stream или -X fetch с retention.
⚠️ 4. PG version mismatch
Заголовок раздела «⚠️ 4. PG version mismatch»Physical replication требует точно той же мажорной версии, минорная может отличаться. Logical replication — между мажорными работает.
⚠️ 5. Logical replication: REPLICA IDENTITY
Заголовок раздела «⚠️ 5. Logical replication: REPLICA IDENTITY»Без PK или REPLICA IDENTITY: UPDATE/DELETE на subscriber молча игнорируются.
ALTER TABLE my_tbl REPLICA IDENTITY FULL; -- worst case, по всей строке⚠️ 6. Logical replication: initial sync блокирует publisher
Заголовок раздела «⚠️ 6. Logical replication: initial sync блокирует publisher»COPY под snapshot держит долгий снапшот на primary → bloat. Большие таблицы — лучше pre-load через pg_dump.
⚠️ 7. pgbouncer transaction mode + prepared statements
Заголовок раздела «⚠️ 7. pgbouncer transaction mode + prepared statements»До PG 14 и/или pgbouncer < 1.21 — prepared statements в transaction mode ломаются. Go-драйвер pgx по умолчанию использует prepared. Решение: QueryExecModeSimpleProtocol или обновиться.
⚠️ 8. pgbouncer max_connections vs PG max_connections
Заголовок раздела «⚠️ 8. pgbouncer max_connections vs PG max_connections»pgbouncer max_client_conn = 10000pgbouncer default_pool_size = 50, databases = 4, users = 2 → ~400 server connpostgres max_connections должно быть ≥ 400 + резерв (auto_vacuum, repl, superuser)⚠️ 9. hot_standby_feedback и bloat
Заголовок раздела «⚠️ 9. hot_standby_feedback и bloat»Долгие read’ы на standby блокируют VACUUM на primary. Если есть аналитические запросы по часу — primary распухает.
⚠️ 10. Patroni: maximum_lag_on_failover
Заголовок раздела «⚠️ 10. Patroni: maximum_lag_on_failover»Если все standby отстают больше порога — Patroni НЕ сделает failover (защита от потерь). Кластер останется без primary. Мониторь и держи лаг низким.
⚠️ 11. Promote с устаревшим WAL
Заголовок раздела «⚠️ 11. Promote с устаревшим WAL»При async-репликации promote standby = потеря последних async-транзакций. Только sync mode (или semi-sync) даёт zero data loss.
⚠️ 12. pg_rewind requirements
Заголовок раздела «⚠️ 12. pg_rewind requirements»Чтобы старый primary вернулся в кластер как standby после promote, нужен pg_rewind. Требует wal_log_hints = on или data checksums. Без этого — pg_basebackup (полная пересинхронизация).
⚠️ 13. pgbouncer и application_name
Заголовок раздела «⚠️ 13. pgbouncer и application_name»При transaction mode application_name меняется при каждом коннекте. Если используешь его для роутинга/логирования — путаница. Решение: SET LOCAL application_name = '...' в начале каждой транзакции.
⚠️ 14. HAProxy keepalive
Заголовок раздела «⚠️ 14. HAProxy keepalive»HAProxy с TCP-режимом не закрывает коннект при смерти бэкенда мгновенно. option clitcpka + option srvtcpka + timeout server 30s помогут.
⚠️ 15. pgbouncer + RDS / Aurora
Заголовок раздела «⚠️ 15. pgbouncer + RDS / Aurora»RDS Proxy ≠ pgbouncer (не поддерживает все фичи). Aurora имеет встроенный pooling, но в transaction mode тоже свои ограничения. Уточнять под конкретный stack.
4. Real cases
Заголовок раздела «4. Real cases»Case 1: Zero-downtime major upgrade PG 13 → PG 16
Заголовок раздела «Case 1: Zero-downtime major upgrade PG 13 → PG 16»Контекст: БД 2TB, downtime < 1 минуты по SLA.
Шаги:
- Поднят PG 16 instance параллельно.
- На PG 13 включён
wal_level = logical. CREATE PUBLICATION app_pub FOR ALL TABLES;- На PG 16: schema dump+restore,
CREATE SUBSCRIPTION app_sub …. - Initial sync ~ 8 часов в фоне.
- Ждали catch-up replication lag → 0.
- App в maintenance mode (30 секунд), последняя транзакция допроцессилась.
- Promote PG 16 (просто переключили DNS / pgbouncer target).
Risk: sequences не реплицируются. Решение — ручной SELECT setval(...) после catch-up.
Case 2: pgbouncer transaction mode сломал prepared statements
Заголовок раздела «Case 2: pgbouncer transaction mode сломал prepared statements»Симптом: 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 = trueCase 3: Replica lag из-за autovacuum
Заголовок раздела «Case 3: Replica lag из-за autovacuum»Симптом: реплика отстаёт на десятки минут.
Причина: на 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 мог дотягивать после паузы.
Case 4: Patroni split-brain в k8s
Заголовок раздела «Case 4: Patroni split-brain в k8s»Контекст: 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).
Case 5: Logical replication для multi-region
Заголовок раздела «Case 5: Logical replication для multi-region»Главный кластер в Москве, аналитика в Питере. Latency между ДЦ ~10ms. Synchronous replication — слишком дорого. Сделали:
- async streaming replica в том же ДЦ (HA).
- logical subscription в Питере на read-only БД (с урезанной схемой — без security-чувствительных таблиц).
- Lag мониторится, app в Питере знает, что данные «свежие на 30 секунд».
5. Вопросы (25)
Заголовок раздела «5. Вопросы (25)»- Чем отличается physical (streaming) replication от logical?
- Что такое WAL и какова его роль в репликации?
- Что такое replication slot и какие риски с ним связаны?
- Какие есть режимы
synchronous_commitи какие у них trade-offs? - Что значит
synchronous_standby_names = 'ANY 2 (s1, s2, s3)'? - Что такое hot standby и какие у него ограничения?
- Зачем нужен
hot_standby_feedbackи каковы его побочные эффекты? - Как работает
pg_basebackup? Когда-X streamобязателен? - Как сделать zero-downtime major upgrade Postgres?
- Какие требования у logical replication к таблицам?
- Что такое REPLICA IDENTITY и какие у неё варианты?
- Что такое Patroni и какие у него зависимости?
- Как Patroni выбирает нового primary при failover?
- Что такое DCS и какие реализации (etcd, consul, k8s) есть?
- Что такое split-brain и как Patroni его предотвращает?
- Что такое
pg_rewindи когда без него не обойтись? - Какие есть pool mode в pgbouncer и в чём разница?
- Какие фичи Postgres не работают в transaction pooling?
- Как заставить prepared statements работать в transaction mode?
- Как соотносятся
max_client_connpgbouncer иmax_connectionsPostgres? - Что такое
reserve_pool_size? - Что нужно в HAProxy для роутинга на primary через Patroni REST?
- Какие риски при чтении с read-replica?
- Что такое semi-sync vs sync vs async и что выбрать для финтеха?
- Какие минимальные метрики мониторить для pgbouncer и репликации?
6. Practice
Заголовок раздела «6. Practice»6.1. Локальный кластер (docker-compose)
Заголовок раздела «6.1. Локальный кластер (docker-compose)»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"]6.2. Promote standby и обратное подключение
Заголовок раздела «6.2. Promote standby и обратное подключение»# на 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.conftouch /var/lib/postgresql/data/standby.signalpg_ctl start6.3. Patroni quickstart
Заголовок раздела «6.3. Patroni quickstart»Подними etcd, два узла Patroni, сделай switchover, посмотри patronictl list.
6.4. pgbouncer + Go
Заголовок раздела «6.4. pgbouncer + Go»cfg, _ := pgxpool.ParseConfig("postgres://app:pass@pgbouncer:6432/app?sslmode=disable")cfg.MaxConns = 30cfg.MinConns = 5cfg.MaxConnLifetime = 5 * time.Minute// Для transaction mode без PG14 prepared statements:cfg.ConnConfig.DefaultQueryExecMode = pgx.QueryExecModeExecpool, _ := pgxpool.NewWithConfig(ctx, cfg)6.5. Logical replication
Заголовок раздела «6.5. Logical replication»Подними два PG instance, сделай publication/subscription, проверь, что:
- INSERT доезжает.
- DDL не доезжает.
- DELETE без PK не работает (поставь REPLICA IDENTITY FULL).
6.6. Тест “read your writes”
Заголовок раздела «6.6. Тест “read your writes”»-- на primaryINSERT INTO t (id) VALUES (1) RETURNING pg_current_wal_lsn();-- получишь, например, 1/2D000028-- на standby:SELECT pg_last_wal_replay_lsn() >= '1/2D000028';-- false → подожди → true → читай.7. Источники
Заголовок раздела «7. Источники»- PostgreSQL Documentation — High Availability, Load Balancing, and Replication. https://www.postgresql.org/docs/current/high-availability.html
- PostgreSQL Documentation — Logical Replication. https://www.postgresql.org/docs/current/logical-replication.html
- Patroni Documentation. https://patroni.readthedocs.io/
- Patroni GitHub (Zalando). https://github.com/zalando/patroni
- pgbouncer documentation. https://www.pgbouncer.org/config.html
- pgbouncer prepared statements (1.21+). https://www.pgbouncer.org/2023/10/release-1.21.0
- HAProxy + Patroni recipe. https://patroni.readthedocs.io/en/latest/watchdog.html
- Zalando Postgres Operator (k8s). https://github.com/zalando/postgres-operator
- Crunchy Postgres Operator. https://access.crunchydata.com/documentation/postgres-operator/
- «PostgreSQL High Performance» — Gregory Smith. Книга по tuning и репликации.
- «Mastering PostgreSQL 15» — Hans-Jürgen Schönig. Главы по репликации и HA.
- Citus blog: «Why does pgbouncer break X». https://www.citusdata.com/blog/
- pgcasts.com — PostgreSQL Cast (видео по репликации).
- PGConf.EU talks (2024-2025) — Patroni 3.0, logical replication improvements.
- «The Internals of PostgreSQL» — Hironobu Suzuki, Chapter 11 (Streaming Replication). http://www.interdb.jp/pg/pgsql11.html
Приложение. Cheatsheet команд
Заголовок раздела «Приложение. Cheatsheet команд»Streaming replication health:
-- На primarySELECT 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 lagFROM pg_stat_replication;
-- На standbySELECT 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_lagFROM pg_stat_subscription;Replication slot lag:
SELECT slot_name, active, restart_lsn, pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS bytes_lagFROM 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_timeSHOW CLIENTS;SHOW SERVERS;Patroni operations:
patronictl -c /etc/patroni.yml listpatronictl -c /etc/patroni.yml switchover --master pg01 --candidate pg02patronictl -c /etc/patroni.yml failover --master pg01patronictl -c /etc/patroni.yml reinit pg-cluster pg03 # reinit standbypatronictl -c /etc/patroni.yml pause pg-cluster # отключить failover на manual opspatronictl -c /etc/patroni.yml resume pg-clusterЧеклист пред prod-promote:
- ☐ Все standby в ISR / synchronous?
- ☐ Replication lag = 0?
- ☐ Бэкап перед операцией свежий?
- ☐ Application имеет circuit breaker / retry?
- ☐ HAProxy / pgbouncer обновят endpoint автоматически?
- ☐ Алерты включены?
- ☐ Rollback план готов?