Горячая замена процесса с переносом сессий
Перед SIGUSR2 или admin-командой UPGRADE можно заменить бинарь и
конфиг на диске. Работающий процесс проверит эту комбинацию через -t,
запустит дочерний процесс с теми же аргументами запуска, передаст ему
слушающий сокет и переведёт на него новые подключения.
Старый процесс продолжает обслуживать уже подключённых клиентов и переносит свободные TCP-сессии без TLS в новый процесс через Unix-сокет. Клиент остаётся на том же TCP-соединении: вместе с файловым дескриптором передаются ключ отмены запроса, параметры PostgreSQL-сессии и кеш prepared statements.
Клиенты внутри транзакции продолжают работать на старом процессе и
переезжают, когда транзакция завершится. Для приложений это убирает
волну переподключений, а для PostgreSQL — всплеск auth/SCRAM.
TLS-сессии мигрируют только в Linux-сборке с фичей tls-migration.
Дистрибутивные пакеты и Docker-образ собраны без неё, поэтому TLS-клиенты
при такой замене дренируются и переподключаются к новому процессу.
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, читайте 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-режим:
- Создаётся Unix
socketpair()для миграции клиентов. - Файловый дескриптор слушающего сокета передаётся дочернему
процессу через
--inherit-fd. - Канал готовности: родитель ждёт до 10 секунд байт от дочернего процесса. Дочерний процесс пишет в канал, когда начинает принимать соединения.
- Родитель закрывает свой слушающий сокет — новые соединения идут в дочерний процесс.
Режим демона:
Запускается новый фоновый процесс. Старый закрывает слушающий сокет.
Миграция клиентов через socketpair() не используется: клиенты
остаются на старом процессе. По истечении shutdown_timeout старый
процесс выходит, а оставшиеся клиентские сокеты закрываются. Если
клиенты должны мигрировать в новый процесс, используйте foreground-режим.
Фаза 3: Миграция свободных клиентов (foreground-режим)
Когда установлен флаг MIGRATION_IN_PROGRESS, каждый свободный клиент
(нет активной транзакции, нет отложенного BEGIN, нет
буферизованных данных на чтение) мигрирует:
- Сериализация:
connection_id,secret_key, имя пула, username, параметры сервера, полный кеш prepared statements. dup()+SCM_RIGHTS: дескриптор клиентского TCP-сокета дублируется и передаётся новому процессу через Unixsocketpair().- Восстановление: новый процесс заново создаёт структуру клиента,
подключает к нужному пулу и запускает
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 типов параметров
В новом процессе:
- Каждая запись регистрируется в общем кеше пула (
DashMap). - На новых серверных соединениях ещё нет prepared statements.
- При первом
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или Godatabase/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-метрики: клиенты на новом процессе
HTTP-слушатель, который отдаёт /metrics, использует SO_REUSEPORT. Пока
старый процесс дренируется, а новый принимает клиентов, оба процесса
делят один порт, и ядро распределяет scrape-запросы между ними. На
отдельном scrape счётчики могут выглядеть так, будто откатились назад,
пока старый процесс не завершится. Это окно длится не дольше
shutdown_timeout.