Горячая замена процесса с переносом сессий

Перед SIGUSR2 или admin-командой UPGRADE можно заменить бинарь и конфиг на диске. Работающий процесс проверит эту комбинацию через -t, запустит дочерний процесс с теми же аргументами запуска, передаст ему слушающий сокет и переведёт на него новые подключения.

Старый процесс продолжает обслуживать уже подключённых клиентов и переносит свободные TCP-сессии без TLS в новый процесс через Unix-сокет. Клиент остаётся на том же TCP-соединении: вместе с файловым дескриптором передаются ключ отмены запроса, параметры PostgreSQL-сессии и кеш prepared statements.

Клиенты внутри транзакции продолжают работать на старом процессе и переезжают, когда транзакция завершится. Для приложений это убирает волну переподключений, а для PostgreSQL — всплеск auth/SCRAM.

TLS-сессии мигрируют только в Linux-сборке с фичей tls-migration. Дистрибутивные пакеты и Docker-образ собраны без неё, поэтому TLS-клиенты при такой замене дренируются и переподключаются к новому процессу.

Чем это отличается от PgBouncer / Odyssey

PgBouncer (-R, устарел с 1.20, или rolling restart через so_reuseport) и Odyssey (SIGUSR2 + bindwith_reuseport) переводят новые подключения на новый процесс, а старые сессии оставляют в старом до отключения клиентов. Сессии, prepared statements и TLS-состояние между процессами не переносятся.

pg_doorman передаёт живой клиентский сокет через SCM_RIGHTS, а в Linux-сборке с tls-migration может перенести и состояние OpenSSL.

Быстрый старт

Используйте дистрибутивный пакет, если он есть

Если pg_doorman поставлен через apt install pg-doorman или dnf install pg-doorman, заменяйте бинарник через пакетный менеджер. Обычный путь — apt-get install --only-upgrade pg-doorman или dnf upgrade pg-doorman. Ручной install ниже нужен только там, где бинарник раскладывают без пакетного менеджера.

# 1. Установите новый бинарник по пути, из которого запущен сервис,
#    и при необходимости обновите конфиг на диске.
install -m 0755 pg_doorman_new /usr/bin/pg_doorman

# 2. Проверьте, что новый процесс стартует с этим конфигом, до того
#    как запускать замену. SIGUSR2 тоже прогоняет `-t` и отменяет
#    замену при ошибке, но проверка здесь даёт шанс починить конфиг,
#    не трогая работающий сервер.
/usr/bin/pg_doorman -t /etc/pg_doorman/pg_doorman.toml

# 3. Запустите замену процесса. С `ExecReload=/bin/kill -SIGUSR2 $MAINPID`
#    в юните `systemctl reload` отправляет SIGUSR2 и запускает
#    горячую замену процесса. Дальше pg_doorman проверяет конфиг,
#    запускает дочерний процесс, мигрирует состояние где возможно и
#    дренирует старый процесс. systemd шлёт сигнал в MainPID, поэтому
#    выбирается ровно один процесс, даже если на хосте есть
#    другие инстансы pg_doorman. Прямой `kill -USR2 $(pgrep -f
#    /usr/bin/pg_doorman)` работает, но pgrep ищет по командной
#    строке и может попасть во все инстансы сразу — пакетные
#    установки используют systemctl.
sudo systemctl reload pg_doorman.service

# Успешный reload означает только, что systemd отправил SIGUSR2.
# Проверка конфига, запуск дочернего процесса, MAINPID handoff и
# миграция клиентов происходят внутри pg_doorman. Проверьте их следующим
# шагом и по логам.

# 4. Проверьте: systemd подхватил новый MainPID. При Type=notify
#    дочерний процесс отправляет `MAINPID=<new_pid>` во время передачи
#    управления. Состояние active и админ-консоль подтверждают, что
#    клиенты на месте.
systemctl show -p MainPID --value pg_doorman.service
psql -h pgdoorman -p 6432 -c 'SHOW POOLS;'  # отвечает новый процесс

Установки без systemd

Если процесс не управляется systemd, читайте PID-файл, который пишет режим демона (daemon_pid_file, по умолчанию /tmp/pg_doorman.pid), вместо парсинга pgrep: kill -USR2 "$(cat /var/run/pg_doorman/pg_doorman.pid)". Для запуска в foreground-режиме без systemd храните PID процесса-супервизора и шлите сигнал ему напрямую.

Ту же замену процесса можно запустить из консоли администратора:

UPGRADE;

UPGRADE шлёт SIGUSR2 запущенному процессу — тот же путь, что и kill -USR2. Успешный ответ команды означает, что сигнал отправлен, а не что проверка и миграция уже закончились.

Как работает замена

                        SIGUSR2
                           |
                           v
               +-----------------------+
               | 1. Проверка конфига   |
               |    (pg_doorman -t)    |   -- ошибка --> отмена
               +-----------+-----------+
                           |
                           v
               +-----------------------+
               | 2. Запуск нового      |
               |    socketpair()       |
               |    inherit-fd         |
               |    readiness pipe     |   — ожидание до 10 с
               +-----------+-----------+
                           |
             +-------------+-------------+
             |                           |
             v                           v
  +---------------------+    +---------------------+
  | СТАРЫЙ процесс      |    | НОВЫЙ процесс       |
  |                     |    |                     |
  | 3. Свободные        |    | migration_receiver  |
  |    сериализация     +--->+    восстановление   |
  |    dup() + SCM_RIGHTS    |    запуск клиента   |
  |                     |    |    handle()         |
  | 4. Клиенты в tx     |    |                     |
  |    дождаться COMMIT +--->+ Принимает новые     |
  |    мигрировать      |    | соединения          |
  |                     |    |                     |
  | 5. Таймер выхода    |    +---------------------+
  |    опрос 250 мс     |
  |    выход при 0      |
  +---------------------+

Фаза 1: Проверка конфига

Работающий процесс берёт путь, из которого стартовал сам, и запускает его с -t и текущим конфигом. После install и правки конфига из быстрого старта этот путь уже указывает на новый бинарник и новый файл конфига, поэтому проверяется именно процесс, который должен принять трафик. Если проверка проваливается, замена отменяется, а старый процесс продолжает обслуживать трафик. В логах появляется баннер:

!!!  BINARY UPGRADE ABORTED - SHUTDOWN CANCELLED  !!!
!!!  FIX THE CONFIGURATION BEFORE ATTEMPTING BINARY UPGRADE AGAIN  !!!
!!!  THE SERVER WILL CONTINUE RUNNING WITH THE CURRENT BINARY  !!!

Фаза 2: Запуск нового процесса

Foreground-режим:

  1. Создаётся Unix socketpair() для миграции клиентов.
  2. Файловый дескриптор слушающего сокета передаётся дочернему процессу через --inherit-fd.
  3. Канал готовности: родитель ждёт до 10 секунд байт от дочернего процесса. Дочерний процесс пишет в канал, когда начинает принимать соединения.
  4. Родитель закрывает свой слушающий сокет — новые соединения идут в дочерний процесс.

Режим демона:

Запускается новый фоновый процесс. Старый закрывает слушающий сокет. Миграция клиентов через socketpair() не используется: клиенты остаются на старом процессе. По истечении shutdown_timeout старый процесс выходит, а оставшиеся клиентские сокеты закрываются. Если клиенты должны мигрировать в новый процесс, используйте foreground-режим.

Фаза 3: Миграция свободных клиентов (foreground-режим)

Когда установлен флаг MIGRATION_IN_PROGRESS, каждый свободный клиент (нет активной транзакции, нет отложенного BEGIN, нет буферизованных данных на чтение) мигрирует:

  1. Сериализация: connection_id, secret_key, имя пула, username, параметры сервера, полный кеш prepared statements.
  2. dup() + SCM_RIGHTS: дескриптор клиентского TCP-сокета дублируется и передаётся новому процессу через Unix socketpair().
  3. Восстановление: новый процесс заново создаёт структуру клиента, подключает к нужному пулу и запускает handle().

Клиент не замечает миграции: нет повторного подключения, ошибки или повторной аутентификации. TCP-соединение остаётся тем же физическим сокетом.

Фаза 4: Доработка клиентов внутри транзакции

Клиент внутри BEGIN ... COMMIT продолжает работать на старом процессе. Его серверное соединение остаётся живым. После завершения транзакции (COMMIT или ROLLBACK) клиент становится свободным и мигрирует на следующей итерации цикла.

Отложенный BEGIN (сервер ещё не выделен) тоже блокирует миграцию. Клиент должен отправить запрос, тем самым материализовать BEGIN, затем выполнить COMMIT; после этого соединение можно перенести.

Фаза 5: Таймер завершения

Таймер завершения опрашивает CURRENT_CLIENT_COUNT каждые 250 мс. Когда все клиенты мигрировали или отключились — старый процесс вызывает process::exit(0).

Если shutdown_timeout истекает раньше, старый процесс выходит принудительно, а оставшиеся соединения закрываются.

Во время миграции drain_all_pools() откладывается: клиентам внутри транзакций нужны их серверные соединения. Дренирование пулов начинается только после завершения миграции или сброса MIGRATION_IN_PROGRESS.

Кеш prepared statements при миграции

Клиентский кеш prepared statements сериализуется при миграции:

  • Ключ записи: имя statement или хеш анонимного Parse
  • Хеш запроса
  • Полный текст запроса
  • OID типов параметров

В новом процессе:

  1. Каждая запись регистрируется в общем кеше пула (DashMap).
  2. На новых серверных соединениях ещё нет prepared statements.
  3. При первом Bind к мигрированной записи pg_doorman прозрачно отправляет Parse на новое серверное соединение. Клиент не видит дополнительного обмена.

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

  • Если client_anonymous_prepared_cache_size нового конфига меньше, лишние анонимные записи вытесняются по LRU. Именованная часть не ограничена и переносится полностью. Оставшиеся записи работают нормально.
  • Анонимные prepared statements (Parse с пустым именем) переносятся в новый процесс, но требуют повторного Parse перед Bind.
  • DEALLOCATE ALL после миграции очищает переданный кеш. Повторный Parse с тем же именем использует новый текст запроса.

Миграция TLS-сессий

По умолчанию TLS-клиенты не мигрируют — зашифрованная сессия требует ключевой материал, который живёт внутри автомата OpenSSL. Такие клиенты дренируются при замене процесса: соединение закрывается при истечении shutdown_timeout, клиент переподключается к новому процессу.

Опциональная сборка с tls-migration решает эту проблему. Патченный OpenSSL экспортирует состояние симметричного шифрования, передаёт его вместе с файловым дескриптором через Unix-сокет, а новый процесс импортирует состояние и продолжает шифрование. Клиент не выполняет повторное TLS-рукопожатие.

Что экспортируется

Патч добавляет SSL_export_migration_state() и SSL_import_migration_state() в OpenSSL 3.5.5. Экспортируемые данные:

  • Версия TLS-протокола
  • ID набора шифров и длина тега
  • Симметричные ключи для чтения/записи (входные данные для AES key schedule, не развёрнутые ключи)
  • IV (nonce) для чтения/записи
  • Номера записей для чтения/записи (по 8 байт)
  • Для TLS 1.3: секреты клиентского и серверного трафика приложения

Этого достаточно для восстановления слоя TLS-записей в новом процессе и продолжения шифрования/дешифрования на том же TCP-соединении.

Сборка с миграцией TLS

cargo build --release --features tls-migration

Требует perl и patch в окружении сборки. Vendored OpenSSL 3.5.5 собирается из исходников с наложенным патчем.

Сборка без доступа к интернету

# Скачайте архив заранее
curl -fLO https://github.com/openssl/openssl/releases/download/openssl-3.5.5/openssl-3.5.5.tar.gz

# Собрать с указанием пути
OPENSSL_SOURCE_TARBALL=./openssl-3.5.5.tar.gz \
  cargo build --release --features tls-migration

SHA-256 архива проверяется автоматически.

Ограничения

  • Только Linux. На macOS/Windows миграция TLS не поддерживается (native-tls использует Security.framework / SChannel, не OpenSSL).
  • Одинаковые сертификаты. Старый и новый процесс должны использовать одни и те же tls_private_key и tls_certificate. Состояние шифрования привязано к SSL_CTX, созданному из сертификата. Изменённые сертификаты приводят к ошибке импорта и отключению клиента.
  • FIPS несовместимо. Vendored OpenSSL не проходит FIPS-валидацию. Для FIPS используйте сборку без tls-migration (TLS-клиенты будут дренироваться вместо миграции).
  • Нет HSM/PKCS#11. Vendored OpenSSL собирается с no-engine.

Известные ограничения

  • TLS 1.3 KeyUpdate меняет ключи шифрования. Если любая сторона отправит KeyUpdate после экспорта состояния шифрования, импортированные ключи станут невалидными — соединение упадёт с ошибкой AEAD.

    Поведение драйверов (проверено апрель 2026):

    ДрайверАвтоматический KeyUpdate?Риск
    libpq (psql, pgbench)Нет — OpenSSL не отправляетНет
    asyncpg (Python)Нет — Python ssl = OpenSSLНет
    node-postgresНет — Node.js tls = OpenSSLНет
    Npgsql (.NET)Нет — SslStream без KeyUpdate APIНет
    pgjdbc (Java)Да — JSSE после ~128 GB (jdk.tls.keyLimits)Высокий
    tokio-postgres (rustls)Да — rustls при лимите AEADСредний
    PostgreSQL serverНет — renegotiation отключён, KeyUpdate не вызываетсяНет

    Java-клиенты: JSSE автоматически отправляет KeyUpdate после ~128 GB зашифрованных данных. Баг JDK-8329548 может вызвать шторм сообщений KeyUpdate. Для Java с долгоживущими высоконагруженными соединениями миграция TLS может потерять соединения. Обходной путь: увеличить порог через jdk.tls.keyLimits в java.security, или отключить TLS между Java-клиентом и pg_doorman.

    Rust-клиенты с rustls: rustls ротирует ключи при лимитах AEAD (очень высокий порог, ~2^36 записей для AES-GCM). Для типичных PostgreSQL-нагрузок этот порог практически недостижим. Использование native-tls (OpenSSL) вместо rustls устраняет риск.

    Все OpenSSL-драйверы безопасны. OpenSSL явно не выполняет автоматический KeyUpdate (openssl#23566).

  • Данные SSL_pending не проверяются. Миграция происходит в точке простоя, где нет буферизованных данных приложения. Этот инвариант гарантируется состоянием клиента, но явная проверка SSL_pending() не выполняется.

  • Привязка к OpenSSL 3.5.5. Патч модифицирует внутренние структуры OpenSSL (ssl_local.h, rec_layer_s3.c, ssl_lib.c). При обновлении OpenSSL нужно проверить и переложить патч на новую версию.

Сигналы

СигналПоведение
SIGUSR2Горячая замена процесса + дренирование старого процесса. Рекомендуемый для всех режимов.
SIGINTВ foreground + TTY (Ctrl+C): только завершение, без замены процесса. В режиме демона или без TTY: горячая замена процесса для совместимости со старыми установками.
SIGTERMНемедленный выход. Транзакции обрываются. Все клиенты отключаются.
SIGHUPПеречитать конфигурацию без перезапуска. Без простоя.
UPGRADE (admin)Отправляет SIGUSR2 текущему процессу. Тот же эффект.

Поведение SIGINT для совместимости: SIGINT запускает горячую замену процесса в режиме демона или без TTY (например, под systemd). В интерактивном терминале Ctrl+C останавливает процесс без запуска нового. Используйте kill -USR2 или UPGRADE в консоли администратора для горячей замены в foreground-режиме.

Режим демона и foreground-режим

ForegroundРежим демона
Миграция клиентов через передачу fdДа (socketpair)Нет
Свободные клиенты сохраняютсяДаНет (закрываются при выходе старого процесса)
Клиенты внутри транзакцииЗавершают транзакцию, затем мигрируютРаботают до таймаута, затем закрываются
Запуск нового процессаНаследует файловый дескриптор слушающего сокетаЗапускается независимо
Рекомендуется дляsystemd, контейнеры, KubernetesСтарые установки

Для горячей замены процесса с миграцией клиентов запускайте pg_doorman в foreground-режиме. systemd управляет жизненным циклом процесса. Используйте Type=notify: юнит становится active только после сигнала готовности от pg_doorman, а новый процесс при SIGUSR2 обновляет MainPID на себя:

[Service]
Type=notify
# Дочерний процесс, который принимает трафик после SIGUSR2, должен
# отправить READY=1 и MAINPID=<new_pid> во время передачи управления.
NotifyAccess=exec
ExecStart=/usr/bin/pg_doorman /etc/pg_doorman/pg_doorman.toml
# `systemctl reload` запускает горячую замену процесса: проверка конфига,
# новый процесс, миграция клиентов где возможно, затем дренирование
# старого процесса по shutdown_timeout из конфига pg_doorman.
ExecReload=/bin/kill -SIGUSR2 $MAINPID
# `systemctl stop` — немедленное завершение. Это не путь горячей замены,
# и он не ждёт миграции активных транзакций.
ExecStop=/bin/kill -SIGTERM $MAINPID
# При горячей замене новый дочерний процесс становится MainPID через sd_notify.
# KillMode=mixed при обычном stop шлёт SIGTERM только в MainPID, а
# оставшиеся процессы cgroup добивает SIGKILL только после TimeoutStopSec.
KillMode=mixed
TimeoutStopSec=60
# Не перезапускать сервис после чистой ручной остановки или после выхода
# старого процесса, который успешно передал трафик новому при замене.
Restart=on-failure
Nice=-15
# pg_doorman активно использует дескрипторы: каждый клиент и каждый
# бэкенд — отдельный fd, плюс служебные pipe. 65536 покрывает большинство
# OLTP-пулов; считается как `general.pool_size * num_pools` плюс
# несколько тысяч на клиентов.
LimitNOFILE=65536
# Сервисная учётка, которой принадлежит PID-файл. На многих установках
# postgres уже существует — переиспользование оставляет владение
# файлов согласованным с самим PostgreSQL.
User=postgres
Group=postgres
SyslogIdentifier=pg_doorman

systemctl reload pg_doorman отправляет SIGUSR2; нулевой код выхода означает только, что сигнал доставлен. Затем pg_doorman прогоняет -t на новом бинарнике, отменяет замену при битом конфиге, иначе порождает новый процесс и дренирует старый. UPGRADE; из админ-консоли идёт по тому же пути. Окно дренирования задаёт shutdown_timeout в pg_doorman.toml; TimeoutStopSec относится к обычному systemctl stop, а не к тому, сколько systemctl reload ждёт мигрировавшие сессии.

Продакшен-установки часто добавляют сверху ограничения ресурсов: MemoryMax=, CPUAffinity=2,3,4,5,6,7,8,9. Это зависит от нагрузки и не меняет контракт горячей замены.

Конфигурация

shutdown_timeout

Максимальное время ожидания завершения транзакций перед принудительным закрытием соединений. Старый процесс завершается по истечении этого таймаута вне зависимости от оставшихся клиентов.

По умолчанию: 10 секунд.

Рекомендация для продакшена с длинными аналитическими запросами: 30–60 секунд.

[general]
shutdown_timeout = 60000  # миллисекунды

Слишком маленькое значение — риск оборвать активные транзакции. Слишком большое — задержка выхода старого процесса при зависшем клиенте (например, idle-in-transaction). Выбирайте значение, покрывающее самую длинную ожидаемую транзакцию, с запасом.

tls_private_key / tls_certificate

Чтобы миграция TLS отработала, оба процесса (старый и новый) должны загрузить один и тот же сертификат и приватный ключ для входящих клиентских TLS-соединений. Состояние шифрования привязано к SSL_CTX, созданному из этих файлов; при несовпадении импорт падает, и затронутые клиенты отключаются и переподключаются.

Клиентский TLS-материал не перезагружается по SIGHUP (это делает только серверный TLS, см. горячую перезагрузку TLS). Не совмещайте ротацию сертификата для входящих клиентских TLS-соединений с горячей заменой, где вы ожидаете миграцию TLS-сессий. Если файлы отличаются между старым и новым процессом, импорт TLS-состояния падает, и затронутые клиенты переподключаются даже с включённым tls-migration. Ротируйте этот сертификат в окно, где переподключения допустимы, либо оставьте те же файлы на время горячей замены процесса и меняйте сертификат позже через рестарт.

prepared_statements_cache_size

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

client_anonymous_prepared_cache_size

LRU-кеш анонимных записей на клиента. Клиентский кеш (и именованная, и анонимная части) сериализуется полностью при миграции. Если новый конфиг имеет меньшее значение, LRU вытесняет лишние анонимные записи. Именованные записи не ограничены этим параметром и мигрируют целиком.

Откат

У горячей замены процесса нет отдельного пути отмены. Для отката положите предыдущий бинарник на тот же путь и запустите ещё одну замену через SIGUSR2. Если проверка конфига провалится, текущий процесс продолжит обслуживать трафик. Если новый процесс уже принял трафик, откат проходит как обычная горячая замена в обратную сторону.

Не используйте systemctl restart или SIGTERM для отката, если переподключения недопустимы: оба пути закрывают клиентские сессии, а не мигрируют их.

Мониторинг

Логи

Ключевые строки в логах при миграции:

INFO  Got SIGUSR2, starting binary upgrade and graceful shutdown
INFO  Validating configuration with: /usr/bin/pg_doorman -t pg_doorman.toml
INFO  Configuration validation successful
INFO  Starting new process with inherited listener fd=5
INFO  New process signaled readiness
INFO  Client migration enabled
INFO  [user@pool #c42] client 10.0.0.1:51234 migrated to new process
INFO  waiting for 3 clients in transactions
INFO  All clients disconnected, shutting down
INFO  Migration sender finished

В новом процессе:

INFO  migration receiver: listening for migrated clients
INFO  [user@pool #c42] migrated client accepted from 10.0.0.1:51234
INFO  migration receiver done: migration socket closed
INFO  migration receiver: stopped

Prometheus-метрики

МетрикаЗначение при замене процесса
pg_doorman_pools_clients{status="active"}Должна упасть до 0 на старом процессе
pg_doorman_pools_clients{status="idle"}Падает по мере миграции клиентов
pg_doorman_connections_total{type="total"}Новый процесс принимает свежие подключения; используйте rate() / increase()
pg_doorman_clients_prepared_cache_entriesПодтверждает перенос кэша

Консоль администратора

-- На новом процессе (старый отклоняет не-admin соединения)
SHOW POOLS;
SHOW CLIENTS;

Диагностика

Клиент получил 58006 или отключился вместо миграции

Ctrl+C в foreground-режиме. SIGINT в TTY означает завершение без горячей замены. Используйте kill -USR2 или UPGRADE в консоли администратора.

Режим демона. В режиме демона нет миграции через файловые дескрипторы. Существующие клиенты остаются на старом процессе и закрываются, когда он выходит. Переключитесь на foreground-режим.

PG_DOORMAN_CI_SHUTDOWN_ONLY=1 установлен. Эта переменная окружения принудительно включает режим только завершения (используется в CI-тестах). Уберите её.

Старый процесс не завершается

Длинная транзакция. Клиент застрял в BEGIN без COMMIT. Дождитесь shutdown_timeout или завершите транзакцию вручную.

Административные соединения. Они не мигрируют. Закройте административную сессию на старом процессе.

Принудительный выход: kill -TERM <old_pid> отправляет SIGTERM и завершает процесс без ожидания миграции.

TLS-соединение оборвалось после замены процесса

Бинарник собран без --features tls-migration. TLS-клиенты дренируются вместо миграции. Пересоберите с --features tls-migration.

Запуск не на Linux. Миграция TLS работает только на Linux.

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

"TLS migration not available" в логах

Новый процесс получил пакет миграции с TLS-данными, но собран без --features tls-migration или запущен не на Linux. Клиент отключается. Пересоберите новый бинарник с --features tls-migration.

"migration channel not ready" в логах

Канал MIGRATION_TX ещё не инициализирован. Новый процесс не завершил запуск, когда клиент попытался мигрировать. Клиент повторит попытку на следующей итерации простоя (через миллисекунды).

"migration channel send failed" в логах

Канал миграции переполнен (capacity: 4096). Возможно при одновременной миграции тысяч клиентов. Клиент повторит попытку на следующей итерации простоя.

"prepare_migration failed" в логах

Исходный fd клиента недоступен или dup() не удался. Возможные причины: исчерпание файловых дескрипторов, или клиент подключился через ветку кода, которая не сохраняет исходный fd. Проверьте ulimit -n.

Совместимость с клиентскими библиотеками: Библиотеки вроде github.com/lib/pq или Go database/sql могут потребовать настройки для обработки переподключения, если клиент не мигрировал и получил 58006 или закрытие соединения. См. issue.

Чек-лист перед продакшеном

Перед выкатом горячей замены процесса:

  • Запуск в foreground-режиме (не daemon) для миграции через файловые дескрипторы
  • shutdown_timeout покрывает самую длинную ожидаемую транзакцию (рекомендация: 30–60 секунд для OLTP, больше для аналитики)
  • Если используете TLS: сборка с --features tls-migration, оба процесса используют одинаковые файлы сертификата и ключа
  • Протестировать замену на staging: открыть сессию, отправить SIGUSR2, убедиться что сессия продолжает работать
  • В systemd-юните указано Type=notify с NotifyAccess=exec, ExecReload=/bin/kill -SIGUSR2 $MAINPID (тогда systemctl reload запускает горячую замену процесса с проверкой конфига), KillMode=mixed и Restart=on-failure
  • Мониторинг логов на ошибки миграции после первой замены в продакшене
  • Подтвердить что старый процесс завершился (PID-файл или pgrep)
  • Проверить Prometheus-метрики: клиенты на новом процессе

Prometheus во время дренирования

HTTP-слушатель, который отдаёт /metrics, использует SO_REUSEPORT. Пока старый процесс дренируется, а новый принимает клиентов, оба процесса делят один порт, и ядро распределяет scrape-запросы между ними. На отдельном scrape счётчики могут выглядеть так, будто откатились назад, пока старый процесс не завершится. Это окно длится не дольше shutdown_timeout.