← 문제로

19. 2단계 커밋 코디네이터 장애와 in-doubt 블로킹 (C#)

난이도 최상
내 리뷰 · C#
해설 · C#

해설 — 2단계 커밋 코디네이터 장애와 in-doubt 블로킹 (C#)

난이도: 최상

요약

2PC 코디네이터가 커밋 결정을 내구성 있게(durable) 기록하지 않고, 참여자에게는 타임아웃/종결 프로토콜이 없다. 그래서 (1) 참여자가 Prepare 후 찬성한 상태에서 코디네이터가 죽으면 참여자는 커밋/취소를 알 수 없어 자원을 무기한 잠근 채 멈춘다(in-doubt 블로킹). (2) 코디네이터가 (B)~(C) 사이(한쪽만 Commit)에서 죽고 재시작하면, 결정 로그가 없어 자신이 무엇을 결정했는지 몰라 한쪽만 반영된 불일치가 영구화된다.

문제점

  • [내구적 결정 로그 부재] 코디네이터 크래시 → 결정 유실/불일치

    • 증상: 두 Prepare 가 찬성((A))한 뒤 (B) 직전/직후에 코디네이터가 죽는다. 재시작해도 "커밋하기로 했었나?"를 알 방법이 없다(로그 없음). _a 는 Commit 됐는데 _b 는 안 된 상태로 방치.
    • 재현: (B)(C) 전에 코디네이터 프로세스 강제 종료 → 아이템이 한 샤드에서만 이동(복제 또는 소실).
    • 근본원인: 2PC 의 핵심 규칙 — 코디네이터는 어떤 Commit 도 보내기 전에 "commit" 결정을 WAL 에 durable 하게 써야 한다. 그래야 재시작 후 재전송으로 완결할 수 있다. 그 로그가 없다.
  • [참여자 종결 프로토콜 부재] Prepare 후 무한 대기 → 자원 영구 잠금

    • 증상: 참여자는 찬성표를 던진 순간부터 커밋/취소가 올 때까지 자원을 잠근다. 코디네이터가 사라지면 참여자는 혼자서는 안전하게 결정할 수 없다(코디네이터가 이미 커밋했을 수도, 취소했을 수도 있어 임의 결정은 불일치 위험). 잠금이 영구화되어 해당 아이템/캐릭터가 먹통이 된다.
    • 근본원인: 참여자에게 타임아웃 후 코디네이터/동료에게 결정을 물어보는 협력적 종결(cooperative termination) 프로토콜과, 트랜잭션 상태의 내구적 보존이 없다.
  • [근본 한계] 2PC 는 본질적으로 블로킹

    • 찬성 후 코디네이터가 도달 불가면 참여자는 안전하게 진행/취소를 결정할 수 없다. 이는 구현 버그가 아니라 2PC 프로토콜의 근본 성질이다.

수정안

2PC 를 제대로 하려면 내구적 로깅 + 복구 + 종결 프로토콜을 갖춰야 한다.

  • 코디네이터: 모든 Prepare 찬성 확인 후, Commit 을 하나라도 보내기 전에 decision=commit 을 durable 로그에 쓴다(force write). 재시작 시 로그를 읽어 미완결 트랜잭션의 Commit(또는 Abort)을 멱등하게 재전송한다. 참여자 Commit/Abort 는 재전송에 대비해 멱등해야 한다.
  • 참여자: Prepare 시 트랜잭션 상태(잠금 대상, 투표)를 durable 로 남긴다. 결정 대기가 타임아웃되면 코디네이터(또는 결정 로그/동료 참여자)에게 상태를 조회해 종결한다. 스스로 커밋/취소를 임의 결정하지 않는다.
public async Task<bool> Execute(TxOp op)
{
    if (!await _a.Prepare(op) || !await _b.Prepare(op)) { await AbortAll(op); return false; }

    await _log.WriteDecisionDurably(op.TxId, Decision.Commit);   // 커밋 전 결정을 먼저 내구 기록
    await CommitAllWithRetry(op);   // 멱등 재시도 — 크래시 후 재시작에서도 완결
    await _log.MarkComplete(op.TxId);
    return true;
}
// 재시작 시: 로그의 미완결 트랜잭션에 대해 결정대로 Commit/Abort 재전송

더 나은 설계

  • 분산 트랜잭션을 피한다(사가): 크로스-샤드 교환을 2PC 대신 보상 트랜잭션 기반 사가로 설계 — 각 단계는 로컬 커밋 + 멱등, 실패 시 보상. 원자성 대신 최종 일관성을 택해 블로킹을 없앤다(이 카테고리의 사가 문제와 짝). 게임 아이템 교환에선 보통 이쪽이 실용적.
  • 비블로킹 합의 커밋: Paxos Commit/3PC 로 코디네이터 단일 장애점을 제거하면 블로킹을 완화할 수 있으나 복잡·고비용.
  • 잠금 최소화: Prepare 잠금 시간을 짧게, 리스(lease) 기반으로 자동 만료 + 안전한 재해소(단, 임의 취소가 불일치를 안 만들도록 결정 로그와 연동).

면접 포인트

  • 2PC 는 원자성을 주지만 가용성을 희생한다(블로킹 프로토콜). 찬성 후 코디네이터가 죽으면 참여자는 혼자 결정할 수 없어 자원이 잠긴다 — 이는 근본 한계다.
  • 올바른 2PC 의 필수 요소: 커밋 전 코디네이터의 내구적 결정 로그, 멱등 재전송 복구, 참여자의 내구 상태 + 종결 프로토콜.
  • 실무에선 크로스-샤드/서비스 경계에서 2PC 대신 사가+멱등+보상으로 가는 이유(블로킹 회피, 운영 단순성)와 그 대가(최종 일관성)를 설명할 수 있어야 한다.