출시·고도화 중
레이트 리미터 안내서 · 1/6
레이트 리미터(rate limiter)는 정해진 시간 동안 받아들이는 요청 수를 제한하는 장치입니다. "한 사용자는 1분에 100번까지", "로그인 시도는 IP마다 1분에 5번까지"처럼 규칙을 정해 두고, 규칙을 넘는 요청은 거절하거나 늦춥니다. 이 장에서는 왜 요청을 제한하는지, 어떤 알고리즘들이 있는지, 그리고 이후 장에서 계속 쓰는 용어를 정리합니다.
서버의 자원은 한정되어 있습니다. 한 클라이언트가 짧은 시간에 요청을 쏟아 내면 다른 사용자의 응답이 느려지고, 심하면 서비스 전체가 멈춥니다. 레이트 리미터는 다음 문제를 막습니다.
| 용어 | 뜻 |
|---|---|
| 한도(limit) | 창 하나에서 허용하는 최대 요청 수 |
| 창(window) | 요청을 세는 시간 구간(예: 1초, 1분) |
| 키(key) | 한도를 따로 세는 단위(사용자 ID, API 키, IP 주소 등) |
| 버스트(burst) | 짧은 순간에 몰려 들어오는 요청 묶음 |
| 비율(rate) | 장기적으로 허용하는 평균 속도(예: 초당 10개) |
| 용량(capacity) | 토큰 버킷이 한 번에 담을 수 있는 최대 토큰 수, 곧 허용하는 최대 버스트 |
고정 창 카운터는 다음처럼 몇 줄이면 됩니다. 이후 장에서 이 단순한 방식의 약점을 하나씩 보완해 갑니다.
import time
from collections import defaultdict
LIMIT, WINDOW = 5, 60 # 1분에 5번
counters = defaultdict(int) # (키, 창 번호) -> 횟수
def allow(key: str) -> bool:
window_id = int(time.time() // WINDOW)
counters[(key, window_id)] += 1
return counters[(key, window_id)] <= LIMIT이 코드는 지난 창의 카운터를 지우지 않으므로 실제로는 오래된 항목을 정리하거나, Redis처럼 만료 시간을 줄 수 있는 저장소를 씁니다.
같은 알고리즘이라도 키를 무엇으로 잡느냐에 따라 효과가 크게 달라집니다.
user:42:POST /search처럼 여러 기준을 이어 붙여 세밀하게 나눕니다.프록시 뒤에 있는 서버라면 X-Forwarded-For 헤더를 믿을 수 있는지 확인해야 합니다. 이 헤더는 클라이언트가 마음대로 넣을 수 있으므로, 신뢰하는 프록시가 덧붙인 값만 써야 합니다.
def client_ip(remote_addr: str, forwarded_for: str | None,
trusted_proxies: set[str]) -> str:
# 직접 연결한 쪽이 우리 프록시일 때만 헤더를 믿는다
if forwarded_for and remote_addr in trusted_proxies:
hops = [h.strip() for h in forwarded_for.split(",")]
# 오른쪽부터 보며 신뢰하지 않는 첫 주소가 실제 클라이언트
for hop in reversed(hops):
if hop not in trusted_proxies:
return hop
return remote_addrHTTP에서 한도를 넘은 요청에는 보통 429 Too Many Requests 상태 코드를 돌려줍니다. 이 코드는 RFC 6585에 정의되어 있습니다. 함께 Retry-After 헤더로 몇 초 뒤에 다시 시도하면 되는지(또는 HTTP 날짜)를 알려 주면, 잘 만든 클라이언트는 그만큼 기다렸다가 재시도합니다. 많은 API는 남은 횟수와 초기화 시각도 헤더로 알려 줍니다(GitHub의 x-ratelimit-remaining 등).
HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json
{"error": "rate_limited", "message": "Too many requests. Retry in 12 seconds."}Retry-After로 클라이언트가 언제 다시 시도할지 알려 줍니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.