Timeout / Retry / Idempotency / Circuit Breaker

3줄 요약

  • Timeout은 느린 요청을 포기하는 값이 아니라 thread, connection, transaction, downstream 자원을 언제 회수할지 정하는 운영 정책이다.
  • Retry는 transient failure에는 도움이 되지만 idempotency, backoff, jitter, retry budget 없이 쓰면 장애를 증폭하는 traffic generator가 된다.
  • Circuit Breaker, Bulkhead, Fallback은 장애를 없애는 기능이 아니라 외부 의존성 장애가 전체 서비스로 번지는 것을 제한하는 보호 장치다.

핵심 정리

  • connect timeout은 TCP 연결 수립 제한이고 read/response timeout은 요청을 보낸 뒤 응답을 기다리는 제한이다. pool acquire timeout은 외부 API에 도달하기 전 내 애플리케이션 pool에서 대기하다 실패한 것이다.
  • timeout은 client, CDN/LB/proxy, application, HTTP client, DB, external API가 서로 정렬되어야 한다. 후단 dependency timeout은 caller의 전체 deadline보다 짧아야 사용자 응답 시간을 남길 수 있다.
  • retry 대상은 status code만으로 정하지 않는다. method, exception, idempotency key, Retry-After, downstream overload 여부를 함께 본다.
  • Idempotency Key는 같은 의도의 중복 요청을 하나의 결과로 수렴시키는 저장소 설계다. key, scope, request fingerprint, status, response, TTL, unique constraint가 필요하다.
  • Circuit Breaker가 open되는 것은 장애 해결이 아니라 caller 보호 동작이다. downstream 복구 여부와 breaker state를 별도로 관찰해야 한다.
  • Spring에서는 dependency별 HTTP client, timeout, pool, retry, breaker, bulkhead, fallback, metric을 분리해야 한다. 모든 외부 API가 같은 client와 같은 설정을 공유하면 장애 격리가 어렵다.

헷갈리는 지점

  • timeout을 길게 잡으면 안전하다고 생각하기 쉽다. 실패가 늦게 보일 뿐 thread, connection, DB lock, 외부 API pool을 오래 붙잡아 장애를 키울 수 있기 때문이다.
    • 핵심 판단 포인트는 사용자-facing deadline, downstream timeout, retry 횟수를 곱한 최악의 점유 시간을 계산하는 것이다.
  • retry를 넣으면 안정성이 올라간다고 믿기 쉽다. overload 상태의 retry는 실패한 시스템에 더 많은 traffic을 보내기 때문이다.
    • 핵심 판단 포인트는 retry success rate, retry exhausted count, downstream latency, retry budget을 함께 보는 것이다.
  • POST는 무조건 retry하면 안 된다고 외우기 쉽다. POST 자체는 idempotent하지 않지만 idempotency key와 중복 방지 저장소가 있으면 같은 의도의 재시도를 안전하게 만들 수 있기 때문이다.
    • 핵심 판단 포인트는 같은 key로 같은 payload가 오면 저장된 결과를 반환하고, 다른 payload가 오면 거부하는 것이다.
  • fallback은 사용자 경험을 무조건 좋게 만든다고 착각하기 쉽다. 오래된 데이터나 임의 기본값을 정상처럼 보여 주면 더 큰 비즈니스 문제가 생길 수 있기 때문이다.
    • 핵심 판단 포인트는 critical dependency에는 명확한 실패와 상태 조회를, optional dependency에는 제한이 드러나는 degrade 응답을 주는 것이다.

확인 질문

  • connect timeout, read timeout, pool acquire timeout은 어떻게 다른가?
    • connect timeout은 대상과 TCP 연결을 맺는 제한이고, read timeout은 요청 후 응답 대기 제한이며, pool acquire timeout은 내 애플리케이션의 connection pool에서 연결을 빌리지 못한 것이다.
  • retry가 장애를 증폭하는 대표 상황은 무엇인가?
    • downstream이 overload 상태인데 여러 caller와 여러 계층이 동시에 재시도해 정상 traffic보다 큰 retry traffic을 만들 때다.
  • Idempotency Key 저장소에 request fingerprint가 필요한 이유는 무엇인가?
    • 같은 key로 다른 payload가 들어오는 것을 구분해 잘못된 중복 처리나 공격성 요청을 막기 위해서다.
  • Circuit Breaker와 Bulkhead의 차이는 무엇인가?
    • Circuit Breaker는 실패 중인 dependency 호출을 빠르게 차단하고, Bulkhead는 특정 dependency가 전체 thread나 connection 같은 자원을 점유하지 못하도록 격리한다.
  • Spring resilience 설정에서 가장 위험한 상태는 무엇인가?
    • HTTP client timeout과 pool을 기본값에 맡기고, retry/breaker metric 없이 모든 dependency가 같은 client와 같은 정책을 공유하는 상태다.