Какой у вас опыт работы с Patroni?

Ответ

Настраивал и поддерживал высокодоступные кластеры PostgreSQL с Patroni в production-среде в течение 3 лет. Основной стек: Patroni + etcd + PgBouncer + monitoring.

Архитектура типичного кластера:

┌─────────────────────────────────────────────────┐
│                Load Balancer (HAProxy)          │
│           read-write       read-only            │
└───────────────┬──────────────────┬──────────────┘
                │                  │
        ┌───────▼──────┐   ┌───────▼──────┐
        │  Patroni 1   │   │  Patroni 2   │
        │  (Leader)    │   │  (Replica)   │
        │  PostgreSQL  │   │  PostgreSQL  │
        └───────┬──────┘   └───────┬──────┘
                │                  │
        ┌───────▼──────────────────▼──────┐
        │         etcd Cluster (3 nodes)  │
        │    для распределенного консенсуса│
        └──────────────────────────────────┘

Конфигурация Patroni (/etc/patroni/patroni.yml):

scope: postgres-prod
namespace: /service/
name: pg-node-1

restapi:
  listen: 0.0.0.0:8008
  connect_address: 10.0.1.10:8008
  authentication:
    username: "{{ patroni_api_user }}"
    password: "{{ patroni_api_password }}"

etcd:
  hosts:
    - etcd1:2379
    - etcd2:2379
    - etcd3:2379
  protocol: http
  retry_timeout: 10
  ttl: 30

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      use_pg_rewind: true
      use_slots: true
      parameters:
        max_connections: 100
        shared_buffers: 128MB
        wal_level: logical
        hot_standby: "on"
        archive_mode: "on"
        archive_command: 'cp %p /var/lib/postgresql/wal/%f'

postgresql:
  listen: 0.0.0.0:5432
  connect_address: 10.0.1.10:5432
  data_dir: /var/lib/postgresql/14/main
  bin_dir: /usr/lib/postgresql/14/bin
  pgpass: /var/lib/postgresql/.pgpass
  authentication:
    replication:
      username: replicator
      password: "{{ replication_password }}"
    superuser:
      username: postgres
      password: "{{ postgres_password }}"

watchdog:
  mode: required
  device: /dev/watchdog
  safety_margin: 5

Решаемые задачи:

  1. Автоматический failover — при падении лидера Patroni выбирает новую ноду за 30-60 секунд
  2. Репликация — настройка синхронной/асинхронной репликации с контролем лага
  3. Switchover — плановое переключение без простоя:
    patronictl switchover postgres-prod --master pg-node-1 --candidate pg-node-2
  4. Мониторинг — экспорт метрик в Prometheus (endpoint /metrics), алерты на:
    • Смену лидера
    • Лаг репликации > threshold
    • Нет кворума в etcd
  5. Интеграция с PgBouncer — автоматическое обновление pgbouncer.ini при смене лидера

Проблемы и решения:

  • Split-brain — предотвращаем через watchdog и правильные таймауты
  • Network partitions — настройка ttl и retry_timeout согласно сетевой задержке
  • Большие WAL файлы — использование pg_rewind вместо полного перебилда реплик

Ответ 18+ 🔞

Да ты представь, три года, Карл, три года я с этой штукой жил! Patroni, ёпта, как будто жена вторая, только без скандалов, но с вечными приколами. Ну, то есть, скандалы тоже были, но их я сам и провоцировал, когда что-то накосячил.

В общем, сидишь такой, пьешь кофе, а в мониторе — овердохуища алертов. И думаешь: «Э, сабака сука, опять что?». А это просто реплика отстала на пару мегабайт, потому что кто-то отчет за месяц выгружает. Но в целом, если настроить всё не как попало, а с мозгами, то штука живучая, как таракан после ядерной зимы.

Вот смотри, как обычно это безобразие выглядит. Картинка для наглядности, чтобы понимать, кто кому и куда:

┌─────────────────────────────────────────────────┐
│                Load Balancer (HAProxy)          │
│           read-write       read-only            │
└───────────────┬──────────────────┬──────────────┘
                │                  │
        ┌───────▼──────┐   ┌───────▼──────┐
        │  Patroni 1   │   │  Patroni 2   │
        │  (Leader)    │   │  (Replica)   │
        │  PostgreSQL  │   │  PostgreSQL  │
        └───────┬──────┘   └───────┬──────┘
                │                  │
        ┌───────▼──────────────────▼──────┐
        │         etcd Cluster (3 nodes)  │
        │    для распределенного консенсуса│
        └──────────────────────────────────┘

А теперь самое вкусное — конфиг. Это святая святых, его надо вылизать до блеска. Вот примерно как он выглядит, только пароли, ясное дело, спрятаны, а то мало ли.

scope: postgres-prod
namespace: /service/
name: pg-node-1

restapi:
  listen: 0.0.0.0:8008
  connect_address: 10.0.1.10:8008
  authentication:
    username: "{{ patroni_api_user }}"
    password: "{{ patroni_api_password }}"

etcd:
  hosts:
    - etcd1:2379
    - etcd2:2379
    - etcd3:2379
  protocol: http
  retry_timeout: 10
  ttl: 30

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      use_pg_rewind: true
      use_slots: true
      parameters:
        max_connections: 100
        shared_buffers: 128MB
        wal_level: logical
        hot_standby: "on"
        archive_mode: "on"
        archive_command: 'cp %p /var/lib/postgresql/wal/%f'

postgresql:
  listen: 0.0.0.0:5432
  connect_address: 10.0.1.10:5432
  data_dir: /var/lib/postgresql/14/main
  bin_dir: /usr/lib/postgresql/14/bin
  pgpass: /var/lib/postgresql/.pgpass
  authentication:
    replication:
      username: replicator
      password: "{{ replication_password }}"
    superuser:
      username: postgres
      password: "{{ postgres_password }}"

watchdog:
  mode: required
  device: /dev/watchdog
  safety_margin: 5

И вот за этим всем следишь, как за маленьким, но очень вредным ребёнком. Что мы этим безобразием решаем? Да много чего!

  1. Автоматический фейловер. Это когда лидер, грубо говоря, накрылся медным тазом. Раньше все бегали с криками «ААА! БД упала!», а теперь Patroni сам, за 30-60 секунд, выбирает нового предводителя. Главное — чтобы etcd был в сознании, а то будет пиздопроебибна полная.
  2. Репликация. Настраиваешь, как данные бегать должны. Можно синхронно — надёжно, но медленнее. Можно асинхронно — быстрее, но есть риск немного отстать. Лаг контролируешь, чтобы реплика не превратилась в музей устаревших данных.
  3. Плановый свичовер. Это для красоты. Хочешь на другой сервер переехать без простоев — пожалуйста, одна команда:
    patronictl switchover postgres-prod --master pg-node-1 --candidate pg-node-2

    И все приложения даже не чихнут, если, конечно, PgBouncer правильно настроен. А если нет — ну, сам от себя охуеешь, когда начнутся звонки.

  4. Мониторинг. Без него — никуда. Patroni метрики наружу отдаёт, ты их в Prometheus пихаешь и ставишь алерты на всё: смена лидера, лаг большой, etcd кворум потерял. Доверия ебать ноль ко всему живому, только метрики.
  5. PgBouncer. Чтобы приложения не тупили, когда лидер сменился, надо, чтобы пулер соединений это узнал. Приходится скрипты городить, которые конфиг PgBouncer'а автоматом обновят. Волнение ебать, пока это не заработает как часы.

А теперь про грабли, на которые я сам не раз наступал. Их, блядь, как собак нерезаных.

  • Split-brain (раздвоение личности). Это самый страшный сон. Когда две ноды решают, что они обе — лидеры, и начинают писать в базу кто во что горазд. Итог — пиздец полный и потеря данных. От этого спасает watchdog (железный или софтовый) и правильно подобранные таймауты. Если сеть лагает, а ttl маленький — жди беды.
  • Проблемы с сетью. Сеть — она такая хитрая жопа. Может заглючить, и ноды перестанут друг друга видеть. Надо retry_timeout и ttl выставлять с умом, с запасом, чтобы из-за мелкой икоты кластер не начал сам с собой войну.
  • Большие WAL файлы и сломанные реплики. Раньше, если реплика отстала сильно или сломалась, её надо было с нуля пересоздавать — долго и грустно. Теперь есть pg_rewind — магия, которая позволяет отмотать изменения и подцепиться обратно, как ни в чём не бывало. Просто надо в конфиге use_pg_rewind: true не забыть поставить.

В общем, история такая: настроил один раз и забыл? Хуй там! Это как живой организм, за ним глаз да глаз нужен. Но когда всё летает — красота, можно и кофе спокойно допить.