웹서비스 장애는 특정 서버 하나의 문제가 아니라 의존 서비스의 지연, 무제한 재시도, 연결 고갈이 겹치며 전체 시스템으로 번지는 경우가 많습니다. 핵심은 실패한 요청을 끝없이 붙잡지 않고 빠르게 끊어 부하를 제한하는 것입니다.
📌 핵심 5줄 요약
1. 장애 전파는 호출 관계를 따라 지연과 오류가 연쇄적으로 확대되는 현상입니다.
2. 타임아웃이 없으면 느린 의존 서비스가 호출 스레드와 연결을 계속 점유합니다.
3. 재시도는 일시적 오류에만 제한적으로 적용하고 백오프와 지터를 함께 사용해야 합니다.
4. 서킷 브레이커는 실패가 임계치를 넘을 때 호출을 잠시 차단해 장애를 격리합니다.
5. 폴백, 벌크헤드, 관측 지표를 함께 설계해야 실제 복구 가능성이 높아집니다.
📌 목차
01 | 웹서비스 장애가 전파되는 구조
02 | 지연이 오류보다 위험한 이유
03 | 장애 전파를 키우는 재시도
04 | 타임아웃을 먼저 설계하는 방법
05 | 재시도 가능한 오류와 불가능한 오류
06 | 백오프와 지터의 역할
07 | 서킷 브레이커의 동작 원리
08 | 서킷 브레이커 상태와 전환 조건
09 | 폴백과 사용자 경험 설계
10 | 벌크헤드로 자원 격리하기
11 | 멱등성과 중복 처리 방지
12 | 동기 호출과 비동기 처리 비교
13 | 모니터링과 장애 판단 지표
14 | 실전 설계 체크포인트
01 | 웹서비스 장애가 전파되는 구조
서비스 A가 서비스 B의 API를 호출하고, B가 데이터베이스나 외부 API를 다시 호출한다고 가정해 보겠습니다. 가장 아래 계층에서 지연이 발생하면 B의 응답도 늦어지고, A는 응답을 기다리는 요청을 계속 쌓게 됩니다.
결국 애플리케이션 스레드, 커넥션 풀, 작업 큐가 차례로 소진됩니다. 처음에는 일부 기능의 지연이었지만, 자원 고갈 이후에는 직접 관련이 없는 기능까지 실패할 수 있습니다.
02 | 지연이 오류보다 위험한 이유
즉시 실패하는 오류는 호출자가 비교적 빠르게 자원을 반환할 수 있습니다. 반면 지연은 요청이 살아 있는 동안 연결과 실행 자원을 붙잡아 동시 처리 능력을 떨어뜨립니다.
특히 여러 서비스가 순차적으로 호출되는 구조에서는 각 구간의 대기 시간이 누적됩니다. 사용자는 최종 응답만 보지만, 내부에서는 하나의 요청이 여러 자원을 장시간 점유하는 상태가 됩니다.
03 | 장애 전파를 키우는 재시도
재시도는 일시적인 네트워크 오류를 복구하는 데 유용하지만, 장애가 진행 중일 때는 같은 요청을 반복해 보내는 추가 트래픽이 됩니다. 호출자와 중간 계층이 모두 재시도하면 원래 요청 하나가 여러 요청으로 확대될 수 있습니다.
재시도 횟수를 무조건 늘리는 대신 전체 호출 경로에서 어느 계층이 재시도를 담당할지 정해야 합니다. 일반적으로 모든 계층이 독립적으로 재시도하지 않도록 책임 지점을 제한하는 편이 안전합니다.
04 | 타임아웃을 먼저 설계하는 방법
타임아웃은 “얼마나 기다린 뒤 실패로 판단할 것인가”를 정하는 장치입니다. 연결 타임아웃, 응답 대기 타임아웃, 전체 요청 기한을 구분하면 어느 구간에서 시간이 소모되는지 더 명확하게 관리할 수 있습니다.
하위 서비스의 타임아웃이 상위 서비스보다 길면 상위 요청이 먼저 끊긴 뒤에도 하위 작업이 계속 실행될 수 있습니다. 호출 체인의 전체 예산 안에서 하위 타임아웃을 배치하고, 남은 시간이 없으면 새 재시도를 시작하지 않아야 합니다.
05 | 재시도 가능한 오류와 불가능한 오류
| 오류 유형 | 재시도 판단 | 주의할 점 |
|---|---|---|
| 일시적 네트워크 단절 | 조건부 재시도 | 횟수와 전체 시간 제한이 필요합니다. |
| 서버 과부하 | 신중하게 적용 | 재시도가 부하를 더 키울 수 있습니다. |
| 인증·권한 오류 | 대체로 재시도하지 않음 | 자격 증명이나 권한을 먼저 확인합니다. |
| 잘못된 요청 | 재시도하지 않음 | 같은 요청은 같은 결과를 반복할 가능성이 큽니다. |
| 쓰기 작업의 응답 유실 | 멱등성 확인 후 적용 | 중복 생성이나 중복 결제를 막아야 합니다. |
HTTP 상태 코드나 내부 오류 코드를 기준으로 일괄 재시도하는 방식은 위험할 수 있습니다. 오류의 원인과 작업의 부작용을 함께 판단해야 합니다.
06 | 백오프와 지터의 역할
백오프는 재시도 사이에 대기 시간을 두는 방식입니다. 지수 백오프는 재시도할수록 대기 시간을 늘려 장애 중인 시스템에 요청이 몰리는 것을 줄입니다.
여기에 지터를 더하면 각 요청이 서로 다른 시점에 재시도합니다. 모든 클라이언트가 같은 간격으로 재시도하는 동기화 현상을 피할 수 있습니다. 다만 최대 대기 시간과 총 재시도 시간을 정하지 않으면 사용자의 요청이 지나치게 오래 지연될 수 있습니다.
07 | 서킷 브레이커의 동작 원리
서킷 브레이커는 의존 서비스의 실패율이나 지연이 일정 수준을 넘으면 호출을 잠시 차단하는 패턴입니다. 전기 회로의 차단기처럼 문제가 있는 구간을 끊어 상위 시스템까지 함께 손상되는 것을 막습니다.
차단된 동안에는 즉시 오류를 반환하거나 캐시, 기본 응답, 대체 경로를 사용합니다. 따라서 호출자가 계속 느린 하위 서비스에 매달리지 않고 자신의 자원을 회수할 수 있습니다.
08 | 서킷 브레이커 상태와 전환 조건
일반적으로 서킷 브레이커는 닫힘, 열림, 반열림 상태를 사용합니다. 닫힘은 정상 호출 상태이고, 열림은 호출을 차단하는 상태입니다. 반열림에서는 제한된 일부 요청만 통과시켜 서비스가 회복됐는지 확인합니다.
전환 기준은 단순 오류 횟수만으로 정하지 않는 것이 좋습니다. 시간 구간별 실패율, 타임아웃 비율, 응답 지연, 표본 수를 함께 고려해야 트래픽이 적은 상황에서 오판하는 문제를 줄일 수 있습니다.
09 | 폴백과 사용자 경험 설계
서킷 브레이커가 호출을 차단해도 사용자에게 무조건 빈 오류 화면만 보여줄 필요는 없습니다. 읽기 기능이라면 최근 캐시를 제공하고, 추천이나 부가 정보라면 해당 영역만 생략하는 방식으로 핵심 기능을 유지할 수 있습니다.
다만 폴백 데이터가 최신 정보가 아닐 수 있다는 점을 표시해야 합니다. 결제, 재고 확정, 권한 확인처럼 정확성이 중요한 작업은 임의의 성공 응답으로 대체해서는 안 됩니다.
10 | 벌크헤드로 자원 격리하기
벌크헤드 패턴은 기능별로 스레드, 커넥션, 큐, 동시 실행 수를 분리하는 방식입니다. 외부 배송 조회가 느려져도 로그인이나 주문 상태 조회가 같은 자원을 모두 사용하지 않도록 제한합니다.
기능별 제한은 전체 처리량을 낮추는 것처럼 보일 수 있지만, 한 의존성의 문제가 전체 서비스로 확산되는 것을 막는 안전장치입니다. 핵심 기능에 더 많은 자원을 배정하고 부가 기능에는 별도 한도를 둘 수 있습니다.
11 | 멱등성과 중복 처리 방지
재시도 설계에서 가장 중요한 조건 중 하나는 같은 요청을 여러 번 처리해도 결과가 의도하지 않게 중복되지 않는지 확인하는 것입니다. 조회는 비교적 안전하지만, 생성·결제·예약·상태 변경은 중복 실행 위험이 큽니다.
멱등 키를 요청에 포함하거나 처리 결과를 저장해 같은 키의 중복 요청을 한 번만 반영하도록 설계할 수 있습니다. 응답을 받지 못했다는 이유만으로 작업이 실행되지 않았다고 단정해서도 안 됩니다.
12 | 동기 호출과 비동기 처리 비교
즉시 결과가 필요한 작업은 동기 호출이 적합하지만, 처리 시간이 길거나 일시적 실패가 잦은 작업은 메시지 큐를 이용한 비동기 처리가 더 안정적일 수 있습니다.
비동기 방식은 호출자가 하위 서비스의 응답을 계속 기다리지 않게 하지만, 중복 메시지, 순서, 지연, 실패 메시지 보관 정책을 별도로 관리해야 합니다. 비동기화가 자동으로 장애를 없애는 것은 아니며 처리 상태를 추적할 수 있어야 합니다.
13 | 모니터링과 장애 판단 지표
장애 대응에는 성공률만으로 부족합니다. 응답 시간의 분포, 타임아웃 비율, 재시도 횟수, 서킷 브레이커 상태, 연결 풀 사용량, 큐 적체량을 함께 봐야 장애가 커지는 방향을 파악할 수 있습니다.
서비스 간 호출 관계를 추적하는 분산 추적도 유용합니다. 요청이 어느 구간에서 오래 머물렀는지 확인할 수 있어 단순히 “서버가 느리다”는 수준을 넘어 실제 병목을 찾는 데 도움이 됩니다.
14 | 실전 설계 체크포인트
장애 전파를 줄이는 설계는 특정 라이브러리 하나를 도입하는 것보다 호출 정책을 일관되게 정하는 데서 시작합니다.
예를 들어 결제 API에 짧은 전체 타임아웃을 두고, 인증 오류는 재시도하지 않으며, 일시적 네트워크 오류에만 제한된 재시도를 적용할 수 있습니다. 동시에 결제 요청에는 멱등 키를 사용하고, 결제 서비스가 불안정하면 주문 접수 상태를 명확히 보류 처리하는 식입니다.
☑ 체크리스트
- 모든 외부 호출에 연결·응답 타임아웃이 설정되어 있는가?
- 재시도 횟수와 전체 대기 시간이 제한되어 있는가?
- 재시도 대상 오류와 즉시 실패할 오류가 구분되어 있는가?
- 지수 백오프와 지터를 적용했는가?
- 서킷 브레이커의 열림·반열림 조건을 관측할 수 있는가?
- 기능별 커넥션·스레드·큐가 격리되어 있는가?
- 쓰기 요청의 중복 실행을 막을 멱등성 정책이 있는가?
📖 자주하는 질문 Q&A
서킷 브레이커를 모든 API에 적용해야 하나요?
모든 호출에 같은 기준을 적용할 필요는 없습니다. 실패가 전체 서비스에 큰 영향을 주는 외부 API나 느려질 가능성이 높은 의존성부터 적용하고, 읽기·쓰기 작업의 특성에 맞춰 차단과 폴백 정책을 다르게 정하는 것이 좋습니다.
재시도 횟수를 많이 설정하면 복구율이 높아지나요?
항상 그렇지는 않습니다. 장애가 지속되는 동안 재시도는 부하를 늘려 회복을 늦출 수 있습니다. 오류 유형, 전체 요청 기한, 서버의 과부하 상태를 함께 고려해 제한적으로 사용해야 합니다.
폴백으로 빈 응답을 반환해도 되나요?
부가 기능이라면 일부 정보를 생략하는 폴백이 가능하지만, 결제·권한·재고처럼 정확성이 중요한 기능은 성공으로 오해할 수 있는 빈 응답을 사용하면 안 됩니다. 상태를 명확히 전달하고 후속 확인 경로를 제공해야 합니다.
타임아웃과 서킷 브레이커 중 무엇을 먼저 적용해야 하나요?
타임아웃이 먼저입니다. 기다림의 상한이 정해지지 않으면 서킷 브레이커가 판단할 기준도 불안정해집니다. 이후 실패와 지연을 측정해 서킷 브레이커, 재시도, 폴백을 조합하는 순서가 안전합니다.
📝 마무리
웹서비스 장애 전파는 한 서비스의 실패보다 기다림, 재시도, 자원 공유가 통제되지 않을 때 커집니다. 타임아웃을 기준으로 요청 시간을 제한하고, 재시도에는 백오프와 지터를 적용하며, 서킷 브레이커와 벌크헤드로 장애 구간을 격리해야 합니다. 여기에 멱등성, 폴백, 관측 지표를 더하면 장애 상황에서도 핵심 기능을 유지하기 쉬워집니다.
