API 속도 제한 구현 방법: 완벽 가이드

Ashley Innocent

Ashley Innocent

13 March 2026

API 속도 제한 구현 방법: 완벽 가이드

TL;DR

토큰 버킷 또는 슬라이딩 윈도우 알고리즘을 사용하여 API 비율 제한을 구현하세요. 제한을 초과하면 표준 IETF 비율 제한 헤더(RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset)와 429 Too Many Requests를 반환합니다. Modern PetstoreAPI는 사용자별 할당량과 명확한 오류 응답으로 비율 제한을 구현합니다.

서론

한 클라이언트가 1분 동안 API에 10,000개의 요청을 보냅니다. 여러분의 데이터베이스가 충돌하고, 모니터링 알림이 울리며, 다른 고객들이 API에 접속할 수 없습니다. 공격을 받고 있거나, 단순히 재시도 루프에 빠진 버그 있는 클라이언트를 처리하고 있는 것일 수 있습니다.

비율 제한(Rate limiting)은 이를 방지합니다. 특정 시간 창 내에서 클라이언트가 만들 수 있는 요청 수를 제한합니다. 클라이언트가 이 제한을 초과하면 429 Too Many Requests를 반환합니다. 클라이언트는 요청을 중단하고, 여러분의 API는 건강한 상태를 유지합니다.

기존의 Swagger Petstore는 비율 제한을 전혀 구현하지 않습니다. Modern PetstoreAPI는 표준 IETF 헤더, 사용자별 할당량 및 명확한 오류 응답으로 비율 제한을 구현합니다.

💡
REST API를 구축하거나 테스트하는 경우, Apidog는 비율 제한 동작을 테스트하고, 비율 제한 헤더를 검증하며, API가 과도한 요청을 올바르게 처리하는지 확인하는 데 도움을 줍니다. 고볼륨 시나리오를 시뮬레이션하고 비율 제한 응답을 확인할 수 있습니다.
button

이 가이드에서는 비율 제한 알고리즘, 표준 헤더, 그리고 Modern PetstoreAPI가 비율 제한을 올바르게 구현하는 방법을 배울 것입니다.

API에 비율 제한이 필요한 이유

비율 제한은 API를 남용으로부터 보호하고 공정한 사용을 보장합니다.

남용으로부터 보호

1. 서비스 거부(DoS) 공격

공격자가 API에 요청을 쏟아부어 API를 사용할 수 없게 만듭니다. 비율 제한은 그들의 영향을 제한합니다.

2. 자격 증명 스터핑

공격자들이 수천 가지의 사용자 이름/비밀번호 조합을 시도합니다. 비율 제한은 그들의 속도를 늦춥니다.

3. 데이터 스크래핑

봇이 전체 데이터셋을 스크래핑합니다. 비율 제한은 스크래핑을 비실용적으로 만듭니다.

4. 비용 제어

API가 값비싼 서비스(AI 모델, 타사 API)를 호출하는 경우, 비율 제한은 제어 불능의 비용 발생을 방지합니다.

공정한 사용

1. 한 클라이언트가 리소스를 독점하는 것을 방지

비율 제한이 없으면 초당 1000개의 요청을 보내는 한 클라이언트가 다른 클라이언트의 리소스를 고갈시킬 수 있습니다.

2. 예측 가능한 성능

비율 제한은 모든 클라이언트에 대해 일관된 응답 시간을 보장합니다.

3. 계층별 접근

무료 계층: 시간당 100회 요청. 유료 계층: 시간당 10,000회 요청. 비율 제한은 이러한 계층을 강제합니다.

운영상의 이점

1. 용량 계획

API가 처리할 최대 부하를 알 수 있습니다.

2. 비용 예측 가능성

비율 제한은 인프라 비용을 제한합니다.

3. 점진적 성능 저하

부하가 걸렸을 때, 비율 제한은 연쇄적인 오류를 방지합니다.

비율 제한 알고리즘

다양한 알고리즘은 각기 다른 장단점을 가집니다.

1. 고정 창

고정된 시간 창에서 요청 수를 계산합니다.

작동 방식:

창 1 (00:00-00:59): 100개 요청 허용
창 2 (01:00-01:59): 100개 요청 허용

구현:

def is_allowed(user_id):
    current_minute = get_current_minute()
    key = f"rate_limit:{user_id}:{current_minute}"
    count = redis.incr(key)
    redis.expire(key, 60)
    return count <= 100

장점:

단점:

2. 슬라이딩 윈도우

롤링 시간 창에서 요청 수를 계산합니다.

작동 방식:

01:30에 00:30부터 01:30까지(지난 60분)의 요청을 계산합니다.

구현:

def is_allowed(user_id):
    now = time.time()
    window_start = now - 3600  # 1시간 전
    key = f"rate_limit:{user_id}"

    # 오래된 요청 제거
    redis.zremrangebyscore(key, 0, window_start)

    # 창 내 요청 수 계산
    count = redis.zcard(key)

    if count < 100:
        redis.zadd(key, {now: now})
        redis.expire(key, 3600)
        return True
    return False

장점:

단점:

3. 토큰 버킷

고정된 속도로 토큰이 버킷에 추가됩니다. 각 요청은 토큰 하나를 소모합니다.

작동 방식:

버킷 용량: 100개 토큰
충전 속도: 초당 10개 토큰
요청: 토큰 1개 소모

구현:

def is_allowed(user_id):
    now = time.time()
    key = f"rate_limit:{user_id}"

    # 현재 상태 가져오기
    data = redis.hgetall(key)
    tokens = float(data.get('tokens', 100))
    last_refill = float(data.get('last_refill', now))

    # 토큰 충전
    elapsed = now - last_refill
    tokens = min(100, tokens + elapsed * 10)  # 초당 10개 토큰

    if tokens >= 1:
        tokens -= 1
        redis.hset(key, 'tokens', tokens)
        redis.hset(key, 'last_refill', now)
        redis.expire(key, 3600)
        return True
    return False

장점:

단점:

4. 누출 버킷

요청이 대기열에 추가되고 고정된 속도로 처리됩니다.

작동 방식:

대기열 용량: 100개 요청
처리 속도: 초당 10개 요청

장점:

단점:

어떤 알고리즘을 사용해야 할까요?

대부분의 API의 경우: 토큰 버킷

업계 표준이며, 합리적인 버스트를 허용하고, 부드러운 비율 제한을 제공합니다.

Modern PetstoreAPI는 사용자별 할당량과 함께 토큰 버킷을 사용합니다.

표준 비율 제한 헤더

IETF 표준 헤더(draft-ietf-httpapi-ratelimit-headers)를 사용하세요.

표준 헤더

RateLimit-Limit: 시간 창 내에서 허용되는 최대 요청 수

RateLimit-Limit: 100

RateLimit-Remaining: 현재 창에서 남은 요청 수

RateLimit-Remaining: 45

RateLimit-Reset: 비율 제한이 초기화될 때까지 남은 시간(초)

RateLimit-Reset: 3600

응답 예시

GET /pets
200 OK
RateLimit-Limit: 100
RateLimit-Remaining: 99
RateLimit-Reset: 3600

{
  "data": [...]
}

레거시 헤더 (사용 중단됨)

많은 API가 비표준 헤더를 사용합니다:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1710331200

이것들은 사용하지 마세요. `X-` 접두사는 사용 중단되었으며, 형식도 표준화되지 않았습니다.

Modern PetstoreAPI가 비율 제한을 구현하는 방법

Modern PetstoreAPI는 표준 헤더와 함께 토큰 버킷 비율 제한을 구현합니다.

계층별 비율 제한

무료 계층:

프로 계층:

엔터프라이즈 계층:

구현

성공적인 요청:

GET /v1/pets
200 OK
RateLimit-Limit: 100
RateLimit-Remaining: 99
RateLimit-Reset: 3540

{
  "data": [...]
}

비율 제한 초과:

GET /v1/pets
429 Too Many Requests
Content-Type: application/problem+json
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 120
Retry-After: 120

{
  "type": "https://petstoreapi.com/errors/rate-limit-exceeded",
  "title": "Rate Limit Exceeded",
  "status": 429,
  "detail": "시간당 100회 요청 제한을 초과했습니다.",
  "instance": "/v1/pets",
  "retryAfter": 120,
  "limit": 100,
  "window": "1h"
}

사용자별 vs IP별

사용자별 (인증된 요청):

사용자 ID 또는 API 키로 비율을 제한합니다. 더 정확하고 공정합니다.

user_id = get_authenticated_user()
is_allowed(user_id)

IP별 (인증되지 않은 요청):

IP 주소로 비율을 제한합니다. 덜 정확하지만(공유 IP, VPN) 없는 것보다는 낫습니다.

ip_address = request.remote_addr
is_allowed(ip_address)

Modern PetstoreAPI는 인증된 요청에 대해 사용자별 비율 제한을 사용하고, 공개 엔드포인트에 대해서는 IP별 비율 제한을 사용합니다.

비율 제한 응답 형식

비율 제한이 초과되면 RFC 9457 오류 형식으로 429를 반환합니다.

응답 구조

{
  "type": "https://petstoreapi.com/errors/rate-limit-exceeded",
  "title": "Rate Limit Exceeded",
  "status": 429,
  "detail": "비율 제한을 초과했습니다. 나중에 다시 시도해 주세요.",
  "instance": "/v1/pets",
  "retryAfter": 120,
  "limit": 100,
  "remaining": 0,
  "reset": 120,
  "window": "1h"
}

헤더

429 Too Many Requests
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 120
Retry-After: 120

Retry-After: 클라이언트에게 언제 다시 시도해야 하는지 알려줍니다 (초 단위).

Apidog로 비율 제한 테스트하기

Apidog는 비율 제한 동작을 테스트하는 데 도움을 줍니다.

테스트 시나리오

1. 일반적인 사용:

50개 요청 전송 → 모두 성공
RateLimit-Remaining 감소 확인

2. 제한 초과:

101개 요청 전송 → 101번째에서 429 반환
오류 응답 형식 확인
Retry-After 헤더 확인

3. 초기화 동작:

제한 초과 → 초기화될 때까지 대기 → 제한 복원 확인

4. 다른 계층:

무료 계층 (시간당 100회) 테스트
프로 계층 (시간당 10,000회) 테스트
제한이 올바르게 적용되는지 확인

Apidog 테스트 예시

// 비율 제한 헤더 테스트
pm.test("Rate limit headers present", () => {
  pm.response.to.have.header("RateLimit-Limit");
  pm.response.to.have.header("RateLimit-Remaining");
  pm.response.to.have.header("RateLimit-Reset");
});

// 비율 제한 초과 테스트
pm.test("Returns 429 when limit exceeded", () => {
  // 101개 요청 보내기
  for (let i = 0; i < 101; i++) {
    pm.sendRequest("GET /v1/pets");
  }
  pm.response.to.have.status(429);
});

비율 제한 모범 사례

1. 표준 헤더 사용

사용자 지정 `X-` 헤더 대신 IETF 표준 헤더를 사용하세요.

2. 403 대신 429 반환

429는 "요청이 너무 많음"을 의미합니다. 403은 "금지됨"을 의미합니다. 이들을 혼동하지 마세요.

3. Retry-After 포함

클라이언트에게 언제 다시 시도할 수 있는지 알려주세요.

4. 제한 사항 문서화

문서에 비율 제한을 명확히 표시하세요.

5. 다른 계층 제공

무료 계층: 낮은 제한. 유료 계층: 높은 제한.

6. IP가 아닌 사용자별로 비율 제한

사용자별 제한이 더 정확하고 공정합니다.

7. 버스트 허용

토큰 버킷은 정상적인 사용을 방해하지 않으면서 합리적인 버스트를 허용합니다.

8. 비율 제한 초과 모니터링

클라이언트가 비율 제한에 도달하는 빈도를 추적하세요. 높은 비율은 문제를 나타냅니다.

9. 비율 제한 상태 엔드포인트 제공

GET /v1/rate-limit
200 OK
{
  "limit": 100,
  "remaining": 45,
  "reset": 3540
}

10. 비율 제한 테스트

배포 전에 Apidog를 사용하여 비율 제한 동작을 테스트하세요.

결론

비율 제한은 API를 남용으로부터 보호하고 공정한 사용을 보장합니다. 표준 IETF 헤더(RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset)와 함께 토큰 버킷 알고리즘을 사용하세요. 제한을 초과하면 RFC 9457 오류 형식으로 429 Too Many Requests를 반환합니다.

Modern PetstoreAPI는 사용자별 할당량, 표준 헤더 및 명확한 오류 응답으로 비율 제한을 올바르게 구현합니다. 구현 세부 정보는 문서를 확인하세요.

Apidog로 비율 제한을 테스트하여 부하 상태에서 올바르게 작동하고 엣지 케이스를 적절히 처리하는지 확인하세요.

button

자주 묻는 질문 (FAQ)

어떤 비율 제한을 설정해야 하나요?

무료 계층의 경우 시간당 100회, 유료 계층의 경우 시간당 10,000회와 같이 보수적으로 시작하세요. 사용 패턴과 인프라 용량에 따라 조정하세요.

IP별로 비율을 제한해야 하나요, 사용자별로 제한해야 하나요?

인증된 요청의 경우 사용자(API 키)별로 비율을 제한하세요. 공개 엔드포인트에 대해서만 IP 기반 비율 제한을 사용하세요.

클라이언트가 비율 제한을 초과하면 어떻게 되나요?

Retry-After 헤더와 함께 429 Too Many Requests를 반환하세요. 클라이언트를 영구적으로 차단하지 말고, 창이 초기화된 후 다시 시도할 수 있도록 하세요.

웹훅의 비율 제한은 어떻게 처리해야 하나요?

웹훅은 서버 대 서버이므로 비율 제한이 더 높아야 합니다. 웹훅과 API 호출에 대해 별도의 제한을 고려하세요.

내부 서비스도 비율 제한을 해야 하나요?

예, 하지만 훨씬 더 높은 제한으로 설정해야 합니다. 비율 제한은 내부 시스템에서도 연쇄적인 오류를 방지합니다.

비율 제한은 어떻게 테스트하나요?

Apidog를 사용하여 여러 요청을 보내고 429 응답, 비율 제한 헤더 및 초기화 동작을 확인하세요.

API가 CDN 뒤에 있으면 어떻게 되나요?

CDN 캐싱은 부하를 줄여주지만, 캐시 미스 및 POST/PUT/DELETE 요청에 대해서는 여전히 비율 제한이 필요합니다.

여러 서버에서 비율 제한을 구현하려면 어떻게 해야 하나요?

모든 서버에서 비율 제한을 추적하기 위해 공유 데이터 저장소(Redis, Memcached)를 사용하세요. 분산 시스템에서는 작동하지 않으므로 로컬 메모리를 사용하지 마세요.

Apidog에서 API 설계-첫 번째 연습

API를 더 쉽게 구축하고 사용하는 방법을 발견하세요