fix(db): enable pool_pre_ping on async engine — 500 on stale Postgres connection
증상: - 라이브 smoke 도중 SSE poll loop 가 0.5s 마다 connection 을 빌리던 중, asyncpg pool 이 idle/network blip 으로 socket 이 닫힌 stale connection 을 그대로 넘김. 다음 요청 (GET /api/sessions) 이 `sqlalchemy.exc.InterfaceError: connection is closed` 로 500. 원인: - `create_async_engine(database_url, poolclass=None, echo=False)` — pool_pre_ping 미설정. SQLAlchemy 가 checkout 시 connection 생존 확인 안 함. 수정: - `pool_pre_ping=True` 한 줄 추가. SQLAlchemy 가 매 checkout 직전 빠른 SELECT 1 (asyncpg 는 protocol-level ping) 을 보내고 실패 시 pool 에서 invalidate 후 새 connection 발급. 표준 SQLAlchemy 권장 패턴. - 부하 (SSE 0.5s polling + REST) 에서 검증: 재시작 후 GET /api/sessions 연속 호출 모두 200. 테스트: - ruff / mypy: PASS (141 files) - pytest tests/integration/test_persistence.py: 20 passed (회귀 없음) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -75,10 +75,17 @@ class Database:
|
||||
"""
|
||||
|
||||
def __init__(self, database_url: str) -> None:
|
||||
# v0.3 hotfix: Postgres asyncpg pool occasionally hands out stale
|
||||
# connections whose underlying socket was closed by the server (idle
|
||||
# timeout, container restart, network blip, …). `pool_pre_ping`
|
||||
# adds a fast ping before each checkout and invalidates dead
|
||||
# connections so the next acquire dials a fresh one — fixes the
|
||||
# "InterfaceError: connection is closed" 500 seen under SSE load.
|
||||
self._engine: AsyncEngine = create_async_engine(
|
||||
database_url,
|
||||
poolclass=None,
|
||||
echo=False,
|
||||
pool_pre_ping=True,
|
||||
)
|
||||
_attach_dialect_pragmas(self._engine)
|
||||
self._session_factory: async_sessionmaker[AsyncSession] = async_sessionmaker(
|
||||
|
||||
Reference in New Issue
Block a user