2026년 06월 23일 | DBMS Error 가이드
이 글에서 다루는 내용
25002 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
25002 branch transaction already active 는?
PostgreSQL 에러 코드 25002, branch transaction already active는 분산 트랜잭션(Distributed Transaction) 환경에서 동일한 트랜잭션 브랜치가 이미 활성 상태인데 다시 시작하려 할 때 발생합니다. 주로 XA 트랜잭션(eXtended Architecture Transaction) 프로토콜을 사용하는 Java EE/Jakarta EE 애플리케이션 서버, 미들웨어, 또는 2단계 커밋(Two-Phase Commit, 2PC)을 구현한 시스템에서 마주치는 에러입니다. 동일한 XID(Transaction Identifier)를 가진 브랜치 트랜잭션이 세션 내에서 이미 열려 있는 상태에서 중복으로 시작 명령이 내려질 경우 PostgreSQL이 이 에러를 던지며 작업을 거부합니다.
주요 발생 원인
1. XA 트랜잭션 브랜치의 중복 시작 (가장 빈번한 원인)
XA 트랜잭션에서 XA START 혹은 XA BEGIN 명령으로 특정 XID를 가진 브랜치를 시작한 이후, 동일 세션 혹은 동일 XID로 다시 XA START를 호출하면 이 에러가 발생합니다. 트랜잭션 매니저(Transaction Manager)가 상태를 제대로 추적하지 못하거나, 애플리케이션 레이어에서 커넥션 풀 반환 전 트랜잭션을 정리하지 않은 채 동일 커넥션을 재사용하는 경우가 대표적입니다. 특히 WildFly, WebLogic, WebSphere 같은 WAS 환경에서 JTA(Java Transaction API)를 통해 데이터소스를 관리할 때 커넥션 풀 설정 오류와 결합되어 나타나는 경우가 많습니다.
2. 커넥션 풀에서 미정리된 트랜잭션 브랜치 재사용
PgBouncer, HikariCP, c3p0 등의 커넥션 풀을 사용하는 환경에서, 이전 세션이 XA 트랜잭션 브랜치를 PREPARE하거나 완전히 종료하지 않은 상태에서 커넥션이 풀로 반환된 후 새 요청에 재할당될 때 문제가 발생합니다. 새 요청이 동일한 XID로 트랜잭션을 시작하려 하면 이미 브랜치가 활성화된 것으로 인식되어 에러가 발생합니다. 이 경우 pg_prepared_xacts 뷰를 조회하면 고아(orphan) 상태의 Prepared Transaction을 확인할 수 있습니다.
3. 2PC(Two-Phase Commit) 구현 코드의 로직 오류
직접 PREPARE TRANSACTION 구문을 사용하여 2단계 커밋을 구현하는 경우, 예외 처리 로직의 미흡으로 인해 동일한 트랜잭션 이름으로 중복 PREPARE가 시도될 수 있습니다. 예를 들어, 네트워크 타임아웃이나 일시적인 오류 발생 시 재시도(Retry) 로직이 작동하면서 이미 활성화된 브랜치를 다시 시작하려는 시도가 일어날 수 있습니다. 애플리케이션이 트랜잭션 상태를 외부 저장소(Redis, DB 등)에서 관리하지 않고 메모리에만 유지할 경우 서버 재시작 이후 상태 불일치로 이 문제가 더욱 빈번하게 나타납니다.
해결 방법
원인 1: 중복 XA 트랜잭션 브랜치 정리
현재 활성화된 Prepared Transaction 목록을 확인하고 고아 트랜잭션을 정리합니다.
-- 현재 준비된(Prepared) 트랜잭션 목록 확인
SELECT gid, prepared, owner, database
FROM pg_prepared_xacts
ORDER BY prepared;
-- 고아 상태의 Prepared Transaction 롤백 (운영 환경에서는 신중하게 수행)
ROLLBACK PREPARED 'your_transaction_id';
-- 또는 커밋 처리
COMMIT PREPARED 'your_transaction_id';
현재 세션에서 진행 중인 트랜잭션 상태를 확인하려면 아래 쿼리를 사용하세요.
-- 현재 세션의 트랜잭션 상태 확인
SELECT pid, usename, application_name, state, query, backend_xid, backend_xmin
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY backend_xid;
원인 2: 커넥션 풀의 미정리 트랜잭션 처리
커넥션 풀에서 커넥션을 반환하기 전에 반드시 트랜잭션을 종료하는 검증 쿼리를 설정하거나, 애플리케이션 코드 레벨에서 명시적으로 처리합니다.
-- 커넥션 반환 전 트랜잭션 상태 초기화 (PgBouncer server_reset_query 설정에 활용)
DISCARD ALL;
-- 또는 트랜잭션 명시적 롤백 후 반환
ROLLBACK;
-- 오랫동안 유휴 상태인 트랜잭션을 가진 세션 강제 종료
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
AND query_start < NOW() - INTERVAL '10 minutes';
-- max_prepared_transactions 설정 확인 (0이면 2PC 비활성화)
SHOW max_prepared_transactions;
-- postgresql.conf에서 적절한 값으로 설정 필요
-- max_prepared_transactions = 100 -- 예시값
원인 3: 2PC 구현 코드 수정
직접 2단계 커밋을 구현하는 경우, PREPARE 전에 반드시 중복 여부를 체크하는 방어 로직을 추가합니다.
-- PREPARE 전 동일한 gid 존재 여부 확인
DO $$
DECLARE
v_gid TEXT := 'my_transaction_001';
v_count INT;
BEGIN
SELECT COUNT(*) INTO v_count
FROM pg_prepared_xacts
WHERE gid = v_gid;
IF v_count > 0 THEN
RAISE NOTICE '트랜잭션 [%]이 이미 준비된 상태입니다. 먼저 정리가 필요합니다.', v_gid;
-- 상황에 따라 ROLLBACK PREPARED 또는 COMMIT PREPARED 수행
EXECUTE 'ROLLBACK PREPARED ' || quote_literal(v_gid);
END IF;
END;
$$;
-- 이후 새로운 트랜잭션 시작
BEGIN;
-- ... DML 작업 수행 ...
PREPARE TRANSACTION 'my_transaction_001';
-- 트랜잭션 상태 모니터링 함수 예시
CREATE OR REPLACE FUNCTION check_and_cleanup_prepared_xact(p_gid TEXT)
RETURNS TEXT AS $$
DECLARE
v_result TEXT;
BEGIN
IF EXISTS (SELECT 1 FROM pg_prepared_xacts WHERE gid = p_gid) THEN
ROLLBACK PREPARED p_gid;
v_result := 'CLEANED: ' || p_gid;
ELSE
v_result := 'NOT_FOUND: ' || p_gid;
END IF;
RETURN v_result;
EXCEPTION WHEN OTHERS THEN
RETURN 'ERROR: ' || SQLERRM;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
예방 방법
1. Prepared Transaction 주기적 모니터링 및 자동 정리 체계 구축
운영 환경에서는 pg_prepared_xacts 뷰를 주기적으로 모니터링하여 일정 시간(예: 30분) 이상 PREPARE 상태로 남아 있는 고아 트랜잭션을 자동으로 감지하고 알림을 보내는 체계를 구축해야 합니다. Prometheus + pg_exporter, Datadog, 또는 자체 크론잡을 통해 SELECT COUNT(*) FROM pg_prepared_xacts WHERE prepared < NOW() - INTERVAL '30 minutes' 쿼리를 주기적으로 실행하고, 결과가 0보다 크면 즉시 운영팀에 알림을 전송하도록 설정하세요. 이를 통해 브랜치 트랜잭션 중복 시작 상황이 발생하기 전에 선제적으로 대응할 수 있습니다.
2. 커넥션 풀 및 트랜잭션 매니저 설정 표준화
PgBouncer를 사용하는 경우 server_reset_query = DISCARD ALL을 반드시 설정하고, Transaction 풀링 모드보다 Session 풀링 모드를 XA 트랜잭션 환경에서 사용하는 것을 권장합니다. HikariCP를 사용하는 경우 connectionInitSql에 트랜잭션 상태를 초기화하는 쿼리를 등록하고, idleTimeout 및 maxLifetime 설정을 통해 오래된 커넥션이 자동으로 교체되도록 해야 합니다. 또한 max_prepared_transactions PostgreSQL 파라미터를 애플리케이션의 최대 동시 분산 트랜잭션 수의 1.5배 이상으로 설정하여 준비된 트랜잭션 슬롯 부족으로 인한 연쇄 장애를 방지하세요.
관련 에러
- 25000 (invalid_transaction_state): 트랜잭션 상태가 유효하지 않을 때 발생하는 상위 카테고리 에러로, 25002와 동일한 클래스에 속합니다.
- 25001 (active_sql_transaction): 이미 활성화된 SQL 트랜잭션이 있는 상태에서 허용되지 않는 작업을 수행할 때 발생합니다.
- 25003 (inappropriate_access_mode_for_branch_transaction): 브랜치 트랜잭션에 적절하지 않은 접근 모드(읽기/쓰기)를 사용하려 할 때 발생합니다.
- 25004 (inappropriate_isolation_level_for_branch_transaction): 브랜치 트랜잭션에 허용되지 않는 격리 수준을 설정하려 할 때 발생합니다.
- 40001 (serialization_failure): 분산 트랜잭션 환경에서 직렬화 실패 시 함께 나타날 수 있는 에러로, 2PC 구현 시 함께 처리 로직을 고려해야 합니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.