본문으로 건너뛰기
개발 머꼬
개발 노트API 설계
hohyeon.dev25

요청을 제한하면서 클라이언트에게 언제 다시 오라고 말하지 않던 문제

  • #API 설계
  • #Engineering Note

문제 발생

외부 연동 API에 속도 제한을 걸었습니다. 초과하면 이렇게 응답했습니다.

HTTP/1.1 403 Forbidden

클라이언트 입장에서는 권한이 없는 것인지 잠시 뒤 다시 오면 되는지 알 수 없었습니다. 대부분의 클라이언트가 즉시 재시도했고, 제한을 건 의미가 사라졌습니다.

원인 분석

전용 상태 코드가 있습니다. RFC 6585의 정의는 한 문장입니다 — 429 상태 코드는 사용자가 주어진 시간 동안 너무 많은 요청을 보냈음을 나타낸다("rate limiting").

그리고 응답이 무엇을 담아야 하는지도 적혀 있습니다 — 응답 표현은 그 조건을 설명하는 세부 정보를 포함해야 하며(SHOULD), 새 요청을 보내기 전에 얼마나 기다려야 하는지를 나타내는 Retry-After 헤더를 포함할 수 있다(MAY).

Retry-After 자체는 RFC 9110에 정의돼 있습니다 — 사용자 에이전트가 후속 요청을 보내기 전에 얼마나 기다려야 하는지를 나타내며, 503과 3xx 리다이렉트 코드 응답에 적용된다. 429에 함께 쓰는 것이 RFC 6585가 안내하는 조합입니다.

명세는 구현 자유도도 명시합니다 — 이 명세는 원 서버가 사용자를 어떻게 식별하는지, 요청을 어떻게 세는지 정의하지 않습니다. 그리고 캐시 관련 제약도 있습니다 — 429 응답은 캐시에 저장되어서는 안 됩니다(MUST NOT).

해결 방안

  1. 상태 코드와 대기 시간을 함께 보냅니다.
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/problem+json

{ "type": "https://meokko.com/problems/rate-limited", "title": "요청이 너무 많습니다", "status": 429 }
  1. 왜 걸렸는지 설명합니다. 명세가 "조건을 설명하는 세부 정보"를 요구하는 이유는 클라이언트 개발자가 한도를 추측하지 않게 하기 위해서입니다. 분당 한도, 적용 범위(사용자/IP/엔드포인트)를 응답이나 문서에 드러냅니다.

  2. 남은 한도를 헤더로 알려주면 더 좋습니다. RateLimit-* 계열 헤더를 쓰면 클라이언트가 429를 맞기 전에 스스로 속도를 줄일 수 있습니다.

  3. 클라이언트 쪽에는 지수 백오프와 지터를 넣습니다. Retry-After를 존중하고, 없으면 백오프로 재시도합니다. 모든 클라이언트가 정확히 60초 뒤에 동시에 오면 그 순간 다시 무너집니다.

  4. 인증 실패와 구분합니다. 401/403은 "자격/권한" 문제이고 429는 "속도" 문제입니다. 섞어 쓰면 클라이언트의 재시도 정책이 틀어집니다.

  5. 정말 공격 상황에서는 다른 선택도 가능합니다. 명세도 서버가 이 코드를 반드시 쓸 필요는 없고 연결을 끊어도 된다고 적습니다 — 응답을 만드는 비용조차 아까운 상황이 있습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.