SQLAlchemy 1.4가 async 지원 한다고 해서 프리뷰(b1 정도) 버전 물려서 FastAPI 서비스 한 개를 async db로 전환. 아직 정식은 아님, 2020년 하반기 정도에 1.4 GA 예정이라는 분위기.
현재 스택: FastAPI 0.52, SQLAlchemy 1.4.0b1, asyncpg 0.20, PostgreSQL 12.
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
engine = create_async_engine(
"postgresql+asyncpg://user:pw@localhost/db",
pool_size=10,
max_overflow=5,
)
AsyncSessionLocal = sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False
)
async def get_db():
async with AsyncSessionLocal() as session:
yield session
쿼리 모양. 기존 session.query() 대신 select() 쓰는 2.0 스타일이 강요됨.
from sqlalchemy import select
@app.get("/users/{user_id}")
async def get_user(user_id: int, db: AsyncSession = Depends(get_db)):
stmt = select(User).where(User.id == user_id)
result = await db.execute(stmt)
user = result.scalar_one_or_none()
return user
걸린 점 몇 가지 적어둠.
- expire_on_commit=False 꼭 필요. 안 하면 commit 후 속성 접근할 때 lazy load 들어가서 "another operation in progress" 예외. 처음에 한참 헤맸음.
- relationship의 lazy loading은 여전히 sync 전제. selectinload, joinedload로 eager load하거나 AsyncSession.refresh 명시 호출 필요.
- Alembic은 아직 sync 전용이라 migration은 sync 커넥션으로 돌림. 지금은 이게 맞다.
벤치는 별 의미 없는데, 동시성 200 기준 p95 latency가 sync 대비 40% 정도 좋음. 단 psycopg2 대비 asyncpg는 드라이버 자체가 빨라서 그 영향도 큼.
beta라 돌다가 내부 에러 뜨는 경우 있고, 스택트레이스가 greenlet/asyncio 섞여서 난해함. prod 넣을 때는 1.4 GA 까지 기다리는 게 현명할 듯.
댓글 없음:
댓글 쓰기