728x90
반응형
외주 프로젝트 중 백엔드 서버로부터 SQLAlchemy TimeoutError가 지속적으로 발견되는 것을 확인했다. 에러 코드를 읽어보면, QueuePool의 limit이 5(overflow 허용치는 10)인데, 한계값에 도달했으며, timeout 30초에 도달하여 연결이 끊겼다는 설명이다.
이 경우 원인은 크게 2가지일 수 있다.
1. 비동기 처리 코드 처리 중 DB 트랜잭션이 열린 채로 방지되어 커넥션이 해제되지 않는 경우
2. SQLAlchemy의 커넥션 풀 크기가 너무 작게 설정되어 있는 경우
위 2가지 원인에 대한 해결 방법을 찾아보니, 아래와 같은 결과가 나왔다.
1. 비동기 처리 코드 처리 중 DB 트랜잭션이 열린 채로 방지되어 커넥션이 해제되지 않는 경우
# 이전 코드
async def get_db():
db = database.AsyncSessionLocal()
# DB 세션 의존성 수정
async def get_db():
db = database.AsyncSessionLocal()
try:
yield db
finally:
await db.close()
수정 전의 코드는 db를 호출하기만 한 뒤, 아무 동작도 하지 않아 DB 트랜잭션이 열린 채로 유지되었다. 결국 API 호출이 쌓이다 보면 위와 같은 에러가 발생할 수밖에 없다.
코드에 try-finally를 추가해 DB 세션을 전달(try)한 뒤, 동작이 종료되면 db.close()가 되도록 설정했다.
2. SQLAlchemy의 커넥션 풀 크기가 너무 작게 설정되어 있는 경우
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "{DB_URL}"
# 비동기 엔진 생성
engine = create_async_engine(
DATABASE_URL,
echo=True,
pool_size=20,
max_overflow=30,
pool_timeout=60,
pool_recycle=1800,
)
# 비동기 세션 설정
AsyncSessionLocal = sessionmaker(
bind=engine,
class_=AsyncSession,
expire_on_commit=False,
)
Base = declarative_base()
위 코드는 DB 엔진을 연결하는 비동기 세션을 만드는 코드다. 위 비동기 엔진(engine)을 선언할 때, pool_size, max_overflow, pool_timeout, pool_recycle 등의 파라미터를 활용해 세부 항목을 설정할 수 있다. 각 파라미터의 의미는 아래와 같다.
- pool_size: 기본적으로 유지할 커넥션 개수(기본값: 5)
- max_overflow: 추가로 허용할 커넥션 개수(기본값: 10)
- pool_timeout: 커넥션을 기다릴 최대 시간(초 / 기본값: 30초)
- pool_recycle: 커넥션을 재사용 가능하도록 설정할 시간(초)
728x90