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

25004
2026년 06월 24일 | DBMS Error 가이드

이 글에서 다루는 내용

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

25004 inappropriate isolation level for branch transaction 는?

PostgreSQL 에러 코드 25004는 INAPPROPRIATE ISOLATION LEVEL FOR BRANCH TRANSACTION 으로, 분산 트랜잭션(Distributed Transaction) 환경에서 브랜치 트랜잭션에 허용되지 않는 격리 수준(Isolation Level)을 설정하려 할 때 발생합니다. 특히 2PC(Two-Phase Commit, 2단계 커밋) 프로토콜을 사용하는 환경에서 SERIALIZABLE 이외의 격리 수준을 브랜치 트랜잭션에 적용하려는 경우 이 에러가 트리거됩니다. 외부 트랜잭션 매니저(XA 트랜잭션, JDBC XA 드라이버 등)와 PostgreSQL을 함께 사용하는 미들웨어 또는 엔터프라이즈 환경에서 자주 목격됩니다.


주요 발생 원인

1. 2PC(Two-Phase Commit) 브랜치에서 잘못된 격리 수준 설정

분산 트랜잭션 환경에서 PREPARE TRANSACTION 명령을 통해 브랜치 트랜잭션을 준비할 때, PostgreSQL은 SERIALIZABLE 격리 수준만을 허용합니다. 개발자가 READ COMMITTED 또는 REPEATABLE READ 상태에서 PREPARE TRANSACTION을 실행하면 25004 에러가 즉시 발생합니다. 특히 애플리케이션 레벨에서 트랜잭션 격리 수준을 명시적으로 낮추는 경우 이 문제가 자주 발생합니다.

2. XA 트랜잭션 매니저와의 격리 수준 불일치

Java EE / Jakarta EE 환경에서 XA 데이터소스를 사용하면, 트랜잭션 매니저(JBoss, WebLogic, Atomikos 등)가 내부적으로 분산 트랜잭션을 처리합니다. 이때 애플리케이션 코드 또는 데이터소스 설정에서 defaultTransactionIsolationTRANSACTION_READ_COMMITTED(2) 또는 TRANSACTION_REPEATABLE_READ(4)로 지정하면, PostgreSQL 브랜치 트랜잭션과의 격리 수준 불일치로 인해 에러가 발생합니다. 미들웨어 레이어와 데이터베이스 레이어 사이의 격리 수준 협상 로직이 잘못 구현된 경우에도 동일한 문제가 발생합니다.

3. postgresql.conf 또는 세션 레벨의 기본 격리 수준 변경

DBA가 운영 편의를 위해 default_transaction_isolation 파라미터를 serializable이 아닌 값으로 변경한 경우, 2PC를 사용하는 모든 브랜치 트랜잭션이 영향을 받습니다. 세션 레벨에서 SET default_transaction_isolation TO 'read committed'와 같이 변경한 뒤 분산 트랜잭션을 시작하면 동일한 에러가 발생하며, 이는 운영 중 갑작스럽게 배치 작업이나 서비스 전체가 실패하는 원인이 됩니다.


해결 방법

원인 1 해결: 브랜치 트랜잭션 전 SERIALIZABLE 격리 수준 명시적 지정

PREPARE TRANSACTION을 실행하기 전, 반드시 트랜잭션을 SERIALIZABLE 격리 수준으로 시작해야 합니다.

-- 잘못된 예시 (에러 발생)
BEGIN;  -- 기본값이 READ COMMITTED인 경우
INSERT INTO orders (customer_id, amount) VALUES (101, 5000);
PREPARE TRANSACTION 'txn_order_001';  -- 25004 에러 발생!

-- 올바른 예시
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
INSERT INTO orders (customer_id, amount) VALUES (101, 5000);
PREPARE TRANSACTION 'txn_order_001';  -- 정상 동작

-- 또는 SET 명령을 사용하여 격리 수준 변경
BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
UPDATE accounts SET balance = balance - 1000 WHERE account_id = 42;
PREPARE TRANSACTION 'txn_transfer_002';  -- 정상 동작

원인 2 해결: XA 드라이버 및 데이터소스 격리 수준 설정 수정

Java/JDBC 환경에서 XA 데이터소스를 사용하는 경우, JDBC URL 또는 데이터소스 설정에서 격리 수준을 SERIALIZABLE로 지정합니다.

-- PostgreSQL 서버에서 현재 세션의 격리 수준 확인
SHOW transaction_isolation;

-- 세션 기본 격리 수준을 SERIALIZABLE로 변경 (임시)
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 설정 후 분산 트랜잭션 실행
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
INSERT INTO shipments (order_id, warehouse_id, status)
VALUES (2001, 5, 'PENDING');
PREPARE TRANSACTION 'txn_shipment_2001';

-- 커밋 또는 롤백
COMMIT PREPARED 'txn_shipment_2001';
-- 또는
ROLLBACK PREPARED 'txn_shipment_2001';

원인 3 해결: postgresql.conf 기본 격리 수준 수정

-- 현재 기본 격리 수준 확인
SHOW default_transaction_isolation;

-- 서버 전체 기본값 확인 (postgresql.conf 반영 여부 포함)
SELECT name, setting, source
FROM pg_settings
WHERE name = 'default_transaction_isolation';

postgresql.conf 파일을 직접 수정합니다:

-- postgresql.conf 수정 후 적용 확인 (재로드 필요)
-- 파일 내용: default_transaction_isolation = 'serializable'

-- 설정 재로드 (재시작 없이 적용)
SELECT pg_reload_conf();

-- 적용 확인
SHOW default_transaction_isolation;
-- 결과: serializable

-- 현재 미결 2PC 트랜잭션 확인 및 정리
SELECT gid, prepared, owner, database, transaction
FROM pg_prepared_xacts
ORDER BY prepared;

-- 고아 상태의 준비된 트랜잭션 롤백
ROLLBACK PREPARED 'txn_orphan_001';

예방 방법

1. 2PC 사용 환경에서 격리 수준 표준화 및 코드 리뷰 강화

분산 트랜잭션을 사용하는 서비스의 모든 데이터 접근 레이어(DAO, Repository)에서 트랜잭션 시작 시 반드시 ISOLATION LEVEL SERIALIZABLE을 명시하는 코딩 표준을 수립하세요. CI/CD 파이프라인에 정적 분석 룰(예: custom PMD/SonarQube 룰)을 추가하여 2PC 환경에서 격리 수준이 누락되거나 잘못 설정된 코드를 배포 전에 감지하면 운영 중 장애를 사전에 차단할 수 있습니다.

-- 모니터링 쿼리: 비정상적인 격리 수준으로 실행 중인 트랜잭션 탐지
SELECT pid, usename, application_name,
       state, wait_event_type, wait_event,
       query_start, query
FROM pg_stat_activity
WHERE state = 'idle in transaction'
  AND query_start < NOW() - INTERVAL '5 minutes';

2. pg_prepared_xacts 정기 모니터링 및 알람 설정

고아 상태(Orphaned)의 준비된 트랜잭션은 테이블 VACUUM 차단, 잠금 경합, 스토리지 팽창 등 심각한 부작용을 초래합니다. 아래 쿼리를 Prometheus, Zabbix, 또는 pgBadger 같은 모니터링 툴에 통합하여 준비된 트랜잭션이 일정 시간 이상 미결 상태로 남아 있으면 즉시 알람을 발생시키는 체계를 구축하세요.

-- 10분 이상 미결 상태인 준비된 트랜잭션 감지 (알람 트리거 기준)
SELECT gid, prepared, owner, database,
       EXTRACT(EPOCH FROM (NOW() - prepared)) AS pending_seconds
FROM pg_prepared_xacts
WHERE prepared < NOW() - INTERVAL '10 minutes'
ORDER BY prepared ASC;

관련 에러

  • 25000 – INVALID TRANSACTION STATE: 트랜잭션의 현재 상태가 요청된 작업에 유효하지 않을 때 발생하는 상위 에러 클래스입니다. 25004는 이 클래스의 하위 에러입니다.
  • 25001 – ACTIVE SQL TRANSACTION: 이미 활성화된 트랜잭션 내에서 허용되지 않는 SQL 명령(예: BEGIN 중첩)을 실행할 때 발생합니다.
  • 25006 – READ ONLY SQL TRANSACTION: 읽기 전용으로 설정된 트랜잭션에서 데이터 변경(DML)을 시도할 때 발생하며, 격리 수준 관련 설정과 함께 실수하기 쉬운 에러입니다.
  • 40001 – SERIALIZATION FAILURE: SERIALIZABLE 격리 수준에서 직렬화 충돌이 발생했을 때 나타나는 에러로, 25004를 해결하기 위해 SERIALIZABLE로 전환한 뒤 추가로 마주칠 수 있는 에러입니다. 적절한 재시도(Retry) 로직 구현이 필요합니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기