From 40ef833ad3b4a9be4b43d3b79899c0036a3850fb Mon Sep 17 00:00:00 2001 From: chungyeong Date: Mon, 18 May 2026 00:24:24 +0900 Subject: [PATCH] =?UTF-8?q?fix(db):=20enable=20pool=5Fpre=5Fping=20on=20as?= =?UTF-8?q?ync=20engine=20=E2=80=94=20500=20on=20stale=20Postgres=20connec?= =?UTF-8?q?tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 증상: - 라이브 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) --- my-deepagent/src/my_deepagent/persistence/db.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/my-deepagent/src/my_deepagent/persistence/db.py b/my-deepagent/src/my_deepagent/persistence/db.py index ce1a555..a5d0ca2 100644 --- a/my-deepagent/src/my_deepagent/persistence/db.py +++ b/my-deepagent/src/my_deepagent/persistence/db.py @@ -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(