checkpoint 커넥션의 안정화 인자 넷을 줄이지 않는다
| 결정일 | 상태 | 원본 |
|---|---|---|
| 2026-07-28 | active | heymoa-ai/docs/decisions/2026-07-28-checkpoint-connection-stability.md |
platform/bootstrap/di.py의 build_checkpoint_pool 하나에 대한 문서다.
장애 기록의 원본은 APP-192다. 실측치와 당시 판단은 그쪽에 있고, 이 문서는 그 결과로 굳은 규칙과 되돌리는 조건만 갖는다.
무엇이 있었나
프로덕션에서 채팅이 952초 멈췄다. 앱은 커넥션을 ESTAB으로 보는데 Postgres 쪽
pg_stat_activity에는 그 커넥션이 없었고, 소켓 하나가 Send-Q에 1059바이트를 붙들고 있었다.
중간 장비가 유휴 흐름을 조용히 버린 상태였다.
예외도 타임아웃도 안 났다. 로그가 비었고 헬스체크는 초록이었다.
넷을 다 넣은 직후 첫 턴이 17초로 성공했다. 그 뒤 8초로 줄었지만 그것은 프롬프트 등록까지 끝난 다른 턴이라 이 결정의 효과로 셀 수 없다 (APP-192의 표).
넷이 각각 다른 구간을 맡는다
| 무엇 | 어느 구간 |
|---|---|
| 풀 | 커넥션이 끊긴 뒤. 버리고 새로 뽑는다 |
keepalives* | 아무것도 안 보내는 동안 |
tcp_user_timeout | 보낸 뒤 ACK을 기다리는 동안 |
check | 풀에서 꺼내는 순간 |
하나가 다른 하나를 대신하지 못한다. check의 SELECT 1도 죽은 소켓에서는 같이
매달리므로 tcp_user_timeout 없이 check만 켜는 구성은 의미가 없다.
감지에 걸리는 시각은 여기 적지 않는다. 설정값에서 산술로 유도한 숫자를 적어두면 다음 사람이 재보지 않고 그것을 믿는다. 우리가 실측한 것은 위의 952초 → 8~17초 하나뿐이다. 값을 조정할 사람은 대상 환경에서 직접 재라.
autocommit·prepare_threshold·row_factory는 LangGraph가 요구하는 값이고
from_conn_string이 쓰는 것과 같다.
되돌리는 조건
넷을 각각 적는다. 하나로 뭉뚱그리면 어느 것을 뺄 수 있는지 판단할 자리가 사라진다.
| 무엇 | 언제 뺄 수 있나 |
|---|---|
| 풀 | AsyncPostgresSaver가 커넥션 재연결을 자체 지원하게 되고, 동시 채팅이 그 커넥션 하나에서 직렬화되지 않는 것을 확인했을 때 |
tcp_user_timeout | 다른 상한이 그 자리를 대신할 때만. 같은 VPC로 들어오는 것은 조건이 못 된다 — 중간 장비가 없어져도 Postgres 장애나 패킷 유실로 ACK이 안 오는 경우가 남고, 상한이 없으면 커널 재전송이 끝날 때까지(십수 분) 다시 멈춘다 |
keepalives* | tcp_user_timeout을 위 조건대로 대체한 뒤에만 검토한다. 둘 중 하나만 남긴다면 유휴 감지가 없는 쪽이 더 위험하다 |
check | tcp_user_timeout이 살아 있는 상태에서, 죽은 커넥션이 풀에 남지 않는 것을 실측으로 확인했을 때 |
넷을 한꺼번에 정리하려는 시도는 APP-192를 다시 읽고 시작하라.