PostgreSQL 25P03 오류 원인과 해결 방법 완벽 가이드

25P03
2026년 08월 29일 | DBMS Error 가이드

이 글에서 다루는 내용

25P03 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.

25P03 idle in transaction session timeout 는?

PostgreSQL 에러 코드 25P03 (idle_in_transaction_session_timeout) 은 트랜잭션이 시작된 이후 아무런 쿼리 활동 없이 일정 시간 이상 유휴 상태(idle in transaction)로 머물렀을 때 PostgreSQL 서버가 해당 세션을 강제로 종료하면서 발생하는 에러입니다. 즉, BEGIN 또는 START TRANSACTION 으로 트랜잭션을 열어 놓은 채 지정된 타임아웃 시간 동안 아무 SQL도 실행하지 않으면 서버가 해당 연결을 끊어 버립니다. 이 에러는 주로 애플리케이션의 커넥션 풀 관리 미흡, 코드 버그, 또는 사용자가 트랜잭션 도중 장시간 자리를 비운 경우에 발생하며, 방치된 트랜잭션이 테이블 잠금(Lock)을 계속 보유하여 다른 쿼리들이 대기(Blocking)하는 심각한 성능 저하를 유발할 수 있습니다.


주요 발생 원인

1. 애플리케이션 코드에서 트랜잭션을 열고 커밋/롤백을 누락

가장 흔한 원인으로, 애플리케이션이 BEGIN으로 트랜잭션을 시작한 후 비즈니스 로직 처리 도중 예외가 발생하거나 외부 API 호출이 지연되어 COMMIT 또는 ROLLBACK이 실행되지 않는 경우입니다. 커넥션 풀(PgBouncer, HikariCP 등)을 사용하는 환경에서는 해당 커넥션이 풀로 반환되지 않고 idle in transaction 상태로 장시간 점유되어, 타임아웃 설정이 있을 경우 서버가 강제로 세션을 종료합니다.

2. 대화형(Interactive) 세션에서 트랜잭션 방치

개발자나 DBA가 psql 또는 GUI 툴(DBeaver, DataGrip 등)을 통해 수동으로 BEGIN을 실행한 뒤 자리를 비우거나 다른 작업으로 전환하여 트랜잭션을 닫지 않는 경우입니다. 이 상황은 개발 및 QA 환경에서 특히 빈번하게 발생하며, 해당 세션이 테이블 락을 보유하고 있다면 다른 사용자들의 작업 전체를 블로킹할 수 있어 운영 환경에서는 치명적입니다.

3. 커넥션 풀 설정 오류 또는 풀러(Pooler) 레벨의 세션 재사용 문제

PgBouncer를 session 모드로 운영할 때, 클라이언트가 커넥션을 반환했음에도 불구하고 서버 측 커넥션이 idle in transaction 상태로 남아 있는 경우가 발생합니다. 이는 애플리케이션이 트랜잭션을 명시적으로 종료하지 않고 커넥션을 풀에 반납할 때 흔히 발생하며, 설정된 idle_in_transaction_session_timeout 값에 도달하면 PostgreSQL이 해당 백엔드 프로세스를 종료시킵니다.


해결 방법

원인 1 해결: 트랜잭션 명시적 종료 및 예외 처리 강화

애플리케이션 코드에서 반드시 try-catch-finally 패턴으로 트랜잭션을 관리하고, 예외 발생 시 롤백을 보장해야 합니다. 아래는 현재 idle in transaction 상태인 세션을 확인하고 강제 종료하는 SQL입니다.

-- idle in transaction 상태의 세션 현황 확인
SELECT
    pid,
    usename,
    application_name,
    client_addr,
    state,
    state_change,
    now() - state_change AS idle_duration,
    query
FROM pg_stat_activity
WHERE state = 'idle in transaction'
ORDER BY idle_duration DESC;

-- 특정 PID의 세션을 안전하게 종료 (SIGTERM - 쿼리 완료 후 종료)
SELECT pg_terminate_backend(12345);

-- 5분 이상 idle in transaction 상태인 세션 일괄 강제 종료
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
  AND state_change < now() - INTERVAL '5 minutes'
  AND pid <> pg_backend_pid();

원인 2 해결: 타임아웃 파라미터 설정

idle_in_transaction_session_timeout 값을 적절하게 설정하여 방치된 트랜잭션이 자동으로 종료되도록 합니다. 운영 환경에서는 보통 1~5분 사이의 값을 권장합니다.

-- 현재 설정값 확인
SHOW idle_in_transaction_session_timeout;

-- 세션 레벨에서 임시 설정 (현재 세션에만 적용)
SET idle_in_transaction_session_timeout = '2min';

-- 데이터베이스 레벨에서 설정 (해당 DB 접속 시 모두 적용)
ALTER DATABASE mydb SET idle_in_transaction_session_timeout = '3min';

-- 특정 역할(Role)에 대해 설정
ALTER ROLE myapp_user SET idle_in_transaction_session_timeout = '1min';

-- postgresql.conf 또는 ALTER SYSTEM으로 전역 설정
ALTER SYSTEM SET idle_in_transaction_session_timeout = '5min';
SELECT pg_reload_conf(); -- 설정 재로드 (재시작 불필요)

원인 3 해결: 커넥션 풀 점검 및 트랜잭션 모드 전환

PgBouncer를 사용하는 경우 가능하면 transaction 모드를 사용하고, 애플리케이션이 트랜잭션을 명확히 종료하도록 강제합니다.

-- PgBouncer 상태 확인용 (psql로 PgBouncer 관리 포트 접속 후 실행)
-- SHOW POOLS;
-- SHOW CLIENTS;

-- 트랜잭션 블록 내 타임아웃 설정 예시 (개별 트랜잭션 보호)
BEGIN;
SET LOCAL idle_in_transaction_session_timeout = '30s';
UPDATE orders SET status = 'processing' WHERE order_id = 1001;
-- 이후 반드시 COMMIT 또는 ROLLBACK 실행
COMMIT;

-- 현재 활성 잠금(Lock) 현황과 idle in transaction 세션의 상관관계 확인
SELECT
    l.pid,
    l.locktype,
    l.relation::regclass AS table_name,
    l.mode,
    a.state,
    a.query,
    now() - a.state_change AS blocked_duration
FROM pg_locks l
JOIN pg_stat_activity a ON l.pid = a.pid
WHERE a.state = 'idle in transaction'
ORDER BY blocked_duration DESC;

예방 방법

1. idle_in_transaction_session_timeout 전역 설정을 운영 표준으로 채택

모든 PostgreSQL 인스턴스에 idle_in_transaction_session_timeout 을 반드시 설정해야 합니다. 기본값은 0(무제한)으로, 운영 환경에서는 절대 기본값을 그대로 사용해서는 안 됩니다. 일반적인 OLTP 환경에서는 2~5분, 배치 작업이 혼재하는 환경에서는 10~30분 을 권장하며, postgresql.conf에 명시적으로 기록하여 인프라 코드(IaC)로 관리하는 것이 Best Practice입니다. 또한 모니터링 도구(Prometheus + postgres_exporter, pg_activity 등)를 통해 idle in transaction 세션 수를 실시간 알림으로 감지하는 대시보드를 구축해야 합니다.

-- 예방을 위한 권장 설정 (postgresql.conf)
-- idle_in_transaction_session_timeout = '5min'
-- statement_timeout = '30min'
-- lock_timeout = '30s'

-- 현재 설정 종합 확인
SELECT name, setting, unit, short_desc
FROM pg_settings
WHERE name IN (
    'idle_in_transaction_session_timeout',
    'statement_timeout',
    'lock_timeout',
    'deadlock_timeout'
);

2. 애플리케이션 레벨의 트랜잭션 관리 패턴 표준화

모든 데이터베이스 접근 코드에서 트랜잭션의 시작과 종료를 명확히 하는 코딩 표준을 수립해야 합니다. ORM(JPA, SQLAlchemy, Django ORM 등)을 사용하는 경우 트랜잭션 자동 관리 기능을 올바르게 설정하고, 커넥션을 풀로 반환하기 전에 반드시 트랜잭션이 종료된 상태인지 확인하는 로직을 포함해야 합니다. 정기적인 코드 리뷰 시 트랜잭션 경계(Transaction Boundary)가 명확한지 점검하는 체크리스트를 운용하는 것을 강력히 권장합니다.


관련 에러

  • 25001 (active_sql_transaction): 활성 트랜잭션 내에서 허용되지 않는 명령 실행 시 발생하며, 25P03과 마찬가지로 트랜잭션 상태 관리 미흡에서 비롯됩니다.
  • 55P03 (lock_not_available): idle in transaction 세션이 락을 보유한 채 타임아웃을 맞이하면, 해당 락을 기다리던 다른 세션들이 연쇄적으로 이 에러를 만날 수 있습니다.
  • 57014 (query_canceled): statement_timeout 또는 lock_timeout 초과 시 발생하며, 25P03과 함께 타임아웃 관련 에러군에 속합니다. 세 가지 타임아웃(statement, lock, idle_in_transaction)을 함께 설정하여 데이터베이스를 방어적으로 운영하는 것이 권장됩니다.
  • 08006 (connection_failure): 25P03으로 세션이 강제 종료된 후 클라이언트가 해당 커넥션으로 재시도할 경우 커넥션 실패 에러로 이어질 수 있습니다.

DBMS 에러 코드 시리즈

주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.

본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.

댓글 남기기