쿼리는 멈췄는데 트랜잭션은 남았다: PostgreSQL idle in transaction 읽는 법

🤖 AI Summary
PostgreSQL의 idle in transaction은 트랜잭션이 열린 채 쿼리를 실행하지 않는 상태입니다. 쿼리의 실행 여부와 트랜잭션의 종료 여부를 따로 읽어야 합니다. 상태·시각·애플리케이션의 처리 흐름을 대조하고, 잠금이나 정리 지연의 영향은 별도로 확인하는 순서를 제안합니다. 세션 종료나 타임아웃 변경을 모든 환경의 공통 처방으로 두지 않습니다.
블로그 목차
DB 모니터링 화면에 SQL이 보이는데 상태는 idle입니다. 이 쿼리가 계속 실행되고 있다는 뜻일까요? 화면의 SQL 문자열과 세션 상태가 답하는 질문은 다릅니다. 특히 트랜잭션이 열린 상태라면 쿼리가 멈췄다는 사실만으로 작업이 마무리됐다고 판단할 수는 없죠.
idle과 idle in transaction은 무엇이 다른가요?
둘 다 새 명령을 기다릴 수 있지만, idle in transaction은 트랜잭션이 열린 상태입니다.
PostgreSQL 18의 pg_stat_activity 설명은 상태를 다음처럼 구분합니다. 이름이 비슷하다고 같은 조치 대상으로 묶으면 안 되죠.
state | 구분할 의미 |
|---|---|
| 쿼리를 실행하는 상태입니다. |
| 새 클라이언트 명령을 기다립니다. |
| 트랜잭션 안에서 쿼리를 실행하지 않습니다. |
| 트랜잭션 안의 구문에서 오류가 발생한 상태입니다. |
첫 질문은 연결이 몇 개인지보다 어떤 상태로 남아 있는지입니다. 같은 연결 수라도 확인할 업무 맥락은 다를 수 있죠. 정상적인 유휴 연결까지 문제 세션으로 분류하지 않는 것이 좋아요.

화면에 보이는 query는 지금 실행 중인 SQL인가요?
활동 정보가 보이는 idle 계열에서 query는 마지막 쿼리이므로, 현재 실행문으로 판단하지 않습니다.
활동 정보가 수집되고 조회 권한으로 내용을 볼 수 있을 때, query는 active이면 현재 쿼리, idle 계열이면 마지막 쿼리입니다. 마지막 SQL을 장시간 실행 중인 SQL로 잘못 읽으면 원인 분석의 출발점도 달라지죠.
query는 실행 성공이나 완료를 증명하는 로그가 아닙니다. 실행 전 Parse 단계에서도 쿼리 텍스트가 보고될 수 있어요. 또한 여러 SQL을 한 요청으로 보낸 경우, 문자열에 남은 뒤쪽 구문이 앞선 오류 때문에 실행되지 않았을 수도 있죠. 요청·오류 기록과 대조해 실제로 처리된 범위를 확인하세요.
먼저 state를 읽고 SQL 문자열이 현재 작업인지 직전 작업인지 구분하세요. 그다음 애플리케이션 담당자와 해당 요청이 어느 처리 단계에 있는지 맞춰 보는 순서를 제안합니다. SQL 하나만으로 외부 호출 대기나 예외 처리 누락 같은 애플리케이션 원인을 확정하지는 않아요.
아래 SQL은 상태와 시각을 조회하는 예시이며, 세션을 종료하거나 설정을 바꾸지 않습니다.
SELECT pid, state, xact_start, query_start, state_change FROM pg_stat_activity WHERE state IN ('idle in transaction', 'idle in transaction (aborted)');
조회 계정에 따라 다른 세션의 일부 열이 NULL일 수 있죠. 이 예시에서는 state가 보이지 않는 행이 WHERE 조건에서 제외되므로, 결과가 없다고 유휴 트랜잭션이 전혀 없다고 판단하면 안 됩니다. 조회 권한과 관측 범위를 먼저 확인하는 편이 좋죠.
활동 수집 설정인 track_activities가 꺼진 백엔드는 state가 disabled로 보고됩니다. 이때 활동 정보가 수집된 idle 세션과 같은 방식으로 마지막 쿼리와 시각을 해석하지 마세요. 표시된 query도 track_activity_query_size에 따라 잘릴 수 있어, 완전한 SQL 원문이라고 단정하지 않는 것이 좋아요.
또한 active는 CPU에서 계속 계산한다는 뜻은 아니죠. 공식 문서에서 state와 wait_event는 독립적이므로, active인 쿼리도 잠금이나 I/O 등으로 대기할 수 있습니다.
SQL 본문에는 업무 값이 들어갈 수 있으므로 공유 자료에는 필요한 맥락만 남기고 민감한 값을 가리는 편이 좋습니다. 위 예시는 실제 쿼리 본문을 출력하지 않아요. 진단 기록에는 조회 시점과 대상 환경도 함께 남겨 두세요.
xact_start와 state_change는 어떻게 함께 읽나요?
트랜잭션 시작과 최근 상태 변경은 다른 시점이므로, 두 시각의 의미를 구분해 경과를 읽습니다.
열 | 의미 |
|---|---|
| 현재 활성 트랜잭션의 시작 시각이며, 활성 트랜잭션이 없으면 NULL입니다. |
| active면 현재 쿼리, 그 밖에는 마지막 쿼리의 시작 시각입니다. |
| 상태가 마지막으로 바뀐 시각입니다. |
NULL이 모두 권한 문제를 뜻하지는 않아요. PostgreSQL 18의 최상위 트랜잭션 중단 처리는 보고용 트랜잭션 시작 시각을 초기화합니다. 따라서 최상위 트랜잭션이 중단된 뒤에는 idle in transaction (aborted) 상태여도 xact_start가 NULL일 수 있죠. 값이 없으면 원래 시작 시각을 추정하지 말고, state_change와 오류 기록 등 확인 가능한 근거를 따로 읽어야 합니다.
트랜잭션이 오래 열려 있었다는 관측과 현재 유휴 상태가 오래 지속됐다는 관측을 나누어 기록하세요. 같은 시간으로 계산하면 실제로 무엇이 오래됐는지 혼동하기 쉽죠. 표시 시각의 시간대와 조회 기준 시각도 함께 확인해야 합니다.
또한 공식 문서는 현재 쿼리 정보가 조회 트랜잭션 안에서 같은 내용으로 유지될 수 있다고 설명합니다. 반복 조회에서는 조회 쪽 트랜잭션 범위도 확인해야 하죠. 같은 화면이 계속 보인다는 이유만으로 대상 세션이 전혀 변하지 않았다고 단정하지 마세요.
이 글은 PostgreSQL 18 문서 기준의 해석이죠. 다른 버전이나 관리형 환경에서는 해당 문서와 관측 도구의 갱신 방식도 확인해야 해요.
열린 트랜잭션은 잠금과 VACUUM에 어떤 영향을 주나요?
잠금을 오래 유지하거나 VACUUM의 행 정리를 지연시킬 수 있어, 상태 확인 뒤 실제 영향을 따로 봅니다.
공식 타임아웃 설명은 열린 트랜잭션이 잠금을 유지할 수 있고, 중요한 잠금이 없어도 해당 트랜잭션에 보일 수 있는 최근의 불필요한 행을 VACUUM이 제거하지 못해 테이블 비대화에 기여할 수 있다고 안내합니다.
오류로 중단된 상태의 잠금도 따로 읽어야 하죠. PostgreSQL 18의 최상위 중단 처리는 일반적인 트랜잭션 잠금을 해제하지만, 하위 트랜잭션 중단이면 바깥 트랜잭션의 잠금은 남을 수 있습니다. 또한 세션 수준 advisory lock은 롤백 후에도 유지될 수 있어요. aborted라는 상태만으로 잠금이 전부 남았다거나 전부 사라졌다고 판단하면 안 됩니다.
다만 유휴 트랜잭션을 발견했다는 사실과 다른 작업을 실제로 막고 있다는 판단은 다릅니다. 영향받는 작업과 관측 시점을 맞춰 확인해야 하죠. 상태 이름 하나만으로 모든 지연을 그 세션의 책임으로 돌리지 않는 것이 좋습니다.
운영 기록에는 열린 트랜잭션의 시각, 관련 애플리케이션 요청, 관측한 잠금 대기나 정리 지연의 근거를 나누어 적는 방법을 제안합니다. 원인으로 확정한 항목과 아직 조사할 항목을 분리하는 방식이에요. 서로 다른 시점의 지표를 한 사건의 증거처럼 합치지 마세요.
타임아웃이나 세션 종료 전에 무엇을 확인하나요?
대상 세션의 업무와 영향 범위를 확인하고, 유휴 트랜잭션 제한과 쿼리 취소·연결 종료를 구분합니다.
idle_in_transaction_session_timeout은 열린 트랜잭션 안에서 유휴 상태가 지정 시간보다 오래 지속되면 세션을 종료합니다. PostgreSQL 18 문서의 기본값 0은 비활성화입니다. 트랜잭션 밖의 유휴 연결을 대상으로 하는 idle_session_timeout과도 구분해야 하죠.
다른 제한과의 관계도 확인해야 하죠. PostgreSQL 18에서 transaction_timeout이 0보다 큰 값으로 켜져 있고 idle_in_transaction_session_timeout보다 짧거나 같으면, 유휴 트랜잭션 제한은 무시됩니다. 공식 transaction_timeout 설명과 실제 설정을 함께 확인하세요. 유휴 제한 값 하나만으로 실제 연결 종료 시점을 예측하지 않는 편이 좋아요.
설정 변경을 검토할 때는 실제 적용값, 대상 역할·연결, 정상 업무의 대기 방식과 종료 후 처리 경로부터 확인하는 편이 좋습니다. 모든 환경에 같은 초 단위 값을 추천하지 않아요. 기능의 존재와 우리 서비스에 맞는 정책은 별개의 판단이죠.
PostgreSQL의 서버 신호 함수 설명에서 pg_cancel_backend는 현재 쿼리를 취소하고, pg_terminate_backend는 세션을 종료합니다. 쿼리 취소를 열린 트랜잭션 종료와 같은 조치로 읽으면 안 되죠. 세션 종료 함수의 대기 시간 인자가 기본값이면, 반환값 true는 신호 전달 성공을 뜻하며 실제 종료 완료를 보장하지 않습니다.
조치 전에는 애플리케이션 담당자와 대상 세션의 요청·처리 결과·재시도 영향을 확인하세요. 트랜잭션 경계와 예외 처리 흐름을 함께 점검하는 순서를 제안합니다. 이 글은 운영 세션을 일괄 종료하는 SQL을 제시하지 않아요.
연결 수와 풀링 모드의 설계는 PostgreSQL이 'too many connections'를 던지는 진짜 이유: 커넥션 풀링과 PgBouncer에서 다룹니다. 이번 글은 풀 크기 조정보다 열린 트랜잭션의 상태와 기록을 해석하는 데 집중합니다.
이것만 기억하세요
쿼리의 종료와 트랜잭션의 종료를 따로 확인합니다. 상태·시각·실제 영향의 근거를 함께 읽어야 하죠. 조치 전에는 관측 범위와 대상 업무를 확인하세요.
자주 묻는 질문 (FAQ)
Q. idle 상태인 연결은 모두 종료해야 하나요?
상태 이름만으로 종료 대상을 정하지 않습니다. 트랜잭션이 열려 있는지, 어떤 업무의 연결인지와 실제 영향을 함께 확인해야 하죠.
Q. idle in transaction의 query는 계속 실행 중인 쿼리인가요?
활동 정보가 수집되고 조회 권한이 있다면, idle in transaction의 query는 마지막 쿼리 정보입니다. 현재 실행 중이거나 실행에 성공했다는 증거로 읽지 마세요.
Q. query_start로 현재 유휴 시간도 계산할 수 있나요?
query_start는 쿼리 시작 시각이며 state_change는 최근 상태 변경 시각입니다. 두 값을 같은 의미로 사용하지 않는 것이 좋아요.
Q. idle_in_transaction_session_timeout은 기본으로 켜져 있나요?
PostgreSQL 18 공식 문서의 기본값은 0이며 비활성화 상태입니다. 운영 환경의 실제 적용값과 정책 범위는 별도로 확인해야 합니다.
Q. 쿼리를 취소하면 열린 트랜잭션도 종료되나요?
현재 쿼리 취소와 세션 종료는 다른 조치입니다. 업무의 트랜잭션 처리 흐름과 조치 이후 상태를 함께 확인해야 해요.



