업데이트됨 1개월 전
여러분의 API는 공유 자원입니다. 모든 요청은 CPU 사이클, 데이터베이스 연결, 메모리, 대역폭을 소비합니다. 제한이 없다면, 악의적이거나 단순히 잘못 짜인 클라이언트 하나가 모든 자원을 독점하여 다른 사람들에게 아무것도 남기지 않을 수 있습니다.
Rate limiting이 이 문제를 해결합니다. 문 앞의 경비원처럼, 각 클라이언트가 얼마나 많은 요청을 할 수 있는지, 그리고 허용량을 넘었을 때 어떻게 되는지를 결정합니다.
Rate Limiting이 해결하는 문제
한 개발자가 여러분의 API에서 데이터를 가져오는 스크립트를 작성한다고 상상해 보세요. 요청 사이에 딜레이를 넣는 것을 깜빡합니다. 스크립트가 무한 루프로 돌면서 초당 수천 건의 요청을 보냅니다. 순식간에 데이터베이스 연결 풀이 바닥납니다. API 서버는 과부하가 걸립니다. API를 쓰는 다른 모든 사용자에게 타임아웃과 오류가 쏟아집니다.
악의는 없습니다—그냥 버그입니다. 하지만 결과만 보면 서비스 거부 공격과 다를 바 없습니다.
Rate limiting은 이런 상황이 벌어지기 전에 차단합니다. 처음 백 건의 요청은 통과됩니다. 101번째 요청은 명확한 메시지와 함께 거부됩니다: "속도를 줄이세요. 60초 후에 다시 시도하세요."
잘못 짜인 스크립트는 금방 실패합니다. 개발자는 버그를 고칩니다. 다른 사용자들은 아무것도 눈치채지 못합니다.
모든 공개 API에 이것이 필요한 이유
인프라 보호가 가장 명확한 이유입니다. 나쁜 행위자 하나—또는 나쁜 스크립트 하나—가 서비스 전체를 다운시킬 수 있어서는 안 됩니다.
공정성이 더 근본적인 이유입니다. API의 용량은 유한합니다. 한 사용자가 무제한으로 자원을 쓸 수 있다면, 그건 다른 모든 사람의 몫을 빼앗는 것입니다. Rate limiting은 자원이 공평하게 분배되도록 합니다.
비용 통제는 API가 외부 서비스를 호출하거나, 무거운 쿼리를 실행하거나, 클라우드 함수를 트리거할 때 중요합니다. 제한이 없다면, 클라이언트가 폭주하는 동안 누군가 알아채기도 전에 청구서가 천문학적으로 불어날 수 있습니다.
비즈니스 모델도 자연스럽게 따라옵니다. 무료 플랜: 시간당 100건. 유료 플랜: 시간당 10,000건. 엔터프라이즈: 협의합시다. Rate limiting은 단순한 보호 수단이 아닙니다—그 자체가 제품입니다.
알고리즘
가장 단순한 방식에는 명확한 허점이 있습니다. 그 허점이 더 나은 방식들을 낳았습니다. 이 발전 과정을 이해하면 적합한 알고리즘을 고르는 데 도움이 됩니다.
고정 윈도우
시간 윈도우당 N건의 요청을 허용합니다. 윈도우가 초기화되면 카운터도 0으로 돌아갑니다.
분당 100건. 10:00:00에 카운터는 0입니다. 10:00:45에 사용자는 95건을 요청했습니다. 5건을 더 보냅니다. 제한에 도달했습니다. 10:01:00까지 기다리면 카운터가 초기화됩니다.
단순합니다. 이해하기 쉽습니다. 구현하기도 쉽습니다.
허점이 있습니다: 클라이언트가 10:00:59에 100건을 요청합니다. 10:01:00에 윈도우가 초기화됩니다. 10:01:01에 100건을 더 보냅니다. "분당 100건" 제한임에도 2초 만에 200건이 통과됩니다.
경계 문제입니다. 실제 제한이 생각한 것과 다릅니다.
슬라이딩 윈도우
고정된 경계 대신, 현재 시점에서 정확히 한 윈도우 길이만큼 과거를 돌아봅니다.
10:00:45에, 09:59:45부터 10:00:45 사이의 요청을 모두 셉니다. 100건 미만이면 요청을 허용합니다.
경계 문제가 사라집니다. "분당 100건"이 진정으로 어떤 60초 구간에서든 100건을 의미하게 됩니다.
단점은 모든 요청의 타임스탬프를 저장해야 한다는 것입니다. 메모리 사용량이 늘어납니다.
영리한 근사법도 있습니다: 현재와 이전 윈도우의 카운터를 유지하고, 현재 윈도우를 얼마나 진행했는지에 따라 가중 평균을 계산합니다. 메모리는 적게 쓰면서 거의 동일한 정확도를 얻습니다.
토큰 버킷
토큰이 담긴 버킷을 상상해 보세요. 버킷은 최대 용량이 있고 일정한 속도로 채워집니다. 요청 하나당 토큰 하나를 씁니다. 버킷이 비어 있으면 요청이 거부됩니다.
용량: 100개. 충전 속도: 초당 10개.
사용자가 빠르게 50건을 요청해 토큰 50개를 씁니다. 5초를 기다립니다. 버킷에 토큰 50개가 다시 채워집니다. 다시 몰아서 요청할 수 있습니다.
대부분의 프로덕션 API가 이 알고리즘을 사용합니다. 버스트(버킷 용량까지)를 허용하면서도 장기적으로는 평균 속도를 강제합니다. 트래픽은 원래 들쑥날쑥합니다. 토큰 버킷은 이를 자연스럽게 수용합니다.
누수 버킷
요청이 대기열에 쌓입니다. 대기열은 고정된 속도로 처리됩니다. 대기열이 가득 차면 새 요청이 거부됩니다.
입력이 얼마나 불규칙해도 출력 속도는 완벽하게 균일합니다. 그러나 요청이 대기열에서 기다리는 만큼 지연이 발생합니다. 대부분의 API에서는 그 균일함이 지연을 감수할 만한 가치가 없습니다.
무엇을 제한할 것인가
제한은 종종 여러 범위를 조합해 적용할 수 있습니다:
API 키별: 각 애플리케이션마다 독립적인 제한을 갖습니다. 한 앱의 버그가 다른 앱에 영향을 주지 않습니다.
사용자별: 여러 앱이 서로 다른 사용자를 대신해 API에 접근할 때, 각 사용자가 자신만의 제한을 갖습니다.
IP 주소별: 인증이 이루어지기 전에도 공격을 막을 수 있습니다.
엔드포인트별: 무거운 작업에는 더 엄격한 제한을, 가벼운 작업에는 더 넉넉한 제한을 줍니다. 복잡한 검색은 분당 10건, 단순 조회는 1,000건.
전역: 개별 제한과 무관하게 전체 시스템 부하에 상한선을 둡니다.
일반적인 구성: API 키당 시간당 1,000건 AND IP 주소당 초당 10건. 이중 안전망입니다.
클라이언트에게 상황 알리기
클라이언트는 제한이 얼마인지, 얼마나 썼는지, 언제 초기화되는지를 알아야 합니다. HTTP 헤더가 표준 방식입니다:
제한이 초과되면 상태 코드 429(Too Many Requests)를 Retry-After 헤더와 함께 반환합니다:
좋은 오류 메시지는 그 자체로 기능입니다. 새벽 2시에 디버깅하는 개발자가 고마워할 겁니다.
구현
서버가 하나라면 인메모리 카운터로 충분합니다. 빠르고 단순합니다. 그러나 서버가 여러 대라면 각각 자체 카운터를 갖게 됩니다—클라이언트가 서로 다른 서버를 번갈아 공략하면 제한을 우회할 수 있습니다.
다중 서버 환경에서는 Redis가 정답입니다. 모든 서버가 같은 카운터를 공유합니다. 원자적 증가 연산이 경쟁 조건을 막아줍니다. Rate limiting 확인이 추가하는 지연은 무시할 수 있는 수준입니다.
API 게이트웨이(Kong, AWS API Gateway, Cloudflare)는 요청이 애플리케이션에 닿기 전에 rate limiting을 처리합니다. 거부된 요청이 서버까지 오지 않으니 효율적이고, 로직도 한 곳에 집중됩니다.
적절한 수치 설정
너무 엄격하면: 정상적으로 사용하는 사용자도 제한에 걸립니다. 불만이 쌓입니다. 문의가 늘어납니다. 사용자가 떠납니다.
너무 관대하면: 제한이 아무런 보호도 하지 못합니다. 오작동하는 클라이언트가 여전히 문제를 일으킬 수 있습니다.
실제 사용 패턴 분석부터 시작하세요. 상위 95번째 백분위 사용자는 어떻게 쓰고 있나요? 정상적인 사용보다는 여유 있게, 하지만 사용자 한 명이 문제를 일으킬 수 있는 수준보다는 낮게 제한을 설정하세요.
그다음은 모니터링입니다. 여러 클라이언트에서 429 오류가 많이 발생하면 제한이 너무 빡빡하다는 신호입니다. 한 클라이언트에서만 집중적으로 발생하면 그 클라이언트가 코드를 고쳐야 한다는 뜻입니다—아니면 플랜을 업그레이드하거나.
고급 패턴
적응형 제한은 시스템 부하에 따라 동적으로 조정됩니다. 시스템이 여유로울 때는 넉넉하게. 한계에 다가설 때는 엄격하게. 보호를 유지하면서 처리량을 극대화하는 방식입니다.
비용 기반 제한은 작업마다 다른 비용을 매깁니다. 단순 조회는 1포인트. 복잡한 집계는 10포인트. 사용자는 요청 횟수 대신 포인트 예산을 받습니다. 이 방식이 더 공정합니다—가벼운 요청과 무거운 요청이 왜 똑같이 취급받아야 하나요?
평판 기반 제한은 오래된 사용자에게 신규 계정보다 더 많은 여유를 줍니다. 신뢰는 쌓아가는 것입니다.
Rate Limiting vs. 스로틀링
Rate limiting은 제한을 초과한 요청을 거부합니다. 클라이언트는 429를 받고 다음 행동을 결정합니다.
스로틀링은 요청을 대기열에 넣고 허용된 속도로 처리합니다. 클라이언트는 기다립니다.
대부분의 API는 rate limiting을 씁니다. 스로틀링은 타임아웃을 유발할 수 있고, 클라이언트 입장에서 예측하기가 더 어렵습니다. 무기한 기다리게 하는 것보다, 명확한 메시지와 함께 빠르게 실패하는 편이 낫습니다.
API Rate Limiting 자주 묻는 질문
Rate limiting 대신 그냥 서버를 더 늘리면 안 되나요?
서버 확장은 정상적인 부하 증가에 대응합니다. Rate limiting은 남용, 버그, 공격에 대응합니다. 봇넷이나 잘못 설정된 스크립트는 어떤 자동 확장 시스템도 따라잡을 수 없는 속도로 부하를 만들어냅니다. Rate limiting은 상황을 파악하는 동안 즉각적으로 피해 확산을 막아줍니다.
고정 윈도우와 토큰 버킷 중 어떻게 선택하나요?
프로덕션 API라면 토큰 버킷이 거의 항상 더 나은 선택입니다. 고정 윈도우는 구현과 설명이 더 단순하지만, 경계 문제 때문에 실제 제한이 약속한 것과 달라집니다. 토큰 버킷은 진정한 평균 속도를 강제하면서 버스트 트래픽도 자연스럽게 처리합니다.
인증된 요청과 인증되지 않은 요청에 다른 제한을 적용해야 하나요?
그렇습니다. 인증 없는 엔드포인트는 인터넷 전체에 노출됩니다—누구든 접근할 수 있습니다. 그 제한은 엄격하게 유지하세요(IP당 시간당 10~50건 정도). 인증된 요청은 어느 정도 신원이 확인된 사용자로부터 오는 것입니다. 그 제한은 훨씬 높게 설정할 수 있습니다.
반복적으로 rate limit을 위반하면 어떻게 해야 하나요?
단계적으로 대응하는 것이 합리적입니다. 첫 위반: 재시도 시간과 함께 429 반환. 짧은 시간 내 반복 위반: 더 긴 대기 시간 부과. 지속적인 남용: 일시적 차단. 이 방식은 실수(금방 멈추는)와 악의적 행동(차단되는) 모두에 적절히 대응합니다.
이 페이지가 도움이 되었나요?