← 문제로

21. 비동기 복제 failover 시 확인된 쓰기 유실 (C#)

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

해설 — 비동기 복제 failover 시 확인된 쓰기 유실 (C#)

난이도: 최상

요약

프라이머리 로컬 커밋만으로 클라에 성공을 응답 (A) 하고 복제는 비동기로 뒤따른다. 프라이머리가 죽으면 아직 복제되지 않은(그러나 클라엔 이미 "성공"이라 답한) 쓰기가 승격된 복제본 (B) 에는 없어 확인된 쓰기가 유실된다. 게다가 옛 프라이머리가 복귀하면 그 노드에만 있는 커밋과 새 프라이머리의 이후 쓰기가 갈라져 divergence/split-brain 이 된다.

문제점

  • [분류: 내구성 위반(acknowledged write loss) · 복제 지연 · split-brain]
    • 증상: 결제/재화 충전이 "성공" 떴는데 장애 후 사라짐(거짓 성공). 옛 프라이머리 복귀 시 같은 계정이 노드마다 다른 잔액(데이터 분기).
    • 재현:
      1. 클라가 WriteBalance → 프라이머리 커밋 → (A) 성공 응답. 복제 ReplicateAsync 는 아직 네트워크 큐에 있음.
      2. 이 순간 프라이머리 크래시. 복제본엔 그 쓰기가 없음.
      3. Failover() 로 복제본 승격 → 그 쓰기는 영영 사라짐. 클라는 성공으로 믿음.
      4. 옛 프라이머리 복귀 시 자기만 아는 커밋 + 새 프라이머리 이후 쓰기 → 분기.
    • 근본 원인: 응답 시점의 내구성 경계가 잘못됐다. "로컬 커밋 = 내구성"으로 간주했지만 단일 노드 커밋은 그 노드 장애에 취약하다. 또 승격에 펜싱/에폭이 없어 옛 리더의 늦은 쓰기를 막지 못한다.

수정안

클라에 성공을 응답하기 전에 정족수(quorum) 복제를 확인한다(반동기/합의 복제). 승격에는 에폭(term) + 펜싱으로 옛 리더를 무력화한다.

public async Task<bool> WriteBalance(long playerId, long amount)
{
    await _primary.CommitLocal(playerId, amount);

    // 최소 정족수(예: 3노드 중 1개 이상 복제)까지 대기 후 응답
    int acks = 1; // primary
    var replicaAcks = await Task.WhenAll(
        _replicas.Select(r => r.ReplicateAndAck(playerId, amount, _term)));
    acks += replicaAcks.Count(ok => ok);

    if (acks < _writeQuorum)
        return false;           // 내구성 미확보 → 성공으로 답하지 않음(재시도/실패)
    return true;
}

public void PromoteToPrimary(long newTerm)
{
    _term = newTerm;            // 에폭 증가: 옛 리더의 term 은 이제 무효
    // 새 리더는 커밋되지 않은/뒤처진 로그를 정족수 기준으로 조정(합의 로그 복구)
}

옛 프라이머리는 복귀 시 자신의 term 이 낮음을 확인하고 팔로워로 강등, 자기만 아는 미확정 쓰기를 폐기/재조정한다(Raft/Paxos 의 로그 매칭). 저장소가 term/fencing token 을 검사해 옛 리더의 늦은 쓰기를 거부한다.

더 나은 설계

  • 합의 프로토콜(Raft) 기반 복제 로그로 "정족수 커밋 = 내구성 확정"을 정의하고, 응답을 커밋 인덱스 도달 후로 미룬다.
  • 클라 프로토콜에 멱등 키를 두어 애매한 실패(응답 유실) 시 안전 재시도.
  • 트레이드오프: 정족수 대기는 쓰기 지연↑(반동기). 지연에 민감한 값은 별도 경로, 재화·결제 같은 내구성 필수 값만 정족수 경로로.

면접 포인트

  • "로컬 커밋"과 "확정된(durable) 커밋"의 차이, 응답 시점의 내구성 경계.
  • 비동기 복제 failover 의 확인된 쓰기 유실(RPO>0)과 반동기/정족수로의 해결.
  • 승격 시 에폭/펜싱/로그 매칭으로 split-brain 을 막는 원리.