크기 인식 Retry-After와 지연 수락: 설계 기록¶
이 문서는 토큰 예산 거부에 대한 크기 인식 Retry-After와 선택적 지연 수락 모드(이슈 #2423, #2401)의 설계를 기록합니다. 두 기능 모두 로컬 rate_limiting 리미터에 구현되어 있으며, 이 페이지는 이렇게 만든 이유를 기록합니다.
배경¶
토큰 예산 차원 이전에는 리미터가 요청 수를 셌습니다. 거부의 Retry-After는 고정된 요청당 비용에 대한 부족분을 리필 속도로 나눈 값이었으므로(token_bucket.rs), 200k 토큰 요청과 10 토큰 요청이 같은 대기 시간을 받았습니다. 유일한 피드백은 즉각적인 429였습니다.
함께 제공되는 관련 동작은 다음과 같습니다.
- 허브에서 동기화된 키:
control-plane경로는 키별input_tpm,output_tpm윈도를 적용하며,control_plane.policy.minute_limits.reserve_tokens는 요청 시작 시점에 각 요청의 추정 토큰을 예약합니다(허브 티어 분당 한도 참고). 이 설계가 다루는 로컬rate_limiting토큰 차원은 이 윈도들과 독립적입니다. - Anthropic 경로의 재시도 신호: 라우터가 직접 만드는 408, 409, 429, 5xx 응답에는
retry-after와 함께x-should-retry: true가 붙으므로(#2477), 크기 인식 대기는 Claude 클라이언트가 이미 읽는 헤더로 전달됩니다.
설계¶
- 토큰 예산 차원 (#2401, 구현됨): 리미터가 요청 수 차원 옆에 토큰 예산 윈도우를 갖고, 폴백 예산 플래너가 사용하는 것과 동일한 수락 추정치(전체 요청 입력 토큰 추정치,
max_tokens포함)로 공급됩니다. 버킷의try_consume은 추정 비용에 대한 대기를 보고하고, 요청별 속도 제한 헤더는 두 차원을 모두 전달합니다. - 크기 인식 Retry-After (#2423, 구현됨):
Retry-After는 고정된 요청 비용이 아니라, 거부된 요청 자체의 추정 비용에 대해 소진된 예산에서 계산됩니다. 이를 지키는 클라이언트는 앞으로 소비할 것에 비례하는 백오프를 받습니다. 버스트 전체보다 큰 요청도 크기를 반영한 힌트를 받으며, 기다려도 들어갈 수 없으므로 지연 수락되지 않습니다. 힌트는 최소 1초, 최대 86400초입니다. - 지연 수락 (#2423, 구현됨, 기본값 꺼짐): 짧은 대기 내에 수락할 수 있는 거부된 요청은 거부 대신 그 대기 동안 보류되며, 보류된 요청이 라우터에 열린 연결로 쌓이지 않도록 설정 가능한 최댓값으로 경계가 정해집니다. 보류된 요청은 아직 디스패치되지 않았으므로 백엔드 슬롯을 차지하지 않고, 대기 동안 토큰 예약도 잡지 않습니다. 예약은 대기가 성공한 뒤에 잡힙니다. 예산이 그 경계 내에서 수락할 수 없는 요청은 크기 인식 대기와 함께 즉시 429를 받습니다.
- 완료 시점의 재조정 (#2419, 구현됨): 응답이 끝나면 실제 사용량이 예산의 수락 추정치를 대체하므로, 다음 추정치는 이전 요청들이 실제로 소비한 것 위에서 계산됩니다.
확정된 결정¶
- 지연 수락 대기는 두 번 경계가 정해집니다. 요청별로는
rate_limiting.deferred_admission.max_wait_ms(기본값 2000)가 첫 지연 시점부터 측정되고, 라우터 프로세스마다max_held_requests(기본값 1000)가 레이트 리밋 스토어의 프로세스 로컬 카운트로 경계를 정합니다. 클라이언트 취소는 홀드 가드의Drop으로 카운트를 돌려 놓습니다. - 두 리셋은 별도 행입니다. 요청 수
x-ratelimit-reset은 자체 윈도에 머물고, 토큰 예산은 헤더 계열의X-RateLimit-Tokens-Reset으로 보고합니다.