Как реализуются и работают соединение и сессия в FastAPI
Что проверяют
Понимаете ли вы разницу между пулом соединений, соединением и ORM-сессией и где в FastAPI проходит граница запроса. Следом почти всегда спрашивают про асинхронный вариант и про то, что будет, если завести одну сессию на всё приложение.
Как отвечать
В связке FastAPI и SQLAlchemy 2.x три уровня:
- engine: создаётся один раз на процесс и держит пул соединений. У
QueuePoolпо умолчаниюpool_size=5иmax_overflow=10, полезныpool_pre_ping=Trueиpool_recycle; - соединение: берётся из пула, когда сессии впервые понадобилась база, и возвращается туда при закрытии сессии, а не рвётся;
- сессия: unit of work, отслеживает изменённые объекты и транзакцию. Живёт один запрос.
Сессию выдают зависимостью с yield:
SessionFactory = async_sessionmaker(engine, expire_on_commit=False)
async def get_session():
async with SessionFactory() as session:
yield sessionEngine создаётся через create_async_engine с драйвером asyncpg, в роуте пишут session: Annotated[AsyncSession, Depends(get_session)]. Контекстный менеджер закроет сессию и при исключении в обработчике. Создавать и закрывать engine (await engine.dispose()) удобно в lifespan.
Про sync и async скажите отдельно: синхронные def-роуты FastAPI выполняет в пуле потоков, и обычная Session там работает. В async def синхронный драйвер заблокирует event loop, нужен AsyncSession. Запросы в стиле 2.0: await session.execute(select(User).where(...)), а не session.query.
Где спотыкаются
Заводят одну сессию на всё приложение. Она не потокобезопасна, копит объекты в identity map и после первой ошибки транзакции требует rollback.
Забывают expire_on_commit=False в async. После commit обращение к атрибуту объекта запускает ленивую загрузку и падает с MissingGreenlet.
Путают закрытие сессии с закрытием соединения. session.close() возвращает соединение в пул, и это правильно, а суммарный размер пулов надо сверять с max_connections базы, когда воркеров Uvicorn несколько.