Базы данных
Connection pooling: укроти исчерпанный пул
Читать про инцидент с pool exhaustion в 3 ночи — не то же самое, что вытаскивать сервис из него. Подними реальный стек Postgres + PgBouncer, размечай его верно, намеренно сломай внешним вызовом внутри транзакции, диагностируй дренаж по SHOW POOLS и pg_stat_activity и применяй лестницу фиксов юнита, пока числа не вернутся — с доказательствами на каждом шаге.
Преврати ментальную модель юнита в воспроизводимый инженерный цикл: размечай оба слоя пула из первых принципов, безопасно мигрируй в transaction mode, воспроизведи exhaustion из idle-in-transaction, диагностируй его по сигналам пулера и базы, исправь hold и защити границу safety-GUC — с проверкой числами до/после под идентичной нагрузкой.
Поставить PgBouncer в transaction mode перед небольшим Postgres-сервисом, загнать его в pool exhaustion через паттерн idle-in-transaction и вернуть под SLO — доказывая каждый шаг измерениями, а не оценками.
- Таблица до/после, измеренная под одной нагрузкой: cl_waiting (пик), число backend в idle-in-transaction, p99 задержки запроса и rate ошибок/503 — измеренные, не оценённые.
- Доказательство, что пул теперь держит: на устойчивой нагрузке cl_waiting остаётся 0 и ни один backend не задерживается в idle-in-transaction дольше пары сотен мс, когда внешний вызов вне транзакции.
- Демонстрация, что поднятие одного default_pool_size не разрешило исходный дренаж (снятый прогон с сохраняющейся очередью), обосновывающая, почему hold-time-фикс ранжируется выше ресайзинга.
- Абзац о математике сайзинга, что ты использовал (оба слоя, с входными числами), и почему pool_size привязан к числу ядер Postgres, а не к числу клиентов.
- Добавь одностраничный on-call runbook: триаж по SHOW POOLS и pg_stat_activity, четыре корневые причины exhaustion (медленный запрос, idle-in-transaction, всплеск трафика, внешний API в транзакции) и чек-лист проверки для каждого фикса.
- Включи protocol-level prepared statements драйвера с PgBouncer 1.21+ и max_prepared_statements = 1000; нагружай 30 минут и grep по логам PgBouncer, подтверждая ноль ошибок 'prepared statement does not exist' при активном мультиплексировании transaction mode.
- Симулируй NAT/firewall, дропающий idle TCP (убей сокет backend вне канала), и покажи, как tcp_keepalives_idle плюс client_connection_check_interval детектируют мёртвое соединение, а не падают на следующем запросе с RST.
- Воспроизведи serverless connection storm: породи 200 короткоживущих клиентов одновременно против PgBouncer против прямого Postgres и покажи, что пулер ограничивает число backend Postgres, тогда как direct-connect ловит 'too many clients already'.
Это цикл, который ты будешь гонять в каждом реальном инциденте пулинга: размечай оба слоя по формулам (client pool из конкурентности, backend pool из (cores × 2) + spindles), мигрируй в transaction mode за аудитом, а когда пул пустеет — читай сигналы до того, как трогать конфиг: cl_waiting на пулере, state и возраст транзакции в Postgres — чтобы доказать, что это hold-time, а не размер. Исправь hold (вынеси внешние вызовы из транзакций, коммить до медленной работы), затем установи idle_in_transaction_session_timeout и query_wait_timeout как постоянную страховку. Сделав это раз на игрушечном стеке, ты превращаешь production-версию в мышечную память.