← 블로그 목록

Developer Knowledge

개발자가 알아야 할 지식: 헤지 요청, 느린 복제본을 기다리지 않는 법

꼬리 지연을 줄이기 위한 헤지 요청 패턴의 원리, 재시도와의 차이, 부하 증폭 위험, 실무 적용 체크리스트를 정리한다.

gRPC Authors
  • 개발자가 알아야 할 지식
  • Software Engineering
  • Distributed Systems
  • Performance
  • Reliability

왜 개발자가 알아야 하나

서비스 성능 문제는 항상 평균에서 드러나지 않는다. 어제 살펴본 꼬리 지연처럼, 대부분의 요청은 빠르게 끝나는데 일부 요청만 유난히 오래 걸리는 상황이 있다. 특히 여러 하위 서비스를 동시에 호출하는 fan-out 구조에서는 작은 지연이 쉽게 누적된다. 검색 결과를 만들기 위해 여러 샤드를 조회하거나, 추천·가격·재고·프로필 서비스를 동시에 부르는 화면이라면 한 곳의 느린 응답이 전체 사용자 경험을 끌고 내려간다.

헤지 요청(hedged request)은 이런 긴 꼬리를 줄이기 위한 기법이다. 원래 요청을 보낸 뒤 일정 시간이 지나도 응답이 오지 않으면, 같은 요청을 다른 복제본이나 엔드포인트에 한 번 더 보낸다. 둘 중 먼저 성공한 응답을 사용하고 나머지는 취소하거나 무시한다. 핵심은 실패한 요청을 다시 시도하는 것이 아니라, 아직 실패하지 않았지만 비정상적으로 느린 요청을 우회한다는 점이다.

이 패턴은 구글의 대규모 분산 시스템 논의와 gRPC의 request hedging 문서에서도 자주 등장한다. 좋은 조건에서 쓰면 p95, p99 같은 높은 퍼센타일 지연을 크게 낮출 수 있다. 하지만 잘못 쓰면 단순한 성능 개선책이 아니라 장애 증폭기가 된다. 이미 시스템이 과부하인데 모든 클라이언트가 요청을 복제하면, 느린 서비스는 더 많은 요청을 받고 더 느려진다.

개발자가 헤지 요청을 알아야 하는 이유는 “빠르게 만들기”보다 “어떤 느림을 다룰 것인가”를 구분하기 위해서다. 네트워크 패킷이 잠깐 늦었는지, 특정 복제본만 GC pause에 걸렸는지, 하위 서비스 전체가 과부하인지에 따라 처방이 달라진다. 헤지 요청은 세 번째 상황에는 위험하고, 첫 번째와 두 번째 상황에는 유용할 수 있다. 이 차이를 모르고 “느리면 한 번 더 보내자”로 접근하면, 성능 문제를 비용 문제와 장애 문제로 바꾸기 쉽다.

핵심 개념

헤지 요청은 재시도와 비슷해 보이지만 목적이 다르다. 일반 재시도는 실패 응답, 타임아웃, 네트워크 오류가 발생한 뒤 다시 요청을 보낸다. 반면 헤지는 원래 요청이 아직 살아 있는 상태에서 늦어질 가능성을 보고 중복 요청을 보낸다. 그래서 헤지는 실패 복구라기보다 지연 분포 관리에 가깝다.

예를 들어 세 개의 동일한 검색 복제본이 있다고 하자. 클라이언트는 첫 번째 복제본에 요청을 보낸다. 보통은 80밀리초 안에 응답이 오지만, 가끔 800밀리초가 걸린다. 클라이언트가 120밀리초까지 기다렸는데 응답이 없으면 두 번째 복제본에도 같은 요청을 보낸다. 두 번째 복제본이 60밀리초 만에 답하면 총 180밀리초 근처에서 결과를 받을 수 있다. 첫 번째 요청이 나중에 돌아오면 버린다.

여기서 가장 중요한 값은 hedging delay다. 너무 짧게 잡으면 거의 모든 요청이 복제되어 부하가 늘어난다. 너무 길게 잡으면 꼬리 지연을 줄이는 효과가 작다. 보통은 실제 지연 분포를 보고 p95나 p99보다 앞선 지점, 혹은 서비스별 SLO에 맞춘 지점을 신중하게 잡는다. 정답은 고정값이 아니라 트래픽, 의존성, 비용, 용량에 따라 달라진다.

또 하나 중요한 조건은 요청의 안전성이다. 헤지는 같은 요청을 여러 번 보낼 수 있으므로 읽기 요청이나 멱등성이 보장된 요청에 잘 맞는다. 상품 상세 조회, 검색, 추천 후보 조회, 캐시 가능한 메타데이터 조회처럼 같은 요청을 두 번 보내도 부작용이 없는 작업이 적합하다. 결제 승인, 포인트 적립, 주문 생성, 이메일 발송처럼 부작용이 있는 작업에는 그대로 적용하면 안 된다. 이런 작업에는 먼저 멱등성 키와 중복 처리 방어가 필요하다.

작은 예시 또는 체크리스트

간단한 의사 코드는 이렇게 생각할 수 있다.

async function hedgedFetch(primary, secondary, request) {
  const first = primary(request);
  const hedge = delay(120).then(() => secondary(request));

  const response = await Promise.any([first, hedge]);
  return response;
}

실제 구현에서는 이보다 훨씬 더 많은 조건이 필요하다. 원래 요청을 취소할 수 있어야 하고, 헤지 요청 수에 상한이 있어야 하며, 서버가 과부하 신호를 보낼 때는 헤지를 멈춰야 한다. gRPC도 헤지 정책에서 최대 시도 횟수, 지연 시간, non-fatal status code 같은 조건을 분리해서 다룬다.

실무 체크리스트는 다음과 같다.

  • 이 요청은 읽기 요청이거나 멱등성이 보장되는가?
  • 지연 분포를 히스토그램으로 보고 있는가?
  • 헤지를 시작할 기준 시간이 실제 p95/p99와 SLO를 반영하는가?
  • 전체 요청 중 헤지되는 비율을 별도 메트릭으로 보고 있는가?
  • 하위 서비스가 과부하일 때 헤지를 중단할 수 있는가?
  • 같은 사용자 요청에서 최대 몇 번까지 중복 호출을 허용할지 정했는가?
  • 먼저 성공한 응답 이후 남은 요청을 취소하거나 결과를 안전하게 버릴 수 있는가?

실무에서 자주 생기는 오해

첫 번째 오해는 헤지 요청을 재시도의 고급 버전으로 보는 것이다. 재시도는 실패를 복구하고, 헤지는 느린 성공 가능성을 우회한다. 둘 다 호출 수를 늘릴 수 있지만 작동 시점과 위험이 다르다. 재시도와 헤지를 동시에 적용하면 생각보다 빠르게 요청 폭풍이 생길 수 있다.

두 번째 오해는 헤지가 항상 p99를 낮춘다는 믿음이다. 복제본 간 지연이 독립적이고, 일부 복제본만 일시적으로 느린 상황이라면 효과가 좋다. 하지만 공통 데이터베이스가 병목이거나 네트워크 전체가 흔들리거나 하위 서비스가 CPU 포화 상태라면, 중복 요청은 같은 병목을 더 세게 누를 뿐이다.

세 번째 오해는 부하 비용을 무시하는 것이다. 헤지 비율이 5%만 되어도 대규모 서비스에서는 상당한 추가 트래픽이 된다. 특히 요청 하나가 무거운 DB 쿼리나 외부 API 비용을 동반한다면, 꼬리 지연 개선보다 비용 증가와 장애 위험이 더 클 수 있다.

네 번째 오해는 취소를 대충 다뤄도 된다는 생각이다. 먼저 성공한 응답을 받았더라도 원래 요청이 계속 서버에서 실행되면 부하 절감 효과가 줄어든다. 클라이언트 cancellation, 서버 측 cancellation 전파, 이미 시작된 작업을 중단할 수 없는 경우의 비용까지 봐야 한다.

다섯 번째 오해는 모든 엔드포인트에 같은 hedging delay를 쓰는 것이다. 서비스마다 지연 분포와 비용이 다르다. 캐시 조회, 검색, DB 트랜잭션, 외부 결제 API에 같은 정책을 적용하면 안 된다. 헤지는 전역 옵션이 아니라 호출별 정책이어야 한다.

오늘 바로 적용해보기

먼저 평균 지연 시간이 아니라 히스토그램과 p95, p99를 확인하자. 헤지 요청은 분포를 모르면 설계할 수 없다. 어느 엔드포인트에서 꼬리가 길어지는지, 특정 복제본에 집중되는지, 배포 직후 늘어나는지, 트래픽 피크와 관계가 있는지부터 봐야 한다.

그다음 읽기 요청 중 부작용이 없고 사용자 체감 지연에 큰 영향을 주는 경로를 하나 고른다. 검색 샤드 조회, 추천 후보 조회, 여러 복제본을 가진 내부 조회 API가 좋은 후보가 될 수 있다. 이 경로에만 제한적으로 헤지를 실험하고, 헤지 비율, 추가 요청 수, p95/p99 개선 폭, 하위 서비스 CPU와 큐 길이를 같이 본다.

마지막으로 “헤지를 꺼야 하는 조건”을 먼저 정해두자. 에러율이 올라가거나, 하위 서비스가 과부하 상태를 알리거나, 헤지 비율이 상한을 넘으면 자동으로 비활성화해야 한다. 성능 최적화는 장애 대응보다 우선하지 않는다. 좋은 헤지 정책은 빠른 길을 찾는 동시에, 길이 막혔을 때 더 많은 차를 밀어 넣지 않는 정책이다.

더 알아보기

  • gRPC: Request Hedging
  • Google SRE Book: Handling Overload
  • The Tail at Scale, Dean and Barroso
  • OpenTelemetry: Metrics and histograms

오늘의 takeaway

헤지 요청은 “한 번 더 보내기”가 아니라, 꼬리 지연과 부하 증폭 사이의 균형을 계측으로 다루는 기법이다.