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

25005
2026년 08월 28일 | DBMS Error 가이드

이 글에서 다루는 내용

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

25005 no active sql transaction for branch transaction 는?

PostgreSQL 에러 코드 25005는 no active sql transaction for branch transaction으로, 분산 트랜잭션(branch transaction) 또는 준비된 트랜잭션(prepared transaction)을 수행하려 할 때 현재 활성화된 SQL 트랜잭션이 존재하지 않을 경우 발생합니다. 주로 XA 트랜잭션 또는 PREPARE TRANSACTION 명령을 잘못된 컨텍스트에서 호출할 때 나타납니다. 이 에러는 트랜잭션의 생명 주기를 올바르게 관리하지 않은 애플리케이션 코드나 미들웨어에서 자주 목격됩니다.


주요 발생 원인

1. 활성 트랜잭션 없이 PREPARE TRANSACTION 호출

가장 흔한 원인은 명시적인 BEGIN 또는 START TRANSACTION 없이 PREPARE TRANSACTION 명령을 직접 호출하는 경우입니다. PostgreSQL은 준비된 트랜잭션을 사용하려면 반드시 활성 트랜잭션 블록 내에 있어야 하며, 그렇지 않으면 25005 에러를 즉시 반환합니다. 자동 커밋(autocommit) 모드가 활성화된 환경에서는 각 SQL 문이 독립적인 트랜잭션으로 실행되어 이 문제가 더욱 빈번하게 발생합니다.

2. 트랜잭션 블록이 이미 종료된 상태에서 분기 트랜잭션 명령 실행

COMMIT 또는 ROLLBACK으로 트랜잭션이 이미 종료된 후, 애플리케이션 로직의 오류로 인해 추가적인 PREPARE TRANSACTION 또는 관련 분산 트랜잭션 명령이 호출되는 경우입니다. 특히 예외 처리 로직이 미흡한 Java의 JTA(Java Transaction API)나 Python의 데이터베이스 드라이버를 사용할 때 이런 상황이 발생하기 쉽습니다. 트랜잭션 상태 관리가 애플리케이션 레벨과 DB 레벨 간에 동기화되지 않으면 이 에러가 반복됩니다.

3. 연결 풀링 환경에서의 트랜잭션 상태 불일치

PgBouncer와 같은 연결 풀러를 사용할 때 트랜잭션 풀링 모드(transaction pooling mode)에서 연결이 재사용되면서 이전 세션의 트랜잭션 상태가 올바르게 초기화되지 않은 채로 새로운 클라이언트에 할당될 수 있습니다. 이 경우 새로운 클라이언트는 활성 트랜잭션이 없는 상태임에도 불구하고 준비된 트랜잭션 명령을 실행하려다 에러가 발생합니다. 연결 풀 설정과 트랜잭션 관리 전략이 일치하지 않으면 이 문제는 간헐적이고 재현하기 어려운 형태로 나타납니다.


해결 방법

원인 1 해결: BEGIN으로 트랜잭션 블록 명시적 시작

PREPARE TRANSACTION을 사용하기 전에 반드시 명시적으로 트랜잭션 블록을 시작해야 합니다.

-- 잘못된 예 (25005 에러 발생)
PREPARE TRANSACTION 'my_txn_001';

-- 올바른 예
BEGIN;
  INSERT INTO orders (customer_id, amount) VALUES (101, 5000);
  UPDATE inventory SET stock = stock - 1 WHERE product_id = 42;
PREPARE TRANSACTION 'my_txn_001';

-- 이후 다른 세션 또는 트랜잭션 매니저에서 커밋 또는 롤백
COMMIT PREPARED 'my_txn_001';
-- 또는
ROLLBACK PREPARED 'my_txn_001';

원인 2 해결: 트랜잭션 상태 확인 후 명령 실행

애플리케이션에서 트랜잭션 명령을 실행하기 전에 현재 트랜잭션 상태를 확인하는 로직을 추가하세요.

-- 현재 트랜잭션 상태 확인
SELECT pg_current_xact_id_if_assigned();

-- 트랜잭션 상태를 확인하는 방법 (psql 메타 정보 활용)
SELECT txid_current_if_assigned() IS NOT NULL AS has_active_txn;

-- 안전한 2단계 커밋 패턴
DO $$
BEGIN
  -- 트랜잭션이 활성 상태인지 확인 후 처리
  IF txid_current_if_assigned() IS NOT NULL THEN
    RAISE NOTICE 'Active transaction found, proceeding with PREPARE';
  ELSE
    RAISE EXCEPTION 'No active transaction. Start a transaction block first.';
  END IF;
END;
$$;

-- 올바른 2PC(Two-Phase Commit) 전체 흐름
BEGIN;
  INSERT INTO payments (order_id, amount, status)
  VALUES (2001, 15000, 'pending');
  
  -- 중간 검증 로직
  PERFORM pg_sleep(0); -- 실제 환경에서는 비즈니스 로직 수행
PREPARE TRANSACTION 'payment_txn_2001';

원인 3 해결: 연결 풀 설정 조정 및 세션 초기화

-- 연결 재사용 전 트랜잭션 상태를 초기화하는 패턴
-- PgBouncer의 server_reset_query 설정에 추가할 SQL
DISCARD ALL;

-- 또는 연결 반환 전 명시적 롤백으로 잔여 트랜잭션 정리
ROLLBACK;

-- 준비된 트랜잭션 목록 확인 (고아 트랜잭션 점검)
SELECT gid, prepared, owner, database, transaction
FROM pg_prepared_xacts
ORDER BY prepared;

-- 오래된 고아 준비된 트랜잭션 정리
ROLLBACK PREPARED 'orphaned_txn_id';

-- max_prepared_transactions 설정 확인
SHOW max_prepared_transactions;

-- postgresql.conf에서 적절한 값으로 설정
-- max_prepared_transactions = 100

예방 방법

1. 2PC(Two-Phase Commit) 사용 시 표준화된 래퍼 함수 사용

모든 준비된 트랜잭션 로직을 중앙화된 함수나 클래스로 캡슐화하여 트랜잭션 블록의 시작과 종료를 반드시 보장하는 패턴을 사용하세요. 애플리케이션 코드에서 직접 PREPARE TRANSACTION을 호출하는 것을 지양하고, 트랜잭션 매니저 레이어를 통해 상태를 항상 추적하도록 설계하세요. 또한 max_prepared_transactions 파라미터를 실제 필요한 동시 준비 트랜잭션 수보다 약간 여유 있게 설정하고, 주기적으로 pg_prepared_xacts 뷰를 모니터링하여 오래된 고아 트랜잭션을 제거하는 자동화 스크립트를 운영하세요.

-- pg_prepared_xacts 모니터링 쿼리 (정기 실행 권장)
SELECT
  gid,
  prepared,
  owner,
  database,
  EXTRACT(EPOCH FROM (now() - prepared)) / 60 AS age_minutes
FROM pg_prepared_xacts
WHERE prepared < now() - INTERVAL '10 minutes'
ORDER BY prepared ASC;

2. 애플리케이션 레벨 트랜잭션 상태 추적 및 예외 처리 강화

애플리케이션 코드에서 트랜잭션의 시작, 진행, 종료 상태를 명확히 추적하는 상태 머신을 구현하고, 모든 데이터베이스 연결 작업에 적절한 예외 처리를 추가하세요. 특히 연결 풀 환경에서는 연결을 풀에 반환하기 전에 반드시 트랜잭션이 완전히 종료(COMMIT 또는 ROLLBACK)되었음을 확인하는 검증 로직을 추가하여 불완전한 트랜잭션 상태가 다음 세션으로 전파되는 것을 방지해야 합니다.


관련 에러

  • 25000 (invalid_transaction_state): 트랜잭션 상태가 현재 수행하려는 명령과 맞지 않는 일반적인 에러로, 25005의 상위 카테고리에 해당합니다.
  • 25001 (active_sql_transaction): 이미 활성 트랜잭션이 있는 상태에서 허용되지 않는 명령을 실행할 때 발생하며, 25005와 반대 상황에서 나타납니다.
  • 25P01 (no_active_sql_transaction): 활성 트랜잭션 없이 트랜잭션 관련 명령을 실행할 때 발생하는 에러로, 25005와 유사하지만 분산 트랜잭션에 특화되지 않은 일반적인 케이스입니다.
  • 40001 (serialization_failure): 2PC 환경에서 직렬화 충돌이 발생할 때 나타나며, 준비된 트랜잭션 관리와 함께 다루어야 할 연관 에러입니다.
  • 42704: 존재하지 않는 준비된 트랜잭션 ID로 COMMIT PREPARED 또는 ROLLBACK PREPARED를 호출할 때 발생하며, 25005와 함께 2PC 디버깅 시 자주 마주치게 됩니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기