Архитектура бэкенда
Пулинг: диагностируй и укроти исчерпанный pool
Читать про starvation и leaks — не то же самое, что вытащить сервис из них. Построй небольшой сервис против реальной базы, загони его в pool starvation, а затем в leak, и применяй рычаги юнита — size, wait, lifecycle, return — пока четыре датчика pool не вернутся, с доказательством на каждом шаге.
Преврати ментальную модель юнита в воспроизводимый инженерный цикл: заинструментируй четыре датчика pool, воспроизведи starvation и leak, размерь pool по железу, ограничь acquisition-ожидание, гарантируй return и подтверди каждый фикс числами до/после под идентичной нагрузкой.
Возьми небольшой HTTP-сервис на реальном pool Postgres или MySQL, намеренно загони его в pool starvation и в connection-leak exhaustion, затем приведи к здоровому установившемуся состоянию — правильно размеренный pool, fail-fast acquisition, без leaks, свежие соединения — доказывая каждый шаг датчиками active/idle/total/waiting.
- Таблица до/после четырёх датчиков (active, idle, total, waiting), acquisition wait p99 и request p99 — измеренные под одинаковой нагрузкой, а не оценённые, для сценариев starvation и leak.
- Leak доказуемо устранён: с фиксом try/finally active возвращается к базовой линии после остановки трафика по error-пути, и leak detection не выдаёт предупреждений под устойчивой нагрузкой.
- Pool размерен под (cores x 2) + spindles (или под измеренную точку насыщения), и короткий нагрузочный тест показывает, что он достигает пика пропускной способности при меньшей задержке, чем вдвое больший pool на том же железе.
- Абзац-разбор: какой рычаг починил каждый сценарий (size, acquisition timeout, try/finally, maxLifetime) и почему увеличение pool было неверным инстинктом для leak.
- Добавь одностраничный on-call runbook: triage по четырём датчикам (leak vs true overload vs hoarding), формула размера, правило acquisition timeout и чек-лист верификации.
- Сымитируй fan-in: запусти несколько инстансов сервиса против одной базы, чтобы (instances x pool size) превысило max_connections, воспроизведи 'FATAL: sorry, too many clients already', затем поставь PgBouncer в режим transaction pooling впереди и покажи, как затребованные соединения схлопываются до нескольких десятков backend'ов.
- Продемонстрируй компромисс transaction pooling: докажи, что путь запроса, опирающийся на server-side prepared statement или SET session-переменную, ломается под PgBouncer transaction mode, затем перепиши его свободным от session-состояния.
- Добавь ловушку async-границы и её фикс: придержи соединение через несвязанный внешний HTTP-вызов, покажи истощение pool под нагрузкой без всякого leak, затем перенеси acquire после вызова и покажи восстановление конкурентности.
Это цикл, который ты будешь запускать в каждом реальном инциденте с пулингом: сначала заинструментируй четыре датчика, воспроизведи отказ (starvation vs leak vs hoarding), затем чини на правильном рычаге — размерь по ядрам, ограничь acquisition-ожидание, чтобы истощённый pool падал быстро, гарантируй return через try/finally и ротируй соединения под собственным таймаутом БД — и подтверди числами до/после под идентичной нагрузкой. Сделав это раз на игрушечном сервисе, включая случай fan-in, вынуждающий PgBouncer, ты доводишь production-версию до мышечной памяти: соединение — жёсткий, общий, дорогой ресурс, и ты ограничиваешь его осознанно, а не раздуваешь буфер, лишь оттягивающий отказ.