
오늘 알게 된 건 아닌데, 의외로 안 걸어둔 팀이 꽤 있더라. idle_in_transaction_session_timeout 얘기다.
내용은 단순하다. BEGIN 떠놓고 클라이언트가 다음 쿼리 안 보내고 가만히 있는 세션을 지정 시간 지나면 서버가 죽인다. 기본값이 0(비활성)이라 그냥 둔 클러스터가 많은데, 한 번 사고 겪고 나면 절대 안 빼게 된다.
왜 굳이 켜야 하나
문제는 두 갈래다.
첫째, 락 점유. 누가 어딘가에서 UPDATE 치고 COMMIT 안 한 채 자리 비우면 그 행은 계속 잠긴 채로 다른 쿼리들이 줄을 선다. 우리 팀에선 예전에 어드민 페이지에서 BEGIN; UPDATE ... WHERE id=?; 까지 가놓고 응답 안 돌아온 세션 하나 때문에 결제 트래픽 P99가 8초까지 튀었다.
둘째, vacuum 막힘. 활성 트랜잭션의 xmin 호라이즌이 안 올라가면 죽은 튜플을 청소 못 한다. 며칠 방치하면 테이블 블로트가 두 자릿수 GB로 불어난다. 이게 처음엔 안 보이다가 어느 날 plan이 갑자기 바뀌면서 터진다.
어떻게 거나
세션 단위로 통이 잡혀도 되는 OLTP라면 1분도 길다. 보통 이렇게 둔다:
-- postgresql.conf (또는 ALTER SYSTEM)
idle_in_transaction_session_timeout = '60s'
-- 배치/리포팅 유저만 따로 풀어줄 때
ALTER ROLE batch_runner SET idle_in_transaction_session_timeout = '10min';
ALTER SYSTEM 으로 걸었으면 pg_reload_conf() 한 번 호출하면 적용된다. 재시작 안 해도 된다.
확인은 이렇게:
SELECT pid, usename, state, xact_start, query_start, now() - state_change AS idle_for
FROM pg_stat_activity
WHERE state = 'idle in transaction'
ORDER BY idle_for DESC;
여기에 1분 넘게 잡힌 세션이 정기적으로 보이면 애플리케이션 쪽 코드를 의심해야 한다. 대개는 ORM 트랜잭션 컨텍스트가 예외 경로에서 안 닫히는 케이스다.
한 가지 함정
idle_in_transaction_session_timeout 이랑 idle_session_timeout은 다른 거다. 후자는 트랜잭션 밖에서 그냥 놀고 있는 커넥션을 끊는다. 풀러(pgbouncer 등) 뒤에 있으면 후자는 안 켜는 게 보통이고, 전자는 꼭 켠다.
그리고 timeout으로 끊겨도 결국 사고 자체를 막는 건 아니다. 안전망일 뿐. 진짜 원인은 commit/rollback 안 부르고 빠져나가는 코드 경로에 있다. 타임아웃 걸어두고 끝낸 게 아니라, pg_stat_activity 모니터링 같이 해서 누가 자주 걸리는지 추적하는 게 맞다.
근데 일단 한 줄 걸어두는 거랑 안 걸어두는 거랑 차이가 너무 크다. 0초 짜리 작업이니까 오늘 한번 보고 가시길.
'IT > DB 운영' 카테고리의 다른 글
| RDS PostgreSQL 16→17 업그레이드 새벽 작업기 — replication slot에 또 당했다 (0) | 2026.06.10 |
|---|---|
| PgBouncer transaction mode에 prepared statement 켰다가 새벽에 깬 이야기 (0) | 2026.05.25 |
| Aurora PostgreSQL 14 → 16 Blue/Green 업그레이드에서 삽질한 새벽 이야기 (0) | 2026.05.20 |
| PgBouncer transaction pooling, prepared statement 함정에서 빠져나온 이야기 (0) | 2026.05.14 |
| Redis Cluster slot migration 중에 P99이 4초까지 튄 새벽 (0) | 2026.05.07 |