← 문제로

16. 성급한 장애 감지와 플래핑으로 인한 불필요한 리밸런싱 (C#)

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

해설 — 성급한 장애 감지와 플래핑으로 인한 불필요한 리밸런싱 (C#)

난이도: 상

요약

타임아웃 2초, 하트비트 주기와 판정이 "단 한 번 초과하면 즉시 죽음 → 즉시 리밸런싱"이다. 일시적 지터 한 번에 살아있는 노드를 죽었다고 오판(false positive)하고, 곧 하트비트가 돌아오면 다시 살린다. 이 플래핑(flapping) 이 매번 파괴적 리밸런싱(세션 이관·대량 재접속)을 유발해 오히려 클러스터를 무너뜨린다.

문제점

  • [분산/false positive] 임계 한 번으로 즉시 사망 판정. GC 정지·순간 패킷 손실·NTP 보정 한 번에 now - LastHeartbeat > 2s가 참이 되면 (B)(C)로 직행한다. 히스테리시스(연속 N회 실패)나 유예가 없다.
  • [플래핑/진동] 사망↔부활 반복. 판정이 순간값이라, timeout 경계 근처에서 노드가 죽었다 살았다를 오가며 그때마다 (C) 리밸런싱이 돌아 세션이 노드 사이를 왕복한다(이관 폭풍).
  • [미복구 상태] 리밸런싱과 부활의 정합성 부재. (C)로 세션을 옮긴 뒤 노드가 곧 부활하면 Alive=true로 되돌리지만, 이미 옮겨간 세션을 어떻게 되돌릴지·중복 소유를 어떻게 막을지가 없다(split-brain 소지).
  • [벽시계 의존] DateTime.UtcNow 기반 판정. NTP 역행이 판정을 왜곡한다. 단조 시계를 써야 한다.
  • [동시성] _nodes 무보호. OnHeartbeat(수신 스레드)와 Tick(타이머)이 동시 접근한다.

근본 원인

장애 "감지"를 순간 임계값 한 번으로 내리고, 그 감지를 곧바로 "파괴적 조치(리밸런싱)"에 직결했다. 비동기 네트워크에서 "느림"과 "죽음"은 구분 불가능하므로, 감지에는 여유(연속 실패·유예·안정화)가, 조치에는 확정 지연(그레이스)과 되돌림 안전성이 필요하다.

수정안

① 연속 실패 카운트(또는 phi accrual) + 그레이스 기간으로 감지를 둔감하게, ② 사망 확정 후에도 쿨다운을 둬 플래핑을 억제, ③ 단조 시계 사용, ④ 상태 접근을 락으로 보호.

public sealed class NodeInfo
{
    public string NodeId = "";
    public long LastBeatTicks;      // Environment.TickCount64 (단조)
    public int MissCount;           // 연속 실패 횟수
    public bool Suspected;          // 의심 상태
    public bool Dead;               // 확정 사망(리밸런싱 완료)
    public long DeadSinceTicks;
}

// 예: 하트비트 1s 주기, timeout 2.5s, 연속 3회 실패 시 사망 확정, 부활은 10s 쿨다운 후 허용
public void Tick()
{
    long now = Environment.TickCount64;
    lock (_gate)
    foreach (var n in _nodes.Values)
    {
        bool late = now - n.LastBeatTicks > 2500;
        if (late) n.MissCount++;
        else { n.MissCount = 0; n.Suspected = false; }

        if (!n.Dead && n.MissCount >= 3)      // 연속 실패로만 확정
        {
            n.Dead = true; n.DeadSinceTicks = now;
            _rebalanceFrom(n.NodeId);         // 되돌리기 어려운 조치는 여기서 한 번만
        }
        else if (n.Dead && !late && now - n.DeadSinceTicks > 10_000)
        {
            n.Dead = false; n.MissCount = 0;  // 쿨다운 후에만 재편입(플래핑 억제)
            _reintegrate(n.NodeId);           // 소유권 충돌 없이 재합류시키는 절차
        }
    }
}

더 나은 설계

  • Phi Accrual Failure Detector: 하트비트 도착 간격 분포로 "죽었을 확률(phi)"을 연속값으로 산출해 임계 하나의 절벽 판정을 없앤다(Akka/Cassandra가 사용).
  • 감지와 조치의 분리 + 펜싱: 사망 확정 시 리더/에폭을 올리고 펜싱 토큰으로 옛 노드의 쓰기를 무효화해, 오판·부활 시 split-brain을 막는다.
  • 점진적 리밸런싱: 한꺼번에 전 세션을 옮기지 말고 배치·레이트리밋으로 이관 폭풍을 방지.

면접 포인트

  • 비동기 네트워크에서 "느림 vs 죽음"은 구분 불가 — 그래서 타임아웃·연속실패·phi가 필요하다.
  • 플래핑을 억제하는 히스테리시스/쿨다운, 그리고 파괴적 조치를 감지와 분리하는 이유.
  • 오판 후 노드 부활 시 split-brain을 막는 펜싱/에폭.