Hanami 2, PostgreSQL 17. Вчера днём выкатили безобидную миграцию: добавить в invoices колонку без default, просто NULL. Обычно это мгновенно. А тут миграция висела четыре минуты, и всё это время любые запросы к invoices, даже простые SELECT, стояли в очереди. API отдавал таймауты, пока кто-то не нашёл и не убил долгий отчёт аналитиков. Я правильно понимаю, что виноват отчёт? И как защититься, если отчёты не всегда гоняют по реплике?
Правильно понимаете. ALTER TABLE берёт ACCESS EXCLUSIVE и ждал, пока закончится долгая транзакция с отчётом. А все новые запросы, даже SELECT, встают в очередь уже за ним: блокировки выдаются по порядку. В итоге одна долгая выборка плюс одна быстрая миграция кладут таблицу. Защита: короткий lock_timeout для миграций, пусть лучше упадёт и повторится, чем повесит всех:
SET lock_timeout = '3s';
ALTER TABLE invoices ADD COLUMN paid_via text;
И перед выкаткой посмотреть, что сейчас долго открыто:
SELECT pid, now() - xact_start AS duration, state, left(query, 60)
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY duration DESC
LIMIT 5;
Про уровни блокировок подробно здесь: https://www.postgresql.org/docs/current/explicit-locking.html
Мы ещё ставим statement_timeout и idle_in_transaction_session_timeout на роль, под которой ходят отчёты. Десять минут на запрос, кому нужно дольше, идёт на реплику. Аналитики поворчали неделю и привыкли
В итоге так: в обёртке миграций теперь SET lock_timeout = '5s' и три попытки с паузой в 30 секунд. Для роли отчётов statement_timeout 15 минут. Сегодня катили ещё одну колонку: первая попытка упала по таймауту, вторая прошла. Никто ничего не заметил.