나의 잡다한 노트 및 메모
PG의 2PC(2-Phase-Commit) 본문
1) 2PC가 뭐고 언제 쓰나
- 목적: 여러 자원(노드/샤드/DB/외부 시스템)에 걸친 트랜잭션을 원자적으로 커밋하거나 롤백하기 위해 쓰는 프로토콜입니다.
- 적용 예:
- 분산 DB의 여러 샤드에 걸친 쓰기
- DB + 메시지브로커/외부 시스템 동시 원자성 확보(XA 패턴)
- Citus처럼 코디네이터 ↔ 워커 노드가 있는 구조의 다중 샤드 갱신
2) Postgres의 2PC 동작(명령 흐름)
PostgreSQL은 트랜잭션을 준비(prepare) 상태로 영속화해 두었다가, 최종 결정에 따라 커밋/롤백합니다.
- (일반 트랜잭션 수행)
BEGIN; -- DML/DDL...
- 준비 단계(Phase 1) – 디스크에 상태를 기록
- 이 시점에 트랜잭션은 prepared 상태가 되고, 서버 재시작 후에도 남습니다.
- 락과 XID가 유지되어 다른 세션을 막을 수 있습니다.
PREPARE TRANSACTION 'my_gid'; -- GID: 전역 식별자, 클러스터 내 고유하게 사용
- 결정 단계(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)가 됩니다.
기본 시퀀스(개념)
- 코디네이터가 각 워커(샤드)에 서브트랜잭션을 실행
- Phase 1: 모든 워커에 PREPARE TRANSACTION '공통 GID' 전달 → 각 워커가 “준비됨”으로 응답
- 결정: 코디네이터가 성공이라 판단하면
Phase 2: 모든 워커에 COMMIT PREPARED '공통 GID'(혹은 ROLLBACK PREPARED) 브로드캐스트 - 코디네이터는 모든 워커의 결과를 확인 후 성공 처리
왜 중요?
- 여러 샤드에 분산된 쓰기를 원자적으로 만들 수 있습니다.
- 단, 지연/락/오버헤드가 증가하므로 단일 샤드 트랜잭션(코로케이션 키 활용)은 가능하면 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 |