나의 잡다한 노트 및 메모

PG의 2PC(2-Phase-Commit) 본문

DB/PostgreSQL

PG의 2PC(2-Phase-Commit)

peanutwalnut 2025. 8. 11. 20:13

1) 2PC가 뭐고 언제 쓰나

  • 목적: 여러 자원(노드/샤드/DB/외부 시스템)에 걸친 트랜잭션을 원자적으로 커밋하거나 롤백하기 위해 쓰는 프로토콜입니다.
  • 적용 예:
    • 분산 DB의 여러 샤드에 걸친 쓰기
    • DB + 메시지브로커/외부 시스템 동시 원자성 확보(XA 패턴)
    • Citus처럼 코디네이터 ↔ 워커 노드가 있는 구조의 다중 샤드 갱신

 

2) Postgres의 2PC 동작(명령 흐름)

PostgreSQL은 트랜잭션을 준비(prepare) 상태로 영속화해 두었다가, 최종 결정에 따라 커밋/롤백합니다.

  1. (일반 트랜잭션 수행)
     
    BEGIN; -- DML/DDL...
  2. 준비 단계(Phase 1) – 디스크에 상태를 기록
    • 이 시점에 트랜잭션은 prepared 상태가 되고, 서버 재시작 후에도 남습니다.
    • 락과 XID가 유지되어 다른 세션을 막을 수 있습니다.
       
      PREPARE TRANSACTION 'my_gid'; -- GID: 전역 식별자, 클러스터 내 고유하게 사용
  3. 결정 단계(Phase 2) – 준비된 트랜잭션을 커밋/롤백
     
    COMMIT PREPARED 'my_gid'; -- 또는 ROLLBACK PREPARED 'my_gid';

중요 제약

  • COMMIT/ROLLBACK PREPARED는 준비했던 동일 데이터베이스에 접속해 실행해야 합니다.
  • max_prepared_transactions(서버 설정)가 0이면 PREPARE 불가 → 코디네이터/워커 모두 적절히 설정해야 합니다.
  • 임시테이블 등 2PC 미지원 동작이 포함되면 PREPARE가 거부될 수 있습니다.

 

 

3) prepared 상태의 특징(운영 관점)

  • 락 유지: row/relation 락이 풀리지 않아 다른 DDL/DML을 블로킹할 수 있습니다.
  • VACUUM 지연: prepared 트랜잭션의 XID가 오래 남으면 진공/히스토리 정리 지연XID 래퍼라운드 위험을 키울 수 있습니다.
  • 지속성: 서버가 크래시해도 pg_twophase/에 기록된 상태로 복구 후 그대로 존재합니다(수동 정리 필요할 수 있음).
  • 모니터링:
     
    SELECT *, now() - prepared AS age FROM pg_prepared_xacts ORDER BY prepared;

 

4) 샤딩/분산 DB(Citus 등)와 2PC

분산 환경에서는 코디네이터가 트랜잭션 매니저 역할을 하고, **각 샤드(워커)**가 참여자(participant)가 됩니다.

기본 시퀀스(개념)

  1. 코디네이터가 각 워커(샤드)에 서브트랜잭션을 실행
  2. Phase 1: 모든 워커에 PREPARE TRANSACTION '공통 GID' 전달 → 각 워커가 “준비됨”으로 응답
  3. 결정: 코디네이터가 성공이라 판단하면
    Phase 2: 모든 워커에 COMMIT PREPARED '공통 GID'(혹은 ROLLBACK PREPARED) 브로드캐스트
  4. 코디네이터는 모든 워커의 결과를 확인 후 성공 처리

왜 중요?

  • 여러 샤드에 분산된 쓰기를 원자적으로 만들 수 있습니다.
  • 단, 지연/락/오버헤드가 증가하므로 단일 샤드 트랜잭션(코로케이션 키 활용)은 가능하면 2PC 경로를 피하는 것이 일반적인 성능 베스트 프랙티스입니다.

장애/부분 실패 시

  • 코디네이터 장애, 네트워크 분할 등으로 일부 워커만 prepared 상태로 남는 경우가 가장 까다롭습니다.
  • 이런 경우, 코디네이터가 유지하는 결정 로그/메타데이터(최종 커밋/롤백 의사)를 기준으로 워커의 COMMIT/ROLLBACK PREPARED를 재시도해야 합니다.
  • 자동 정리가 실패하면 운영자가 수동으로 남은 GID를 조회 후 정리해야 합니다.
  • -- 워커에서
    SELECT gid, prepared FROM pg_prepared_xacts;
    -- 결정에 맞춰
    COMMIT PREPARED 'gid';  -- 또는 ROLLBACK PREPARED

 

5) 장애 시나리오별 정리 전략(샤딩 포함)

A. 코디네이터가 PREPARE까지 보냈고, 결정 전에 장애

  • 워커들에 prepared xact가 남습니다(락 유지).
  • 복구 후 코디네이터의 결정 정보를 확인 → 모든 워커에 COMMIT/ROLLBACK PREPARED 재전송.
  • 결정 정보가 없다면(최악) 업무 로그/외부 시스템 상태를 근거로 운영 판정(일관성 위해 보수적으로 롤백).

B. 일부 워커 응답 누락(네트워크 분리)

  • 준비/결정이 부분 적용될 수 있습니다.
  • 코디네이터 로그 기준으로 정합성 회복(준비된 곳은 결정 재전송, 누락된 곳은 준비/결정 재시도 혹은 수동 개입).

C. 오래된 prepared로 인한 시스템 정체

  • pg_prepared_xacts에서 최장 대기 트랜잭션 파악 → 업무와 협의해 롤백 우선.
  • 앞서 드린 원샷 스크립트처럼 min-age 기준으로 안전하게 정리

 

6) 운영 베스트 프랙티스

  • 준비 트랜잭션 제한:
    • max_prepared_transactions를 기대 동시성에 맞게, coordinator/worker 모두 설정.
    • 너무 낮으면 에러, 너무 높으면 방치 위험 증가 → 모니터링 필수.
  • GID 네이밍 규칙: 외부 트랜잭션/요청 ID 포함해 중복 없이(e.g., svcA:order:2025-08-11T12:34:...).
    분산 환경에선 모든 참가자에 동일 GID를 사용하면 추적과 정리가 쉬움.
  • 타임아웃/감시: Postgres 자체는 prepared 자동만료 없음 →
    • 애플리케이션 레벨 타임아웃으로 결정 지연 방지
    • 주기적 점검 잡(pg_cron 등)으로 오래된 prepared 경보/자동 정리(보수적 정책)
  • 가능하면 단일 샤드 트랜잭션 유도: 파티션 키(배치 키/고객 키) 설계로 cross-shard 감소 → 2PC 경로 진입 최소화.
  • 업무 설계 대안 검토: 2PC가 비용이 크면 사가(보상 트랜잭션), 아웃박스 패턴 등으로 현실적 일관성 확보.

7) 필수 SQL 치트시트

 
-- 준비 트랜잭션 목록 + 경과시간 SELECT gid, prepared, owner, database, now() - prepared AS age FROM
pg_prepared_xacts ORDER BY prepared;
 
-- 준비 → 커밋/롤백 COMMIT PREPARED 'gid'; ROLLBACK PREPARED 'gid';
 
-- (Citus/샤드)
워커 전역 조회/정리
SELECT * FROM run_command_on_workers($cmd$ SELECT gid, prepared FROM pg_prepared_xacts; $cmd$);
 
SELECT * FROM run_command_on_workers($cmd$ COMMIT PREPARED 'gid'; $cmd$);
 
SELECT * FROM run_command_on_workers($cmd$ ROLLBACK PREPARED 'gid'; $cmd$);

 

'DB > PostgreSQL' 카테고리의 다른 글

PostgreSQL Subscription  (0) 2025.08.20
PostgreSQL Replication slot  (0) 2025.08.20
PG의 wal_level 설정  (3) 2025.07.30
PREPARE TRANSACTION  (3) 2025.07.30
PostgreSQL 아키텍쳐 및 컴포넌트  (2) 2025.07.29