한양과학기술고등학교

에듀테크 프로젝트 대시보드

2-페이즈 커밋 (Two-Phase Commit)

2-페이즈 커밋 (Two-Phase Commit)

은행 송금은 내 계좌 −1만원 과 친구 계좌 +1만원 이 둘 다 반영되거나 둘 다 취소돼야 한다. 그런데 두 계좌가 서로 다른 데이터베이스 서버에 있다면? 네트워크는 언제든 끊길 수 있는데 어떻게 "전부 아니면 전무(all-or-nothing)" 를 보장할까?
2-페이즈 커밋(2PC)은 코디네이터(조정자) 한 명이 여러 참가자(participant) 에게 준비됐니? → 커밋해! 두 라운드로 물어보며 원자적 커밋을 맞추는 프로토콜이다.

인터랙티브 시뮬레이터

대기 중 — 트랜잭션 시작 전
COORDINATOR
조정자 C
idle

두 단계, 그리고 각자의 로그

1단계 · 투표 (Voting / Prepare)

조정자가 모든 참가자에게 PREPARE 를 보낸다. 참가자는 변경을 실제로 커밋하지 않고 undo/redo 로그에 적고 자원을 잠근 뒤, 커밋 가능하면 VOTE-COMMIT(YES), 불가능하면 VOTE-ABORT(NO) 로 답한다. YES를 보낸 참가자는 이제 스스로 되돌릴 수 없는 "준비(prepared)" 상태 — 조정자의 지시를 기다린다.

2단계 · 커밋 (Commit / Completion)

조정자는 모두 YES 면 자기 로그에 GLOBAL-COMMIT 를 먼저 적고(이 순간 결정이 확정됨) 전원에게 COMMIT 를 보낸다. 하나라도 NO거나 응답이 없으면 GLOBAL-ABORT. 참가자는 지시대로 커밋/롤백하고 잠금을 풀고 ACK 를 보낸다.

2PC의 약점: 블로킹

시나리오 ④를 돌려보자. 참가자들이 모두 YES를 보내 prepared 상태 로 잠금을 쥔 채 기다리는데, 결정을 내려야 할 조정자가 죽으면 참가자들은 아무것도 할 수 없다. 커밋해도 될지 어보트해야 할지 혼자서는 알 수 없기 때문이다(조정자만 다른 참가자들의 투표 결과를 안다). 조정자가 되살아날 때까지 잠금은 계속 잡혀 있고 다른 트랜잭션은 멈춘다. 이것이 2PC가 블로킹 프로토콜로 불리는 이유다.

완화책: 조정자를 단일 서버가 아니라 Raft/Paxos 로 복제하기 · 참가자끼리 물어보는 3-페이즈 커밋(3PC) · 타임아웃 후 동료에게 결정 상태를 물어보는 협력적 종료 프로토콜. 그래도 근본적으로 동기적 원자 커밋 + 완벽한 비블로킹 을 네트워크 분단 상황에서 동시에 얻을 수는 없다.

항목2PCRaft (합의)
푸는 문제여러 노드의 원자적 커밋하나의 값에 대한 복제 합의
조정자 다운참가자 블로킹 (잠금 유지)새 리더 선출 후 계속 진행
다수결❌ 만장일치 필요 (1명 NO → abort)✅ 과반수면 충분
흔한 용도분산 트랜잭션(XA), 마이크로서비스 saga 대안etcd, ZooKeeper, 분산 KV 스토어

의사코드

# ── 조정자(Coordinator) ──
def commit_transaction(participants, txn):
    log("BEGIN", txn)

    # 1단계: 투표 요청
    votes = []
    for p in participants:
        try:
            votes.append(p.send("PREPARE", txn, timeout=5))
        except Timeout:
            votes.append("NO")          # 무응답 = 반대로 간주

    # 2단계: 결정 후 통보
    if all(v == "YES" for v in votes):
        log("GLOBAL-COMMIT", txn)   # ★ 결정이 확정되는 지점
        decision = "COMMIT"
    else:
        log("GLOBAL-ABORT", txn)
        decision = "ABORT"

    for p in participants:
        retry_forever(lambda: p.send(decision, txn))   # ACK 받을 때까지

# ── 참가자(Participant) ──
def on_prepare(txn):
    if can_commit(txn):
        write_undo_redo_log(txn); lock(txn.rows)
        log("PREPARED", txn)          # 이제 혼자 못 되돌림
        return "YES"
    return "NO"                        # 바로 로컬 롤백 가능

def on_decision(kind, txn):
    if kind == "COMMIT": apply(txn)
    else:                  undo(txn)
    unlock(txn.rows); log(kind, txn); return "ACK"

모형은 개념 이해용으로 단순화했다. 실제 XA/2PC 구현은 조정자 로그 강제 flush 시점, presumed-abort/presumed-commit 최적화, 참가자 복구 시 in-doubt 트랜잭션 조회 등 더 많은 세부를 다룬다.