Как работает пул коннектов в SQLAlchemy?
Что проверяют
Знаете ли вы, что происходит с соединением между Session и базой, и умеете ли настроить пул под нагрузку. Следом обычно спрашивают про ошибку QueuePool limit of size 5 overflow 10 reached или про соединения, которые рвутся после простоя.
Как отвечать
Пул живёт внутри Engine, один на процесс. По умолчанию для серверных баз это QueuePool:
pool_size=5: сколько соединений пул держит постоянно. Создаются они лениво, по запросу, а не при старте;max_overflow=10: сколько можно открыть сверх этого на пике. Лишние закрываются при возврате, итого потолок 15;pool_timeout=30: сколько секунд ждать свободного соединения доTimeoutError;pool_recycle: по умолчанию выключен (-1). Задают, если соединения рвёт сервер или балансировщик;pool_pre_ping=True: перед выдачей проверяет соединение и тихо заменяет мёртвое.
Цикл такой: Session берёт соединение (checkout) на первом запросе транзакции и отдаёт (checkin) на commit(), rollback() или close(). При возврате пул делает rollback, чтобы следующий не получил чужую транзакцию, а физическое соединение остаётся открытым.
Другие реализации: NullPool, когда пулом управляет PgBouncer; StaticPool для SQLite в памяти в тестах; у create_async_engine свой AsyncAdaptedQueuePool. Размер считают от сервера: число воркеров, умноженное на pool_size + max_overflow, не должно превышать max_connections в PostgreSQL.
Где спотыкаются
Не закрывают сессию. Соединение не возвращается, пул исчерпывается, и запросы висят 30 секунд до ошибки. Лечится with Session(engine) as session: или зависимостью с yield во FastAPI.
Создают Engine до fork в многопроцессном сервере, и дочерние процессы делят одни сокеты. Движок создают после форка или вызывают в дочернем процессе engine.dispose(close=False).
Поднимают pool_size до сотни «на всякий случай» и упираются в лимит соединений базы, вместо того чтобы искать долгие транзакции.