Q.외부 API 하나가 느려졌을 때 전체 서비스가 멈추는 이유는?
느린 호출이 스레드나 커넥션을 붙잡고 있어 다른 요청이 쓸 자원이 사라지기 때문입니다.
| 순서 | 벌어지는 일 |
|---|
| 1 | 외부 호출이 5초씩 걸린다 |
| 2 | 그 호출을 하는 요청이 스레드를 5초간 점유한다 |
| 3 | 스레드풀이 그 요청들로 가득 찬다 |
| 4 | 외부 호출과 무관한 요청도 스레드를 못 받는다 |
| 5 | 전체 서비스가 응답하지 않는다 |
4번이 핵심입니다. 문제는 외부 API 하나인데 증상은 전체 장애로 나타납니다. 그래서 원인을 찾기 어렵습니다.
| 막는 방법 | 내용 |
|---|
| 타임아웃 | 5초가 아니라 상위 목표 안으로 짧게 |
| 격리 | 외부 호출용 자원을 따로 둔다. 그것만 마른다 |
| 차단기 | 실패가 지속되면 즉시 실패로 응답한다 |
| 대체 응답 | 캐시된 값이나 축소된 응답을 준다 |
| 비동기화 | 즉시 필요 없는 호출은 큐로 뺀다 |
두 번째가 근본입니다. 자원을 나눠 두면 그 부분만 고장 나고 나머지는 계속 돕니다.
흔한 실수: 타임아웃만 줄여 해결하려는 것. 짧게 해도 그 시간 동안 자원을 점유하는 것은 같아서, 호출량이 많으면 여전히 전체가 막힙니다.
Q.서킷 브레이커의 상태 전이를 설명해주세요
| 상태 | 행동 | 다음으로 넘어가는 조건 |
|---|
| 닫힘 | 정상적으로 호출한다 | 실패율이 임계를 넘으면 열림으로 |
| 열림 | 호출하지 않고 즉시 실패한다 | 정해진 시간이 지나면 반열림으로 |
| 반열림 | 소수의 호출만 시험해 본다 | 성공하면 닫힘, 실패하면 다시 열림 |
열림 상태의 의미가 중요합니다. 실패할 것이 뻔한 호출을 하지 않는 것입니다. 그러면 우리 자원을 점유하지 않고, 상대에게 부하를 더 얹지도 않습니다.
반열림이 필요한 이유는 회복을 감지하기 위해서입니다. 열림을 영구히 유지하면 상대가 복구돼도 모릅니다. 소수만 보내 확인하고, 괜찮으면 전체를 재개합니다.
설정에서 정할 것들입니다.
| 항목 | 판단 |
|---|
| 실패율 임계 | 낮으면 자주 끊긴다. 보통 50% 안팎 |
| 최소 호출 수 | 호출이 적을 때 우연한 실패로 열리지 않게 |
| 열림 유지 시간 | 상대의 회복 시간을 고려한다 |
| 무엇을 실패로 볼지 | 타임아웃과 5xx 는 실패, 4xx 는 아니다 |
흔한 실수: 마지막 항목을 구분하지 않는 것. 잘못된 요청(4xx)을 실패로 세면, 우리 버그 때문에 정상인 상대를 차단하게 됩니다.
Q.벌크헤드(격리)는 무엇을 막아주나요?
자원을 칸으로 나눠 한 칸이 마르더라도 다른 칸은 계속 돌게 하는 것입니다. 배의 격벽에서 온 이름입니다.
| 나누는 대상 | 예 |
|---|
| 스레드풀 | 외부 호출용과 내부 처리용을 분리 |
| 커넥션 풀 | 조회용과 쓰기용, 또는 기능별 |
| 프로세스나 인스턴스 | 중요 API 와 배치를 다른 서버로 |
| 큐 | 우선순위별로 다른 큐 |
없을 때와 있을 때가 이렇게 갈립니다.
| 구성 | 외부 API 가 느려지면 |
|---|
| 공용 풀 하나 | 그 호출이 풀을 채워 전체 서비스가 멈춘다 |
| 기능별 분리 | 그 기능만 실패하고 나머지는 정상 |
차단기와 함께 쓰면 더 낫습니다. 격리가 피해 범위를 가두고, 차단기가 그 안에서도 빨리 포기하게 만듭니다.
대가는 자원 효율입니다. 나눠 두면 한쪽이 남고 다른 쪽이 부족한 상황이 생깁니다. 전체 활용도를 조금 내주고 장애 격리를 얻는 거래입니다.
흔한 실수: 격리를 인스턴스 분리로만 생각하는 것. 한 프로세스 안에서 스레드풀을 나누는 것만으로도 큰 효과가 있고, 훨씬 값쌉니다.
Q.부하가 지속될 때 요청을 거부하는 것이 왜 필요한가요?
받을 수 없는 요청을 받으면 아무도 응답받지 못합니다. 일부를 거절하는 것이 전체를 지키는 방법입니다.
| 거절하지 않으면 | 결과 |
|---|
| 큐에 계속 쌓인다 | 대기가 길어져 이미 타임아웃된 요청을 처리한다 |
| 메모리가 는다 | 대기 중인 요청 객체가 쌓인다 |
| 지연이 무한히 커진다 | 모든 사용자가 실패한다 |
핵심은 이미 포기한 요청을 처리하는 낭비입니다. 클라이언트가 3초에 타임아웃하는데 큐 대기가 10초면, 처리한 결과를 받을 사람이 없습니다. 그런데도 자원은 씁니다.
| 방법 | 내용 |
|---|
| 큐 길이 제한 | 넘으면 즉시 429 로 응답한다 |
| 대기 시간 기준 폐기 | 큐에서 오래된 요청은 버린다 |
| 우선순위 | 결제는 받고 추천은 거절한다 |
| 동시 처리 수 제한 | 처리 중인 요청 수에 상한을 둔다 |
| 축소 응답 | 무거운 부분을 빼고 핵심만 준다 |
빨리 실패하는 것이 사용자에게도 낫습니다. 10초 기다려 실패하는 것보다 즉시 실패해 재시도 안내를 받는 편이 낫습니다.
흔한 실수: 큐를 무한으로 두는 것. 요청을 잃지 않으려는 의도지만, 결과적으로 모든 요청을 잃습니다.
먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.
읽었으면 문제로 확인해보세요
분산 시스템 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.