Tail Latency 극복: Go 마이크로서비스에서 Request Hedging 가이드

발행: (2026년 3월 14일 PM 12:03 GMT+9)
6 분 소요
원문: Dev.to

Source: Dev.to

분산 시스템에서는 종종 “Long Tail(긴 꼬리)”에 대해 이야기합니다.
예를 들어 95 %의 요청이 100 ms 이하로 완료되지만, 마지막 1 %(P99 지연)는 2 초 이상 걸릴 수 있습니다. 하나의 사용자 행동이 열 개의 서로 다른 서비스 호출을 트리거하는 마이크로서비스 아키텍처에서는, 단 하나의 느린 종속성이 전체 사용자 경험을 병목 현상으로 만들 수 있습니다.

표준 재시도는 여기서 도움이 되지 않습니다. “긴 꼬리” 요청은 아직 실패한 것이 아니라 느린 것이기 때문입니다. 2 초 타임아웃이 발생해 재시도가 일어나기를 기다리는 것은 시간을 낭비하는 것입니다. 긴 꼬리를 이기려면 Request Hedging(요청 헤징)(또는 speculative retries, 추측 재시도) 가 필요합니다.

Request Hedging이란?

개념은 간단하지만 강력합니다: 요청이 평소보다 오래 걸리고 있다면(예: P95 지연보다 오래) 요청을 중단하지 않습니다. 대신 동일한 두 번째 요청을 병렬로 시작합니다. 먼저 끝난 요청의 결과를 사용하고, 다른 요청은 취소합니다.

이 추측적 접근 방식은 두 개의 동일한 요청이 동시에 긴 꼬리를 만나게 될 확률이 매우 낮기 때문에 P99 지연을 크게 감소시킵니다.

수동 헤징의 복잡성

Go에서 헤징을 직접 구현하는 것은 고루틴 관리의 악몽과 같습니다:

  • 타이머가 포함된 select 블록이 필요합니다.
  • 두 개(또는 그 이상)의 고루틴을 조정해야 합니다.
  • 하나가 성공하면 나머지는 즉시 취소해 자원을 절약해야 합니다.
  • 두 요청이 정확히 같은 밀리초에 성공하는 경우와 같은 레이스 컨디션을 처리해야 합니다.

대부분의 개발자는 하나의 헤징된 호출을 처리하기 위해 수백 줄의 부서지기 쉬운 보일러플레이트 코드를 작성하게 됩니다.

Resile 방식: DoHedged

Resile은 요청 헤징을 단일 함수 호출만으로 간단하게 만들어 줍니다. 고루틴 수명 주기, 컨텍스트 취소, 레이스 컨디션을 자동으로 처리합니다.

import "github.com/cinar/resile"

data, err := resile.DoHedged(
    ctx,
    func(ctx context.Context) (*User, error) {
        return apiClient.GetUser(ctx, userID)
    },
    resile.WithMaxAttempts(3),
    resile.WithHedgingDelay(100 * time.Millisecond),
)

내부에서 무슨 일이 일어나나요?

  • Resile은 첫 번째 요청을 시작합니다.
  • 설정된 HedgingDelay(예: 100 ms)만큼 대기합니다.
  • 첫 번째 요청이 아직 끝나지 않았다면 두 번째 요청을 시작합니다.
  • 하나가 성공적으로 결과를 반환하면 Resile은 다른 요청의 컨텍스트를 취소하고 데이터를 반환합니다.

적절한 Hedging Delay 선택하기

헤징의 “마법”은 지연 시간에 있습니다.

  • 너무 짧게: 불필요하게 트래픽이 두 배가 되어 하위 서비스에 추가 부하를 줍니다.
  • 너무 길게: 지연 감소 효과가 거의 없습니다.

팁: HedgingDelayP95 또는 P99 지연으로 설정하세요. 이렇게 하면 가장 느린 1‑5 %의 요청만 헤징하게 되어, 최소한의 추가 부하로 큰 지연 감소를 얻을 수 있습니다.

관측성: “추측” 승리 추적하기

Resile의 OpenTelemetry 통합(telemetry/resileotel)을 사용한다면, 분산 트레이스에서 이러한 승리를 확인할 수 있습니다. 각 헤징 시도는 서브‑스팬으로 기록되며, 헤징된 요청이 승리하면 첫 번째 스팬은 취소되고 두 번째 스팬이 성공하여, 헤징이 사용자를 2‑초 대기에서 구해냈다는 명확한 증거를 제공합니다.

결론

요청 헤징은 과거에 대규모 인프라 팀을 가진 기업만이 사용할 수 있는 기술이었습니다. Resile을 사용하면 모든 Go 개발자가 더 빠르고 회복력 있는 마이크로서비스를 구축할 수 있는 도구가 됩니다.

“대기하고 재시도”에서 “헤징하고 승리”로 전환함으로써, 긴 꼬리 지연을 경쟁력으로 바꿀 수 있습니다.

GitHub에서 Resile에 스타를 눌러 주세요:

0 조회
Back to Blog

관련 글

더 보기 »

Jemalloc, Meta가 포기하지 않음

- Meta는 소프트웨어 인프라에서 고성능 메모리 할당기인 jemalloc의 장기적인 이점을 인식하고 있습니다. - 우리는 jemalloc에 대한 관심을 새롭게 하고 있습니다…

GPU 비행 — 시스템 아키텍처

GPU Flight 아키텍처 개요 이전 게시물에서는 SASS 수준에서의 스레드 발산을 다루었습니다. 다른 최적화 전략에 뛰어들기 전에, r하는 것이 도움이 됩니다.