IT/DB 운영

Postgres에서 한 줄 설정으로 막을 수 있는 idle 사고

gfrog 2026. 6. 5. 15:16
SMALL

오늘 알게 된 건 아닌데, 의외로 안 걸어둔 팀이 꽤 있더라. 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초 짜리 작업이니까 오늘 한번 보고 가시길.

BIG