← all posts
DEV 2026.05.02 · 14 min read Intermediate

시스템은 어떻게 실패하지 않는가

장애 전파 차단부터 분산 트랜잭션의 보상 로직, SLO 기반 에러 버짓, DR 전략의 비용 트레이드오프, 그리고 Blameless 문화까지 — 운영 가능한 시스템을 만드는 다섯 가지 원칙을 추적한다.


시스템은 반드시 실패한다. 문제는 실패를 막는 것이 아니라, 실패가 전파되지 않도록 설계하는 것이다. Circuit Breaker, Saga, SLO, DR, Postmortem — 이 다섯 개의 키워드는 서로 다른 계층에서 같은 질문에 답한다. “장애가 왔을 때 시스템은 어떻게 살아남는가?”

장애는 전파된다 — Circuit Breaker와 Bulkhead

느린 외부 서비스 하나가 전체 시스템을 멈추는 시나리오는 단순하다. 서비스 A가 B의 응답을 기다리며 스레드를 점유하고, 새 요청이 몰리면 A의 스레드 풀이 고갈된다. A에 의존하던 C도 같은 운명을 맞는다. 카스케이드 장애(cascade failure)다.

Circuit Breaker는 이 연쇄를 차단한다. CLOSED → OPEN → HALF_OPEN 세 상태를 순환하며, 실패율이 임계값을 넘으면 외부 호출을 즉시 끊어 “빠른 실패(fast fail)“를 반환한다. 스레드는 즉시 반환되고, 호출자는 빠른 응답을 받는다.

def charge_user(user_id, amount):
    return payment_cb.call(payment_service.charge, user_id, amount)
    # Circuit OPEN 상태라면 → 즉시 Exception, 외부 호출 없음

Bulkhead는 다른 축으로 격리한다. 결제 서비스 전용 스레드 풀 10개, 검색 서비스 전용 20개를 분리하면, 결제 서비스가 느려져도 검색 서비스 스레드는 영향받지 않는다. 한 격실에 물이 차도 배 전체가 침몰하지 않는 선박 설계와 같다.

트레이드오프

Circuit Breaker는 튜닝이 핵심이다. 실패율 임계값이 너무 낮으면 정상 트래픽 스파이크에도 OPEN된다. Bulkhead는 격리의 대가로 리소스 낭비가 생긴다 — 결제 풀 10개 중 9개가 놀고 있어도 검색 풀에서 빌려 쓸 수 없다. 두 패턴 모두 “복잡도를 올려 안정성을 산다”는 거래다.

분산 트랜잭션은 ACID가 아니다 — Saga 패턴

마이크로서비스에서 “주문 생성 → 결제 → 재고 감소 → 배송 등록”은 서로 다른 DB를 건드린다. 단일 트랜잭션이 불가능하다. 2PC(Two-Phase Commit)는 코디네이터 장애 시 참여자들이 잠금 상태로 블로킹되는 치명적 약점을 갖는다.

Saga는 긴 트랜잭션을 여러 로컬 트랜잭션으로 쪼개고, 각 단계에 **보상 트랜잭션(compensating transaction)**을 쌍으로 붙인다. 재고 감소가 실패하면, 이미 실행된 결제를 환불하고 주문을 취소한다.

구현 방식은 두 가지다. Choreography는 각 서비스가 이벤트를 발행하고 구독하며 자율적으로 다음 단계를 실행한다. 중앙 조율자가 없어 결합도가 낮지만, 전체 흐름을 머릿속에서 재구성하기 어렵다. Orchestration은 Saga Orchestrator가 각 서비스를 순서대로 호출한다. 흐름이 한 곳에 모여 디버깅이 쉽지만, Orchestrator가 단일 장애점이 된다.

Saga의 “최종 일관성(eventual consistency)“에는 부작용이 따른다. 네트워크 실패로 같은 결제 요청이 두 번 전달될 수 있다. 이를 막는 것이 **멱등성 키(idempotency key)**다. 클라이언트가 요청에 고유 키를 붙이면, 서버는 첫 번째 처리 결과를 저장해두고 같은 키가 오면 재처리 없이 반환한다.

# Redis에 멱등성 키 저장
SETNX "idempotency:abc123" "processing" EX=3600
# 동일 키 재요청 → 저장된 결과 반환, DB 재처리 없음

측정 없는 가용성은 없다 — SLI/SLO와 에러 버짓

“99.9% 가용성”은 목표가 아니라 계약이다. SLI(Service Level Indicator)는 실제 측정값이고, SLO(Service Level Objective)는 그 측정값이 달성해야 할 기준이다.

SLO 99.9%는 월간 44분의 **에러 버짓(error budget)**을 의미한다. 이 숫자는 두 가지 질문에 답을 준다. “지금 배포해도 되는가?” — 에러 버짓이 50% 이상 남아있다면 OK, 20% 미만이면 안정화에 집중한다. “알림을 보내야 하는가?” — 에러 버짓 소진 속도를 보면 언제 한계에 닿을지 계산할 수 있다.

Alert Fatigue는 SLO 기반 모니터링이 해결하려는 핵심 문제다. CPU > 70%에 즉시 반응하는 알림은 담당자를 지치게 만든다. “5분 평균 에러율 > 1%이고 에러 버짓 소진 속도가 1일 이내”처럼, 사용자 영향이 실제로 있을 때만, 조치 가능한 내용과 함께 알림이 와야 한다.

리전이 죽었을 때 — DR 전략의 비용 트레이드오프

재해 복구(DR)의 두 축은 RPO와 RTO다. RPO(Recovery Point Objective)는 “얼마나 오래된 데이터까지 잃어도 되는가”, RTO(Recovery Time Objective)는 “얼마나 빨리 복구되어야 하는가”다.

네 가지 전략은 이 두 축과 비용 사이의 균형점이다.

전략RPORTO비용
Backup & Restore24시간24시간+5~10%
Pilot Light1시간1~4시간15~20%
Warm Standby분 단위15~60분30~50%
Active-Active0~초즉시100% (2배)

DR 계획의 가장 흔한 실패는 훈련하지 않는 것이다. Runbook이 있어도 실제로 실행해보지 않으면, 실제 장애 시 예상치 못한 단계에서 막힌다. 연간 2회 DR 훈련은 선택이 아니라 RTO를 보장하는 유일한 방법이다.

장애 후에 남는 것 — Blameless Postmortem

장애가 끝난 후 “누가 실수했는가”를 추궁하는 조직과, “시스템의 어떤 점이 이 실수를 가능하게 했는가”를 묻는 조직은 다른 결과를 낸다. 전자는 담당자가 정보를 숨기고, 후자는 팀 전체가 배운다.

5-Why 분석은 표면 원인을 파고드는 도구다. “결제 서비스 다운” → “DB 연결 풀 고갈” → “슬로우 쿼리” → “인덱스 없는 컬럼으로 쿼리” → “배포 전 성능 검증 없음” → “CI/CD에 SQL 분석 도구 없음”. 근본 원인은 사람의 실수가 아니라 시스템의 구멍이다.

포스트모텀의 가치는 액션 아이템이다. 담당자와 기한이 없는 액션 아이템은 존재하지 않는 것과 같다. 48시간 이내 작성, 주간 추적, 팀 전체 공유 — 이 세 조건이 갖춰져야 포스트모텀이 문서가 아닌 학습이 된다.

정리

  • Circuit Breaker와 Bulkhead는 장애의 전파를 차단한다. 실패는 허용하되, 격리한다.
  • Saga는 분산 트랜잭션을 “보상 가능한 단계들”로 분해한다. ACID 대신 최종 일관성을 선택한 대가로, 멱등성과 보상 로직을 직접 설계해야 한다.
  • SLO와 에러 버짓은 가용성을 측정 가능한 숫자로 만든다. 알림 설계와 배포 결정의 기준이 된다.
  • DR 전략의 선택은 RPO/RTO와 비용의 트레이드오프다. 전략보다 훈련이 더 중요하다.
  • Blameless Postmortem은 장애를 조직 학습으로 전환하는 유일한 방법이다. 같은 장애가 반복되면, 포스트모텀이 없었다는 뜻이다.

운영 가능한 시스템은 장애가 없는 시스템이 아니라, 장애가 와도 배우고 회복하는 시스템이다.