고민보단 실천을

Spring Boot Resilience4j 실전: timeout-retry-circuit breaker를 '같이' 설계하는 법 본문

카테고리 없음

Spring Boot Resilience4j 실전: timeout-retry-circuit breaker를 '같이' 설계하는 법

Just-Do-It 2026. 4. 7. 13:59

Spring Boot Resilience4j 실전: timeout-retry-circuit breaker를 '같이' 설계하는 법

재시도는 쉽게 붙이지만, 타임아웃과 서킷 브레이커를 같이 설계하지 않으면 폭주를 만든다.

원칙

  1. Timeout을 먼저 정한다(상한).
  2. Retry는 멱등 요청에만, 횟수는 적게(지터 포함).
  3. Circuit Breaker로 계속 실패하는 다운스트림을 잠시 격리한다.
  4. Bulkhead로 장애 전파를 막는다(격리).

설정 예시(application.yml)

resilience4j:
timelimiter:
instances:
partnerApi:
timeoutDuration: 800ms
retry:
instances:
partnerApi:
maxAttempts: 2
waitDuration: 120ms
circuitbreaker:
instances:
partnerApi:
slidingWindowSize: 50
failureRateThreshold: 50

Retry는 멱등성부터 확인

  • GET/조회는 재시도하기 쉽다.
  • POST/상태 변경은 Idempotency-Key 또는 내부 멱등 처리 없이는 위험하다.
  • 재시도는 '장애를 고친다'가 아니라 '일시적 실패를 흡수'하는 도구다.

fallback 설계 팁

  • 캐시된 값 반환, 기본값 반환, 503 매핑 중 무엇이 맞는지 API별로 정한다.
  • fallback 비율이 급증하면 사실상 장애다: 지표/알람으로 본다.

적용 순서(실무 플로우)

긴 글을 한 번에 다 적용하기보다, 아래 순서대로 '작게' 넣고 관측하면서 키우는 게 실무에서 성공률이 높다.

  1. 현재 상태를 수치로 확인한다(지표/로그/샘플 트래픽).
  2. 팀 규칙(키/상태/응답 포맷/설정)을 문서로 고정한다.
  3. 핵심 경로 1개(가장 중요한 엔드포인트/잡/토픽)부터 적용한다.
  4. 부하/동시성/실패를 재현하는 테스트를 만든다(운영과 비슷하게).
  5. 관측(대시보드/알람)을 붙인다: 실패가 '조용히' 넘어가지 않게.
  6. 점진적으로 확장한다(적용 범위를 넓히기 전에 효과를 확인).
  7. 배포/롤백 계획을 문서화한다(누가, 언제, 어떤 조건에서 되돌릴지).
  8. 1~2주 운영 데이터를 보고 규칙/기본값을 재조정한다(처음 값은 대개 틀린다).

운영 체크포인트

  • Timeout을 먼저 정한다(상한). Retry는 그 안에서만 짧게.
  • Circuit Breaker 상태(Open/Half-open)를 대시보드로 본다.
  • fallback은 조용한 실패가 될 수 있다: 비즈니스 지표로도 감시한다.

운영 지표/알람 추천

  • 에러율(4xx/5xx)과 실패 원인 top N
  • 지연(p95/p99)과 타임아웃 비율
  • 재시도 횟수/비율(있다면)
  • 핵심 비즈니스 지표(성공률 등)와 상관관계

빠른 점검 명령/쿼리

장애가 났을 때 '어디부터 볼지'가 정해져 있으면 대응 속도가 빨라진다. 아래는 팀에서 그대로 템플릿으로 쓰기 좋은 최소 목록이다.

# Resilience4j 메트릭 노출(Actuator/Prometheus) 확인
curl -s http://localhost:8080/actuator/metrics | head
curl -s http://localhost:8080/actuator/prometheus | rg "resilience4j" | head

구조화 로그 필드 추천

  • traceId/requestId/eventId 중 하나는 반드시 포함
  • endpoint/method/status/latencyMs 같은 기본 필드
  • 실패 이유(reasonCode)와 재시도 횟수(retryAttempt)
  • 민감정보 마스킹(토큰/비밀번호/개인정보)
{
"level": "INFO",
"message": "request completed",
"requestId": "...",
"method": "POST",
"path": "/api/orders",
"status": 201,
"latencyMs": 123,
"userId": 1004,
"reasonCode": null
}

테스트 케이스 샘플

테스트 케이스(최소 3종):
1) 정상: 기대 응답/상태 전이가 맞는지
2) 실패: 입력 오류/다운스트림 오류가 '예상한 에러 포맷'으로 떨어지는지
3) 동시성/재시도: 같은 요청이 2~3번 들어와도 데이터가 깨지지 않는지(멱등성)

추가(가능하면):
- 지연/타임아웃: 느린 상황에서 서킷/타임아웃이 상한을 지키는지
- 재시도 폭주: retry가 장애를 키우지 않는지

트레이드오프/대안

  • 기본값은 팀/서비스에 따라 달라진다: 숫자는 '정답'이 아니라 '출발점'이다.
  • 가용성 vs 보호(fail-open vs fail-closed) 결정을 미루면 장애 때 더 큰 혼란이 온다.
  • 관측 없이 최적화하면: 좋아졌는지 나빠졌는지 판단이 안 된다.
  • 단순한 구현이 항상 좋은 건 아니다: 운영/디버깅 비용까지 합쳐서 판단해야 한다.

성공 기준(SLO) 예시

  • 에러율: 5xx 0.1% 이하(서비스 특성에 맞게)
  • 지연: p95 300ms 이하(핵심 API 기준)
  • 타임아웃: 전체 요청의 0.01% 이하
  • 중복 실행(멱등): 0건(또는 '부작용 0건')

자주 터지는 실수/트러블슈팅

  1. POST를 무조건 retry한다: 중복 실행으로 사고가 난다.
  2. timeout 없이 retry만 넣는다: 요청이 더 오래 살아남아 폭주한다.

바로 적용 템플릿

팀 문서/코드 리뷰에서 바로 복붙할 수 있게 최소 규격을 템플릿으로 남겨두는 게 반복 작업을 줄인다.

권장 조합(요약):
- TimeLimiter(800ms) + Retry(최대 2회, backoff+jitter) + CircuitBreaker(실패율 기반)
- 멱등하지 않은 요청: retry 금지 또는 Idempotency-Key로 보호

검증 방법

  • 다운스트림을 느리게 만들어 서킷이 열리는지 확인한다.
  • retry가 요청 폭주를 만들지 않는지 TPS/지연을 측정한다.

장애 대응 Runbook(초안)

  • 현상: 무엇이 깨졌나(에러/지연/중복/누락) 한 문장으로 적기
  • 범위: 언제부터/어느 사용자/어느 엔드포인트/어느 파티션인지
  • 증거: 로그 3줄 + 지표 1개로 재현 가능한 단서 만들기
  • 임시 조치: 제한(레이트리밋), 차단(서킷), 롤백/스위치 등
  • 근본 원인: 키/정렬/락/타임아웃/재시도 등 어떤 규칙이 깨졌는지
  • 재발 방지: 테스트 추가 + 대시보드/알람 + 문서 업데이트
  • 후속 조치: 고객 공지/내부 공유(영향 범위, 원인, 재발 방지) 템플릿으로 남기기

리뷰 체크리스트

  1. 성공/실패 기준이 수치로 정의돼 있다(지표, 임계값).
  2. 입력 검증/에러 처리가 400/409/429 등으로 명확하다(500 남발 금지).
  3. 동시성/재시도 상황에서도 데이터가 깨지지 않는다(멱등성/락/유니크).
  4. 타임아웃이 상한으로 존재한다(무한 대기 금지).
  5. 부하 증가 시 동작이 예측 가능하다(인덱스/큐/풀 고갈 대비).
  6. 로그에 상관관계 키(traceId/requestId/eventId)가 있다.
  7. 설정 기본값이 문서로 남아 있다(왜 이 값인지).
  8. 롤백/비상 조치(runbook)가 준비돼 있다.
  9. 테스트에 최소 1개 이상의 실패 케이스가 있다(운영 재현 목적).
  10. 참고/출처(공식 문서)로 팀이 더 깊게 확인할 경로가 있다.

팀 문서 템플릿

팀 문서 템플릿(복붙용):
1) 목표/배경: 무엇을 해결하나(한 문장)
2) 범위: 적용 대상(엔드포인트/잡/토픽/테넌트)
3) 규칙: 키/정렬/상태/응답 포맷/기본값
4) 예외: 허용하지 않는 케이스(차단/에러 코드)
5) 운영: 지표/알람/대시보드 링크
6) 장애 대응: 임시 조치 + 롤백 절차
7) 변경 이력: 언제/누가/무엇을 바꿨나

FAQ(자주 묻는 질문)

Q. 이걸 도입하면 성능이 무조건 좋아지나요?
A. 항상 그렇진 않다. 목표는 보통 '장애 반경 축소'와 '예측 가능성'이다. 성능은 인덱스/타임아웃/풀/캐시처럼 병목을 같이 잡아야 체감이 나온다.

Q. "Spring Boot Resilience4j 실전: timeout-retry-circuit breaker를 '같이' 설계하는 법"를 적용했는데도 문제가 남아요. 어디부터 봐야 하나요?
A. 먼저 로그/지표로 실패 지점을 좁히고, 설정/키/인덱스 같은 고정 요소부터 점검한다. 재현 가능한 체크리스트를 먼저 만든다.

Q. 운영에서 가장 흔한 실수는요?
A. 규칙을 문서화하지 않고 팀마다 다르게 구현하는 것이다. 같은 이름의 기능이라도 데이터 모델/키/상태 정의가 다르면 장애가 난다.

Q. 최소 도입으로 효과를 보려면?
A. 체크리스트 1~2개만이라도 먼저 적용해 효과가 보이는 변화를 만든 뒤 범위를 넓히는 게 안전하다.

Q. 테스트를 어디까지 해야 하나요?
A. 최소로는 (1) 정상 케이스, (2) 실패 케이스, (3) 동시성/재시도 케이스 3가지는 자동화하는 게 좋다. 운영 이슈의 대부분이 3번에서 나온다.

참고/출처

Comments