16. 성급한 장애 감지와 플래핑으로 인한 불필요한 리밸런싱 (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을 막는 펜싱/에폭.
해설 — 성급한 장애 감지와 플래핑으로 인한 불필요한 리밸런싱 (C++)
난이도: 상
요약
타임아웃 2초를 "한 번 초과하면 즉시 죽음 → 즉시 리밸런싱"으로 판정한다. 일시적 지터 한 번에 살아있는
노드를 오판하고(false positive), 곧 돌아오면 다시 살린다. 이 플래핑이 매번 파괴적 리밸런싱(세션
이관·재접속 폭풍)을 유발한다. 또한 system_clock(벽시계)·무보호 map 사용 문제도 있다.
문제점
- [분산/false positive] 순간 임계 한 번으로 사망 확정. 히스테리시스(연속 N회 실패)나 유예 없이 (B)(C)로 직행한다. 순간 패킷 손실·스케줄링 지연·NTP 보정 한 번에 오판한다.
- [플래핑] 사망↔부활 진동. timeout 경계 근처에서 죽었다 살았다를 반복하며 매번 (C) 리밸런싱이 돌아 세션이 노드 사이를 왕복한다.
- [미복구/ split-brain] 조치 후 부활 정합성 부재. 세션을 옮긴 뒤 노드가 부활하면
alive=true로만 되돌릴 뿐, 이미 옮긴 세션의 중복 소유를 막는 장치가 없다. - [벽시계 의존]
system_clock. 판정 간격 계산에 벽시계를 쓰면 NTP 역행에 취약하다.steady_clock을 써야 한다. - [동시성]
nodes_무보호.OnHeartbeat(수신 스레드)와Tick(타이머 스레드)의 동시 접근이 UB다. 없는 노드에nodes_[nodeId]삽입은 rehash로 다른 스레드의 반복자를 무효화한다.
근본 원인
"느림"과 "죽음"이 구분 불가능한 비동기 네트워크에서, 감지를 순간 임계 한 번으로 내리고 곧바로 파괴적 조치에 직결했다. 감지에는 여유(연속 실패·안정화)가, 조치에는 확정 지연·되돌림 안전성이 필요하다.
수정안
연속 실패 카운트 + 그레이스, 사망 확정 후 재편입 쿨다운, steady_clock, 뮤텍스 보호를 도입한다.
#include <mutex>
struct NodeInfo {
std::string nodeId;
std::chrono::steady_clock::time_point lastBeat; // 단조 시계
int missCount = 0;
bool dead = false;
std::chrono::steady_clock::time_point deadSince;
};
void Tick() {
auto now = std::chrono::steady_clock::now();
std::lock_guard<std::mutex> g(mx_);
for (auto& [id, n] : nodes_) {
bool late = now - n.lastBeat > std::chrono::milliseconds(2500);
if (late) ++n.missCount; else n.missCount = 0;
if (!n.dead && n.missCount >= 3) { // 연속 3회 실패로만 확정
n.dead = true; n.deadSince = now;
rebalanceFrom_(n.nodeId); // 되돌리기 어려운 조치는 한 번만
} else if (n.dead && !late &&
now - n.deadSince > std::chrono::seconds(10)) {
n.dead = false; n.missCount = 0; // 쿨다운 후에만 재편입
reintegrate_(n.nodeId);
}
}
}
// OnHeartbeat 도 동일한 mx_ 로 보호.
더 나은 설계
- Phi Accrual Failure Detector로 하트비트 도착 분포 기반의 연속값(죽었을 확률)을 써 절벽 판정을 제거.
- 감지와 조치 분리 + 펜싱/에폭: 사망 확정 시 에폭을 올려 옛 노드의 쓰기를 무효화해 split-brain 방지.
- 점진적·레이트리밋 리밸런싱으로 이관 폭풍 억제.
면접 포인트
- 비동기 네트워크에서 "느림 vs 죽음" 구분 불가 — 타임아웃·연속실패·phi의 필요성.
- 플래핑 억제(히스테리시스·쿨다운)와 감지/조치 분리.
- 벽시계 대신 단조 시계, 그리고 무보호
unordered_map동시 접근의 위험(rehash 무효화).