API 속도 제한의 토큰 버킷: 버스트와 평균 처리율 설정
토큰 버킷의 용량과 충전률을 수치로 해석하고, 분산 상태·429 재시도 정책을 설계하는 기준을 설명합니다.
발행·오류 제보: infostoa · 편집 기준
이 글의 개별 작성자와 전문 검토자 정보는 제공되지 않습니다.
토큰 버킷은 요청을 평균적으로 제한하면서 짧은 버스트는 허용하는 알고리즘입니다. 용량 B의 버킷에 속도 r로 토큰을 채우고, 요청마다 비용만큼 토큰을 씁니다. 토큰이 부족하면 거부하거나 별도 큐 정책을 적용합니다. Redis rate limiter 안내는 토큰 버킷을 Redis에서 구현하는 방법을 예시하고, RFC 6585 §4는 HTTP 429의 의미를 정의합니다.
용량과 충전률을 숫자로 해석하기
버킷 용량이 5, 충전률이 초당 2개라고 가정해 봅시다. 충분히 쉬었다가 들어온 요청은 버킷이 찬 상태에서 최대 5개까지 빠르게 통과할 수 있습니다. 이후에는 평균적으로 0.5초마다 토큰 하나가 회복됩니다. 따라서 “초당 2개”는 매 순간 정확히 두 요청만 허용한다는 뜻이 아닙니다. 짧은 순간의 허용량은 용량이, 지속 가능한 평균은 충전률이 좌우합니다.
현재 토큰 수를 T, 마지막 계산 시각을 t₀, 지금을 t₁이라 하면 요청 직전에 min(B, T + (t₁−t₀)×r)로 회복량을 계산합니다. 요청 비용 c를 뺄 수 있으면 허용하고, 아니면 거부합니다. 이 계산을 요청 때 수행하면 매초 타이머로 저장소를 갱신하지 않아도 됩니다.
여러 서버에서 상태를 하나로 만들기
게이트웨이 인스턴스마다 자체 버킷을 두면 서버 수만큼 허용량이 늘어날 수 있습니다. 전역 한도를 원한다면 사용자나 API 키 같은 식별자로 공유 상태를 저장하고, 충전 계산과 차감을 경쟁 상태 없이 한 번에 처리해야 합니다. Redis 문서 예시는 Lua 스크립트로 이 작업을 원자적으로 수행하지만, 이는 한 가지 구현 패턴입니다. 다른 저장소의 원자 연산이나 트랜잭션도 설계에 따라 사용할 수 있습니다.
예를 들어 계정별 한도는 로그인 사용자 키로, 공개·비로그인 요청은 IP나 다른 식별자로 제한할 수 있습니다. NAT 뒤 여러 사람이 같은 IP를 공유할 수 있고, 공격자는 여러 IP를 사용할 수 있으므로 식별 기준 하나만으로 모든 남용을 막는다고 단정하지 마세요. 비용이 큰 API에는 요청 종류별 가중치도 고려합니다.
토큰 버킷과 리키 버킷은 목적이 다르다
토큰 버킷은 여유 토큰이 쌓인 시간 이후 버스트를 통과시킵니다. 리키 버킷을 큐로 구현하면 도착한 일을 일정한 속도로 내보내 부하를 평탄화할 수 있지만, 큐 길이와 대기 시간이 생깁니다. 즉시 거부하는 한도와 큐에 넣어 지연시키는 정책은 사용자 경험이 다릅니다. 어느 쪽이 맞는지는 백엔드가 버스트를 감당하는지와 작업을 늦게 처리해도 되는지로 결정합니다.
429 응답과 재시도
한도를 넘은 요청에 HTTP 429를 반환할 수 있습니다. RFC 6585는 Retry-After를 포함할 수 있다고 규정할 뿐, 모든 429 응답에 반드시 넣도록 요구하지 않습니다. 클라이언트는 헤더가 있으면 그 안내를 우선하고, 없을 때는 API 문서의 정책에 따라 제한된 지수 백오프와 지터를 적용할 수 있습니다. 재시도는 같은 과부하를 키우지 않도록 요청 종류와 중복 실행 안전성도 고려해야 합니다.