Python FastAPI + SQLAlchemy非同期DB接続パターン

問題

FastAPIでSQLAlchemyを同期方式で使うと、DBクエリの間イベントループがブロックされます。同時リクエストが増えるとレスポンス速度が大幅に低下します。

解決方法

sqlalchemy[asyncio]asyncpgで非同期接続を設定します。

pip install sqlalchemy[asyncio] asyncpg
# database.py
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession

DATABASE_URL = "postgresql+asyncpg://user:pass@localhost:5432/mydb"

engine = create_async_engine(DATABASE_URL, pool_size=20, max_overflow=10)
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)

async def get_db():
    async with async_session() as session:
        yield session
# main.py
from fastapi import FastAPI, Depends
from sqlalchemy import select
from database import get_db

app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(user_id: int, db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(User).where(User.id == user_id))
    user = result.scalar_one_or_none()
    if not user:
        raise HTTPException(status_code=404)
    return user

ポイント

  • asyncpgはPostgreSQL専用です。MySQLならaiomysql、SQLiteならaiosqliteを使ってください。
  • expire_on_commit=Falseを設定しないと、コミット後にオブジェクトの属性にアクセスした際にlazy loadingが発生し、非同期ではエラーの原因になります。
  • pool_sizemax_overflowは同時接続数に合わせて調整してください。DBのコネクション数の上限を超えないように注意が必要です。
  • get_dbをジェネレーターにすると、FastAPIの依存性注入が自動的にセッションの作成とクリーンアップを行います。手動でtry/finallyを書く必要はありません。