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

25003
2026년 06월 23일 | DBMS Error 가이드

이 글에서 다루는 내용

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

25003 inappropriate access mode for branch transaction 는?

PostgreSQL 에러 코드 25003(inappropriate access mode for branch transaction)은 분산 트랜잭션(Distributed Transaction) 환경에서 브랜치 트랜잭션(Branch Transaction)에 부적절한 액세스 모드가 설정되었을 때 발생합니다. 이 에러는 주로 XA(eXtended Architecture) 트랜잭션이나 2단계 커밋(Two-Phase Commit, 2PC)을 사용하는 환경에서 트랜잭션의 읽기/쓰기 모드가 브랜치 트랜잭션의 규칙과 충돌할 때 나타납니다. 특히 읽기 전용(READ ONLY)으로 설정된 브랜치 트랜잭션에서 쓰기 작업을 시도하거나, 반대로 읽기/쓰기(READ WRITE) 트랜잭션에서 허용되지 않는 모드를 지정할 경우 이 에러가 발생합니다.


주요 발생 원인

1. 브랜치 트랜잭션에서 잘못된 액세스 모드 설정

가장 흔한 원인은 2단계 커밋(2PC) 또는 XA 트랜잭션에서 브랜치 트랜잭션의 액세스 모드를 명시적으로 잘못 지정하는 경우입니다. 예를 들어 전역 트랜잭션(Global Transaction)이 READ ONLY 모드로 설정되어 있는데, 해당 브랜치에서 DML(INSERT, UPDATE, DELETE) 작업을 시도하면 PostgreSQL이 이를 거부하며 25003 에러를 발생시킵니다. 이는 분산 트랜잭션의 일관성 보장을 위한 PostgreSQL의 보호 메커니즘입니다.

2. 2단계 커밋(Two-Phase Commit) 처리 중 트랜잭션 모드 불일치

애플리케이션 미들웨어(예: Java의 JTA, Spring의 TransactionManager)가 분산 트랜잭션을 관리할 때, 각 리소스 매니저(Resource Manager)에 전달되는 트랜잭션 모드가 서로 일치하지 않는 경우 이 에러가 발생할 수 있습니다. 특히 하나의 글로벌 트랜잭션 내에서 일부 브랜치는 READ ONLY로, 일부는 READ WRITE로 혼용하려 할 때 PostgreSQL의 2PC 프로토콜과 충돌이 발생합니다. 이런 경우 PREPARE TRANSACTION 단계에서 에러가 나타나기도 합니다.

3. 세션 레벨 트랜잭션 설정과 브랜치 트랜잭션 간의 충돌

세션 수준에서 SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY로 기본 트랜잭션 모드를 설정한 상태에서 쓰기가 필요한 브랜치 트랜잭션을 시작하려 할 때 충돌이 발생합니다. PostgreSQL은 세션 레벨의 설정을 상속받는 방식으로 동작하기 때문에, 브랜치 트랜잭션이 해당 제약을 위반하는 액세스 모드로 진입하면 즉시 에러를 반환합니다. 이 문제는 커넥션 풀(Connection Pool)을 사용하는 환경에서 특히 자주 발생하며, 이전 세션의 설정이 재사용된 커넥션에 남아있을 수 있습니다.


해결 방법

원인 1 해결: 브랜치 트랜잭션 액세스 모드 명시적 지정

브랜치 트랜잭션을 시작할 때 액세스 모드를 명확히 READ WRITE로 지정하여 충돌을 방지합니다.

-- 잘못된 예: READ ONLY 설정 후 쓰기 시도
BEGIN TRANSACTION READ ONLY;
INSERT INTO orders (product_id, quantity) VALUES (101, 5); -- 25003 에러 발생!
ROLLBACK;

-- 올바른 예: 쓰기 작업이 필요한 경우 READ WRITE 명시
BEGIN TRANSACTION READ WRITE;
INSERT INTO orders (product_id, quantity) VALUES (101, 5); -- 정상 동작
COMMIT;
-- 2PC 환경에서의 올바른 브랜치 트랜잭션 처리 예시
BEGIN TRANSACTION READ WRITE;
UPDATE inventory SET stock = stock - 5 WHERE product_id = 101;
PREPARE TRANSACTION 'xa_branch_001'; -- 1단계: PREPARE

-- 이후 글로벌 커밋 결정 후
COMMIT PREPARED 'xa_branch_001'; -- 2단계: COMMIT

원인 2 해결: 2PC 트랜잭션 모드 일관성 확보

글로벌 트랜잭션 내 모든 브랜치의 액세스 모드를 일관되게 유지합니다.

-- 준비된 트랜잭션 목록 확인 (불일치 트랜잭션 디버깅용)
SELECT gid, owner, database, prepared, transaction
FROM pg_prepared_xacts
ORDER BY prepared DESC;

-- 문제가 있는 준비된 트랜잭션 롤백
ROLLBACK PREPARED 'xa_branch_001';

-- 쓰기/읽기가 혼재된 경우, 전체 브랜치를 READ WRITE로 통일
BEGIN TRANSACTION READ WRITE;
-- 읽기 작업
SELECT balance FROM accounts WHERE user_id = 42;
-- 쓰기 작업
UPDATE accounts SET balance = balance - 100 WHERE user_id = 42;
PREPARE TRANSACTION 'xa_global_txn_002';

원인 3 해결: 세션 레벨 트랜잭션 설정 초기화

커넥션 풀 환경에서 커넥션을 재사용하기 전에 세션 설정을 반드시 초기화합니다.

-- 현재 세션의 트랜잭션 특성 확인
SHOW transaction_read_only;

-- 세션 레벨 READ ONLY 설정을 READ WRITE로 재설정
SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE;

-- 또는 기본값으로 초기화
RESET ALL;

-- 커넥션 풀 반환 전 세션 정리 예시
DISCARD ALL; -- 세션의 모든 임시 설정, 준비된 구문, 임시 테이블 등 초기화

-- 이후 안전하게 브랜치 트랜잭션 시작
BEGIN TRANSACTION READ WRITE;
UPDATE ledger SET amount = amount + 500 WHERE account_no = 'ACC-007';
COMMIT;

예방 방법

1. 트랜잭션 모드 명시적 선언 정책 수립

모든 트랜잭션, 특히 분산 환경에서의 브랜치 트랜잭션은 액세스 모드(READ ONLY 또는 READ WRITE)를 항상 명시적으로 선언하는 코딩 표준을 팀 내에서 확립하세요. 암묵적인 기본값에 의존하지 않고 명시적으로 모드를 지정하면, 세션 설정 상속으로 인한 예기치 않은 충돌을 방지할 수 있습니다. 또한 CI/CD 파이프라인에 트랜잭션 모드 검증 로직을 포함하면 배포 전에 문제를 조기 발견할 수 있습니다.

-- 권장: 트랜잭션 시작 시 항상 모드 명시
BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED READ WRITE;

2. 커넥션 풀 초기화 및 모니터링 강화

PgBouncer, HikariCP 등의 커넥션 풀을 사용하는 경우, 커넥션 반환 시 세션 상태를 초기화하는 설정을 반드시 활성화하세요. PgBouncer의 경우 server_reset_query = DISCARD ALL 설정을 권장하며, 이를 통해 이전 세션의 트랜잭션 모드 설정이 다음 사용자에게 전파되는 문제를 방지할 수 있습니다. 주기적으로 pg_stat_activity 뷰를 모니터링하여 비정상적인 트랜잭션 상태를 조기에 감지하는 것도 중요합니다.

-- 세션 활동 모니터링
SELECT pid, usename, application_name, state, query, 
       now() - query_start AS query_duration
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY query_duration DESC;

관련 에러

  • 25000 (in_failed_sql_transaction): 실패한 트랜잭션 블록 내에서 추가 SQL 실행 시 발생하며, 25003과 함께 트랜잭션 상태 관련 에러군(Class 25)에 속합니다.
  • 25001 (active_sql_transaction): 이미 활성화된 트랜잭션 중 트랜잭션 설정 변경 시 발생합니다. 브랜치 트랜잭션 시작 후 모드를 변경하려 할 때 이 에러가 선행하여 나타날 수 있습니다.
  • 25P01 (no_active_sql_transaction): 활성 트랜잭션이 없는 상태에서 트랜잭션 관련 명령 실행 시 발생하며, 2PC 환경에서 COMMIT PREPARED 또는 ROLLBACK PREPARED를 잘못 호출할 때 연계될 수 있습니다.
  • 25P02 (in_failed_sql_transaction): 트랜잭션이 중단(abort) 상태일 때 발생하며, 25003 에러 이후 복구 처리 없이 추가 쿼리를 실행하면 이 에러로 이어질 수 있습니다.
DBMS 에러 코드 시리즈

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

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

댓글 남기기