개발
[아고라] 장애는 왜 커지는가: 장애 전파와 복구 전략
| 서론
안녕하세요, 팡일입니다.
서비스를 운영하다 보면 “한 군데만 문제가 생겼는데 왜 전체가 죽지?”라는 상황을 자주 마주하게 됩니다.
실제로 많은 장애는 문제 자체보다 ‘확산되는 과정’에서 더 크게 터집니다.
이 글에서는 장애가 어떻게 퍼지는지 이해하고, 그 전파를 어떻게 막을 수 있는지까지 흐름 중심으로 정리해보겠습니다.
| 장애 전파란?
1) 장애 전파의 정의와 기본 개념
장애 전파는 하나의 장애가 시스템 전체로 확산되는 과정을 의미합니다.
겉으로 보면 단순히 “어디 한 곳이 고장 났다”로 보일 수 있지만, 실제로 서비스에 큰 영향을 주는 것은 그 자체가 아니라 그 장애가 어떻게 퍼지느냐입니다.
예를 들어, 하나의 API가 느려지는 문제는 그 자체로는 작은 이슈일 수 있습니다. 하지만 그 API를 호출하는 다른 서비스들이 연쇄적으로 영향을 받기 시작하면, 결국 전체 시스템이 느려지거나 멈추는 상황으로 이어지게 됩니다.
이처럼 장애는 “발생”보다 “확산”의 관점에서 바라봐야 합니다.
이 과정을 단계로 나누어 보면 다음과 같이 정리할 수 있습니다.
단계 | 설명 |
Failure (실패) | 하나의 컴포넌트에서 장애 발생 |
Propagation (번식) | 다른 서비스로 영향 전파 |
Cascade (종속) | 연쇄적으로 시스템 전체 장애 발생 |
즉, 작은 문제 하나가 단일 지점에 머무는 것이 아니라, 도미노처럼 연쇄적으로 퍼지면서 시스템 전체를 무너뜨리는 구조가 되는 것입니다.
여기까지가 “무엇이 장애 전파인가”에 대한 이야기라면, 다음으로는 “왜 이런 일이 발생하는가”를 이해해야 합니다.
2) 장애는 왜 전파되는가?
장애가 커지는 이유는 대부분 특정 코드의 문제가 아니라, 시스템의 구조적인 설계 방식에서 비롯됩니다.
현대의 서비스는 여러 컴포넌트와 서비스가 서로 연결된 형태로 구성되어 있습니다. 이 연결이 많아질수록 유연해지기도 하지만, 동시에 장애가 퍼질 수 있는 경로도 함께 늘어나게 됩니다.
대표적인 원인은 다음과 같습니다.
서비스 간 강결합 구조 (Tight Coupling): 하나의 서비스 변경이나 장애가 다른 서비스에 직접적인 영향을 주는 구조
동기 호출 기반 API 체인: 앞 단계의 응답을 기다리는 구조로, 하나라도 지연되면 전체 흐름이 함께 느려지는 방식
DB, Cache 등 공유 자원 의존성: 여러 서비스가 동일한 자원을 사용하면서, 해당 자원 장애 시 동시에 영향을 받는 구조
단일 장애 지점(SPOF, Single Point of Failure): 하나의 컴포넌트가 전체 시스템의 정상 동작을 좌우하는 구조
이 중에서도 특히 위험한 구조가 동기 호출 체인입니다.
아래의 구조에서는 각 서비스가 이전 단계의 응답을 기다리는 형태이기 때문에, 하나라도 느려지는 순간 그 영향이 그대로 위로 전달됩니다.
Frontend → API Gateway → Service A → Service B → DB예를 들어 DB가 느려지면,
Service B는 응답을 기다리게 되고
Service A는 다시 B를 기다리게 되며
결국 Gateway와 Frontend까지 영향을 받게 됩니다
이처럼 단일 병목이 전체 시스템의 응답 지연으로 이어지는 구조가 만들어집니다. 결국 장애 전파는 우연히 발생하는 것이 아니라, 의존성과 연결 구조 속에서 자연스럽게 발생하는 현상이라고 볼 수 있습니다.
이제 이런 구조 위에서 장애가 실제로 어떻게 퍼지는지 살펴보겠습니다.
3) 실제 장애 전파 패턴
장애는 무작위로 퍼지는 것이 아니라, 일정한 패턴을 가지고 반복적으로 나타나는 특징이 있습니다. 이 패턴을 이해하면 장애 상황을 더 빠르게 인지하고 대응할 수 있습니다.
1) 도미노 패턴
: 가장 직관적인 형태로, 하나의 서비스 장애가 다음 서비스로 순차적으로 전파되는 구조입니다.
A → B → C → D앞 단계의 문제가 해결되지 않는 한, 뒤 단계는 계속 영향을 받을 수밖에 없습니다. 따라서 이 패턴에서는 “가장 앞단의 장애”를 빠르게 찾는 것이 핵심입니다.
2) 트래픽 폭증 패턴 (Traffic Amplification)
: 이 패턴은 특히 위험합니다. 왜냐하면 복구를 시도하는 과정에서 장애가 더 커지기 때문입니다.
요청 실패
자동 retry 발생
다시 실패 → 또 retry 반복
이 과정이 반복되면서 요청 수가 기하급수적으로 증가하게 되고, 결국 정상적으로 동작하던 서비스까지 과부하로 무너지게 됩니다.
즉, 잘못 설계된 retry는 복구 수단이 아니라 장애를 증폭시키는 원인이 될 수 있습니다.
3) 자원 고갈 패턴
: 시스템이 처리 가능한 한계를 넘어서면서 발생하는 패턴입니다.
Thread pool 고갈
DB connection 부족
CPU / Memory saturation
이 상태에 들어가면 요청을 처리할 수 없게 되고, 이미 들어온 요청들까지 대기 상태로 쌓이게 됩니다. 결과적으로 시스템은 점점 느려지다가 결국 응답하지 못하는 상태로 이어집니다.
4) 백프레셔 실패
: 정상적인 시스템은 “처리 가능한 만큼만 요청을 받아야” 합니다. 하지만 이 제어가 없으면 처리하지 못하는 요청이 계속 쌓이게 됩니다.
요청이 계속 쌓이게 되면, 큐가 계속 증가하고, 응답 시간이 길어지며, 결국 시스템 전체가 밀려버리는 상황이 발생합니다.
이러한 상황은 특히 트래픽이 순간적으로 증가할 때 자주 나타납니다. 이처럼 장애는 단순히 퍼지는 것이 아니라, 특정한 패턴을 따라 증폭되며 확산됩니다.
그렇다면 실제 서비스에서는 어떤 형태로 나타날까요?
4) 대표적인 장애 전파 사례
실제 서비스에서는 앞서 설명한 패턴들이 복합적으로 나타나는 경우가 많습니다.
시작 장애 | 전파 결과 |
DB 장애 | API 타임아웃 → 프론트 응답 실패 |
외부 API 장애 | fallback 없음 → 전체 서비스 중단 |
캐시 미스 | DB 과부하 → 전체 장애 |
여기서 중요한 포인트는, 초기 장애보다 그 이후의 연쇄 반응이 더 큰 문제를 만든다는 점입니다.
예를 들어 캐시 미스는 흔히 발생할 수 있는 상황입니다. 하지만 이 상황에서 모든 요청이 동시에 DB로 몰리게 되면, 결국 DB가 버티지 못하고 전체 시스템 장애로 이어지게 됩니다.
이처럼 실제 장애는 하나의 이벤트가 아니라, 여러 단계가 연결되며 점점 커지는 과정으로 나타납니다.
5) 장애 전파를 키우는 안티패턴
마지막으로, 장애를 더 크게 만드는 설계 방식도 존재합니다. 이러한 안티패턴은 의도와는 다르게 시스템을 더욱 취약하게 만듭니다.
대표적인 예는 다음과 같습니다.
무제한 retry (특히 백오프 전략 없음)
timeout 설정 부재
circuit breaker 미적용
모든 요청을 반드시 성공시키려는 설계
이러한 공통된 특징은 하나입니다. “실패를 인정하지 않는다”는 점입니다. 하지만 시스템 설계에서 중요한 것은 실패를 없애는 것이 아니라,실패를 얼마나 빠르게, 안전하게 통제하느냐입니다.
앞에서 살펴본 장애 전파의 대부분은 이러한 안티패턴에서 시작되는 경우가 많습니다.
“실패를 허용하지 않는 시스템은 결국 전체가 실패한다.”
이 문장은 단순한 문장이 아니라, 장애 전파를 이해하는 핵심 원칙이라고 볼 수 있습니다.
| 장애 전파의 복구 전략이란?
앞에서 살펴본 것처럼, 장애는 대부분 “발생 자체”보다 “확산 과정”에서 더 크게 문제가 됩니다. 따라서 복구 전략은 단순히 장애를 고치는 것이 아니라, 장애가 퍼지지 않도록 통제하는 방향으로 설계되어야 합니다.
이제부터는 “장애가 발생했을 때 시스템을 어떻게 지켜낼 것인가”에 대한 전략을 단계적으로 살펴보겠습니다.
1) 기본 원칙: 장애는 막는 게 아니라, 퍼지는 걸 막는다
현실적으로 모든 장애를 사전에 방지하는 것은 불가능합니다. 외부 API, 네트워크, 인프라, 사용자 트래픽 등 다양한 변수들이 존재하기 때문입니다.
따라서 시스템 설계의 목표는 다음과 같이 바뀌어야 합니다.
장애는 반드시 발생한다
중요한 것은 장애가 어디까지 퍼지느냐
빠르게 실패(Fail Fast)하고 확산을 막는다
특히 “Fail Fast”는 매우 중요한 개념입니다.
오래 버티는 시스템이 좋은 것이 아니라, 빠르게 실패해서 다른 영역으로 영향을 주지 않는 시스템이 더 안정적입니다. 예를 들어, 10초 동안 기다리다가 실패하는 것보다, 1초 만에 실패하고 fallback을 사용하는 것이 전체 시스템에는 훨씬 안전합니다.
이 원칙을 이해하면, 이후의 모든 전략이 자연스럽게 연결됩니다.
2) 격리: 장애를 한 곳에 가둔다
장애 확산을 막기 위한 첫 번째 전략은 격리(Isolation)입니다.
핵심은 간단합니다.
“문제가 생겨도, 그 문제가 다른 영역으로 넘어가지 않게 만든다.”
이를 위해 다음과 같은 방식이 사용됩니다.
서비스 간 경계 분리 (Microservice, BFF 등)
리소스 분리 (Bulkhead 패턴)
예를 들어, 하나의 기능이 장애를 일으켰을 때 그 기능만 영향을 받고, 나머지 기능은 정상 동작하도록 만드는 것이 목표입니다.
조금 더 직관적으로 보면 다음과 같습니다.
구조 | 결과 |
격리 없음 | 하나의 장애 → 전체 시스템 영향 |
격리 있음 | 하나의 장애 → 해당 기능만 영향 |

https://en.wikipedia.org/wiki/Bulkhead_(partition)
이 개념은 실제로 “배의 격벽(Bulkhead)”에서 유래했습니다. 한 칸에 물이 들어와도, 다른 칸까지 침수되지 않도록 막는 구조입니다.
시스템도 동일하게, 문제가 발생해도 전체가 무너지지 않도록 구획을 나누는 것이 중요합니다.
3) 제어: 시스템이 버틸 수 있게 만든다
격리만으로는 충분하지 않습니다. 트래픽이 몰리는 상황에서는 시스템이 감당할 수 있는 수준으로 흐름을 조절하는 것이 필요합니다.
이를 위해 사용하는 전략이 바로 “제어(Control)”입니다.
대표적인 방법은 다음과 같습니다.
Rate Limiting: 초당 요청 수 제한
Throttling: 처리 속도 제한
Queue: 요청을 잠시 저장하여 순차 처리
Backpressure: 처리 가능한 만큼만 요청을 받기
이 단계는 한 문장으로 정리할 수 있습니다.
“들어오는 요청을 모두 처리하려고 하지 않는다.”
이 개념은 직관적으로 보면 다음과 같은 차이가 있습니다.
상황 | 제어 없음 | 제어 있음 |
트래픽 급증 | 모든 요청 처리 시도 → 과부하 | 일부 제한 → 안정 유지 |
처리 속도 초과 | 시스템 붕괴 | 점진적 지연 |
특히 Backpressure는 매우 중요한 개념입니다. 시스템이 처리할 수 있는 속도보다 빠르게 요청이 들어오면, 받는 쪽에서 스스로 제한을 거는 구조가 필요합니다.
이렇게 제어가 들어가면, 장애 상황에서도 시스템은 “무너지지 않고 버티는 상태”를 유지할 수 있습니다.
4) 차단 & 완화: 터져도 서비스는 유지한다
이 단계는 실제 서비스 운영에서 가장 중요한 부분입니다. 왜냐하면 현실에서는 장애를 완전히 막을 수 없기 때문입니다.
따라서 목표는 다음과 같이 바뀝니다.
“일부 기능이 죽더라도, 전체 서비스는 살아있게 만든다.”
이를 위해 사용하는 핵심 전략은 다음과 같습니다.
1) 핵심 전략
Timeout: 오래 기다리지 않고 빠르게 실패
Retry: 재시도하되, 지수 백오프 기반으로 제한
Circuit Breaker: 일정 실패율 이상이면 호출 자체를 차단
Fallback: 기본값, 캐시, 대체 응답 제공
이 전략들은 각각 독립적으로 보이지만, 실제로는 함께 동작합니다.
예를 들어,
요청 실패
Timeout 발생
Retry 수행 (제한된 횟수)
계속 실패하면 Circuit Breaker Open
이후 요청은 Fallback으로 처리
이 흐름을 통해 장애는 “확산”이 아니라 “제한된 영역”에서 멈추게 됩니다.
2) 적용 전/후 비교
상황 | 대응 없음 | 대응 있음 |
외부 API 장애 | 전체 기능 중단 | 일부 기능만 제한 |
DB 느림 | 모든 요청 대기 | 빠른 실패 + fallback |
트래픽 폭증 | 시스템 다운 | 제한된 처리 유지 |
이 차이는 단순히 성능 문제가 아니라, 서비스의 생존 여부를 결정하는 차이입니다.
결국 이 단계의 핵심은 하나입니다.
“완벽한 서비스가 아니라, 살아있는 서비스를 만든다.”
5) 관측: 빠르게 감지하고 끊는다
앞에서 설명한 모든 전략은 “빠르게 대응할 수 있을 때” 의미가 있습니다. 아무리 좋은 설계라도, 장애를 늦게 발견하면 이미 전파된 이후일 가능성이 높습니다.
따라서 마지막으로 필요한 것은 관측성(Observability)입니다.
Monitoring: CPU, latency, error rate 등 메트릭 수집
Logging: 장애 원인 파악
Tracing: 요청 흐름 추적 (어디서 병목이 발생했는지)
Alerting: 임계치 초과 시 즉시 알림
이 모든 것은 다음 흐름을 빠르게 만들기 위한 것입니다.
감지 → 차단 → 완화 → 복구이 사이클이 빠를수록 장애는 “사고”가 아니라 “이벤트” 수준으로 끝나게 됩니다. 반대로 이 과정이 느리면, 작은 장애도 금방 전체 서비스 장애로 확산됩니다.
| 마무리 정리
1) 격리~관측
단계 | 목적 |
격리 | 장애를 한 곳에 가둔다 |
제어 | 시스템이 버틸 수 있게 만든다 |
차단 & 완화 | 일부 기능만 희생하고 전체를 살린다 |
관측 | 빠르게 감지하고 대응한다 |
결국 복구 전략의 본질은 하나입니다.
“장애를 없애는 것이 아니라, 장애를 통제하는 것이다.”
이 관점으로 시스템을 바라보면,설계 방식 자체가 완전히 달라지게 됩니다.
2) 핵심 인사이트 3가지
“이처럼 장애는 ‘발생’보다 ‘확산’의 관점에서 바라봐야 합니다.”
“장애는 대부분 특정 코드의 문제가 아니라, 시스템의 구조적인 설계 방식에서 비롯됩니다.”
“장애를 없애는 것이 아니라, 장애를 통제하는 것이다.”
| 결론
안정적인 시스템은 “에러가 없는 시스템”이 아니라, 에러가 발생해도 무너지지 않는 시스템입니다.
특히 서비스가 커질수록 장애 자체보다 장애 전파를 어떻게 다루느냐가 훨씬 중요한 문제가 됩니다.
앞으로 시스템을 설계할 때는 이렇게 한 번 생각해보시면 좋습니다.
“이 기능이 실패하면, 어디까지 영향을 줄까?”
이 질문 하나가, 시스템의 안정성을 완전히 바꿀 수 있습니다.