25. 수용 제어·부하 차단 부재로 인한 메타안정 장애 (C#)
난이도 상해설 — 수용 제어·부하 차단 부재로 인한 메타안정 장애 (C#)
난이도: 상
요약
서버가 들어오는 요청을 무제한 큐에 쌓고(수용 제어 없음), 워커는 데드라인이 지난 요청까지 처리한다(헛일). 부하 스파이크가 오면 큐가 폭증해 지연이 데드라인을 넘고, 클라이언트 재시도가 새 부하로 유입되는 양의 피드백 루프가 형성된다. 그 결과 스파이크가 사라진 뒤에도 시스템이 과부하 상태에 갇히는 메타안정 장애(metastable failure) 가 된다.
문제점
분류 태그: admission control/load shedding 부재 / 데드라인 무시 헛일 → 메타안정 과부하
증상: 순간 부하 후 지연이 계속 높게 유지되고, 부하 그래프상 트래픽이 정상으로 돌아와도 큐 길이·지연이 내려오지 않는다. CPU 는 100%인데 유효 처리량(데드라인 내 응답)은 오히려 감소한다(goodput 붕괴).
재현 시나리오(피드백 루프):
- (A) 스파이크로 도착률 > 처리율 → 큐가 무제한으로 자란다.
- 큐 대기시간이 데드라인을 초과 → 클라이언트가 응답을 버리고 재시도 → 도착률이 더 올라간다.
- (B) 워커는 이미 데드라인이 지나 아무도 기다리지 않는 요청까지 처리 → CPU를 "버려질 응답"에 쓴다. 유효 처리량이 줄어 큐가 더 밀린다.
- 스파이크가 끝나 원래 도착률로 돌아와도, 쌓인 백로그 + 재시도가 처리율을 계속 초과 → 시스템이 과부하 평형에 고착. 외부 개입(트래픽 차단, 재시작) 없이는 회복 불가.
근본 원인: (1) 큐에 상한과 거절 정책이 없어 과부하가 흡수되지 않고 증폭된다. (2) 데드라인을 무시해 "이미 실패한 일"에 자원을 낭비하는 것이 goodput 을 무너뜨린다. 이 둘이 재시도 트래픽과 결합해 메타안정 상태를 만든다.
수정안
유한 큐 + 부하 차단(가득 차면 빠른 거절) + 데큐 시 데드라인 검사(만료 요청 폐기)로 피드백 루프를 끊는다.
public sealed class RequestServer
{
private readonly BlockingCollection<Request> _queue;
public RequestServer(int workers, int capacity)
{
_queue = new BlockingCollection<Request>(new ConcurrentQueue<Request>(), capacity);
for (int i = 0; i < workers; i++)
new Thread(WorkerLoop) { IsBackground = true }.Start();
}
// 가득 차면 즉시 거절 → 클라는 백오프. 큐가 무한히 자라지 않는다.
public bool Submit(Request req)
{
return _queue.TryAdd(req); // false = 부하 차단(load shedding)
}
private void WorkerLoop()
{
foreach (var req in _queue.GetConsumingEnumerable())
{
if (DateTime.UtcNow >= req.DeadlineUtc) // 만료 요청은 버림(헛일 방지)
continue;
req.Work();
}
}
}
추가로: 큐 대기시간이 데드라인의 일정 비율을 넘으면 애초에 거절(예: 예상 대기 > 데드라인 → 즉시 shed), 클라이언트는 지수 백오프 + 지터 + 재시도 예산(retry budget) 으로 재시도 폭증을 억제한다.
더 나은 설계
- 동시성 제한 + 대기열 상한: 워커 수와 큐 용량을 용량에 맞춰 정하고, 초과분은 429/재시도-불가로 빠르게 거절.
- CoDel/adaptive LIFO 같은 큐 규율: 오래 기다린 요청을 우선 폐기(어차피 만료)해 최신 요청의 goodput 을 지킨다. LIFO 는 과부하 시 일부 요청을 빨리 끝내 전체 붕괴를 막는다.
- 재시도 거버넌스: 서버가
Retry-After를 주고, 클라이언트는 재시도 예산·서킷브레이커로 증폭을 차단. - 부하 자동 완화: 부하 신호(큐 길이·지연)에 따라 표본 비율로 shed(부분 저하)해 완전 붕괴 대신 우아한 성능 저하.
- 트레이드오프: 거절은 사용자 경험을 해치지만, 무제한 수용은 "모두 실패"로 이어진다. 부분 거절이 전체 붕괴보다 낫다.
참고: 이 문제는 요청 경로의 데드라인 전파·재시도 증폭(별도 문제)과 달리, 서버 자신의 수용 제어·부하 차단 부재로 트리거가 사라진 뒤에도 과부하가 지속되는 메타안정 평형이 핵심이다.
면접 포인트
- 메타안정 장애의 정의(트리거 제거 후에도 지속되는 과부하 평형)와, 이를 만드는 양의 피드백(재시도·헛일)을 설명.
- load shedding/admission control 이 왜 "친절한 거절"로 전체 goodput 을 지키는가.
- 데드라인 지난 요청을 버리는 것이 왜 처리량이 아니라 유효 처리량(goodput)을 높이는가.
해설 — 수용 제어·부하 차단 부재로 인한 메타안정 장애 (C++)
난이도: 상
요약
서버가 요청을 무제한 큐에 쌓고(수용 제어 없음), 워커는 데드라인이 지난 요청까지 처리한다(헛일). 부하 스파이크 시 큐 대기시간이 데드라인을 넘고 클라이언트 재시도가 새 부하로 되먹임되어, 스파이크 종료 후에도 과부하가 지속되는 메타안정 장애가 된다.
문제점
분류 태그: admission control/load shedding 부재 / 데드라인 무시 헛일 → 메타안정 과부하
증상: 순간 부하 후 지연·큐 길이가 내려오지 않고 고착. CPU 포화인데 데드라인 내 응답(goodput)은 감소. 무제한 std::queue 가 계속 커져 메모리 압박까지 유발.
재현(피드백 루프):
- (A) 도착률 > 처리율 →
queue_무한 성장. - 대기시간 > deadline → 클라 재시도 → 도착률 상승.
- (B) 워커가 만료 요청까지 처리 → CPU 를 버려질 작업에 소모 → goodput 하락 → 큐 더 밀림.
- 스파이크 종료 후에도 백로그+재시도가 처리율 초과 → 과부하 평형에 고착(외부 개입 없이는 회복 불가).
근본 원인: 상한 없는 큐 + 거절 정책 부재로 과부하가 흡수되지 않고 증폭되며, 데드라인 무시가 goodput 을 무너뜨린다. C++에서는 무제한 큐가 OOM 위험까지 더한다.
수정안
유한 큐 + 가득 차면 거절 + 데큐 시 데드라인 검사.
class RequestServer {
public:
RequestServer(int workers, std::size_t capacity) : cap_(capacity) {
for (int i = 0; i < workers; ++i) pool_.emplace_back([this]{ workerLoop(); });
}
// 가득 차면 즉시 false → 클라는 백오프. 큐가 무한히 자라지 않는다.
bool submit(Request req) {
{
std::lock_guard<std::mutex> lk(m_);
if (queue_.size() >= cap_) return false; // load shedding
queue_.push(std::move(req));
}
cv_.notify_one();
return true;
}
private:
void workerLoop() {
for (;;) {
Request req;
{
std::unique_lock<std::mutex> lk(m_);
cv_.wait(lk, [this]{ return !queue_.empty(); });
req = std::move(queue_.front());
queue_.pop();
}
if (std::chrono::steady_clock::now() >= req.deadline) // 만료 폐기
continue;
req.work();
}
}
std::mutex m_;
std::condition_variable cv_;
std::queue<Request> queue_;
std::vector<std::thread> pool_;
std::size_t cap_;
};
추가로: 예상 대기(큐 길이/처리율)가 데드라인을 넘으면 애초에 거절, 클라이언트는 지수 백오프+지터+재시도 예산으로 증폭 억제.
더 나은 설계
- 동시성 제한 + 큐 상한을 용량에 맞춰 설정, 초과분은 빠르게 거절.
- CoDel/adaptive LIFO 큐 규율로 오래된(어차피 만료) 요청을 우선 폐기해 최신 요청 goodput 보존.
- 재시도 거버넌스:
Retry-After, 클라 서킷브레이커·재시도 예산. - 우아한 성능 저하: 부하 신호에 따라 표본 비율 shed.
- 트레이드오프: 거절의 UX 비용 vs 무제한 수용의 전면 붕괴. 부분 거절이 우월.
참고: 요청 경로 데드라인 전파·재시도 증폭(별도 문제)과 달리, 여기서는 서버 자신의 수용 제어·부하 차단 부재로 트리거 제거 후에도 과부하가 지속되는 메타안정 평형이 핵심이다.
면접 포인트
- 메타안정 장애의 정의와 양의 피드백(재시도·헛일) 메커니즘.
- 유한 큐+거절이 전체 goodput 을 지키는 원리, LIFO/CoDel 이 과부하에서 유리한 이유.
- 무제한 큐가 C++ 서버에서 지연뿐 아니라 OOM 위험까지 부르는 점.