Как контролировать количество соединений с базой данных при высокой нагрузке в FastAPI
Что проверяют
Понимаете ли вы, что предел соединений задаёт база, а не приложение, и что считать его нужно на все воркеры и поды сразу. Следом спрашивают, что происходит, когда пул исчерпан, и как не держать соединение во время медленного внешнего вызова.
Как отвечать
Начните с арифметики: в PostgreSQL по умолчанию max_connections = 100, а у каждого процесса uvicorn или gunicorn свой пул. Четыре пода по четыре воркера с pool_size=10 и max_overflow=10 дают до 320 соединений, база столько не выдержит.
- пул SQLAlchemy: в
create_async_engineс драйверомasyncpgзадайтеpool_sizeиmax_overflow(по умолчанию 5 и 10),pool_timeout(ожидание свободного соединения, по умолчанию 30 секунд),pool_pre_ping=Trueпротив мёртвых соединений иpool_recycle; - сессия на запрос: зависимость с
yieldиasync withвокругasync_sessionmaker(), чтобы соединение вернулось в пул даже при исключении. Сессия берёт соединение при первом запросе к базе и отдаёт после коммита или закрытия; - не блокировать цикл: синхронный драйвер внутри
async defостанавливает весь event loop. Нужен либо асинхронный драйвер, либо обычныйdef, который FastAPI выполнит в пуле потоков; - внешний пулер: PgBouncer в режиме
transactionдержит на стороне базы десятки соединений при тысячах клиентских. Сasyncpgза ним отключают кеш подготовленных выражений (statement_cache_size=0вconnect_args) либо включают поддержку prepared statements в самом PgBouncer; - ограничение сверху:
asyncio.Semaphoreна тяжёлые запросы и быстрый отказ с кодом 503 лучше, чем очередь на полминуты; - мониторинг:
pg_stat_activityпо состояниям, включаяidle in transaction, метрики занятости пула и число таймаутов ожидания.
Где спотыкаются
Считают пул на одно приложение и забывают умножить на воркеры, реплики и фоновые задачи вроде Celery, которые ходят в ту же базу.
Держат сессию открытой на время HTTP-запроса к внешнему сервису или отдачи стрима. Пул пустеет уже при умеренном трафике.
Ставят max_overflow=-1 или огромный pool_size, думая, что это лечит таймауты. Проблема просто переезжает в базу, которая начинает отказывать всем.