처리율 제한 장치 Rate Limiter 설계 정리
API를 운영하다 보면 특정 사용자가 짧은 시간 안에 너무 많은 요청을 보내는 상황이 발생할 수 있다. 예를 들어 SNS에서 하루에 작성할 수 있는 게시글 수를 제한하거나, 특정 API를 일정 시간 동안 정해진 횟수만 호출할 수 있게 제한하는 방식이 있다.
이처럼 처리율 제한 장치 Rate Limiter는 클라이언트 또는 사용자가 보낼 수 있는 요청의 수를 제어하는 장치다. 시스템을 과도한 트래픽으로부터 보호하고, 모든 사용자에게 안정적인 서비스를 제공하기 위해 사용된다.
처리율 제한이 필요한 이유
처리율 제한 장치는 단순히 요청을 막기 위한 기능이 아니다. 시스템 관점과 사용자 관점 모두에서 중요한 역할을 한다.
먼저 시스템 관점에서는 서버 자원을 보호할 수 있다. 특정 사용자가 비정상적으로 많은 요청을 보내면 서버, 데이터베이스, 외부 API 등이 과부하될 수 있다. 처리율 제한 장치는 이런 상황을 미리 차단해 장애 가능성을 줄인다.
사용자 관점에서도 필요하다. 요청이 거부되었을 때 아무런 설명 없이 실패하면 사용자는 원인을 알기 어렵다. 따라서 처리율 제한에 걸린 경우에는 일반적으로 HTTP 429 Too Many Requests 상태 코드를 반환하고, 언제 다시 요청할 수 있는지 알려주는 것이 좋다.
처리율 제한 장치는 어디에 둘까?
처리율 제한 장치를 어디에 둘지는 시스템 구조에 따라 달라진다.
클라이언트 측
클라이언트에 처리율 제한 로직을 둘 수도 있지만, 일반적으로 신뢰하기 어렵다. 클라이언트 코드는 사용자가 조작하거나 우회할 수 있고, 모든 클라이언트 구현을 서버가 완전히 통제하기 어렵기 때문이다.
따라서 클라이언트 측 제한은 보조적인 역할로는 사용할 수 있지만, 핵심적인 제한 로직을 두기에는 적절하지 않다.
서버 측
서버 측에서는 API 서버 앞단에 처리율 제한 미들웨어를 둘 수 있다. 클라이언트 요청이 API 서버에 도달하기 전에 미들웨어가 요청 수를 검사하고, 한도를 넘은 요청은 차단한다.
예를 들어 어떤 API가 초당 2개의 요청만 허용한다고 하자. 같은 초 안에 세 번째 요청이 들어오면 해당 요청은 API 서버까지 전달되지 않고 429 Too Many Requests 응답을 받게 된다.
API Gateway
MSA 구조에서는 처리율 제한 장치를 API Gateway에 두는 경우가 많다. API Gateway는 여러 마이크로서비스 앞단에서 공통 기능을 처리하는 계층이다.
API Gateway는 처리율 제한뿐 아니라 SSL 종료, 인증, IP 허용 목록 관리 같은 기능도 함께 담당할 수 있다. 특히 AWS API Gateway 같은 클라우드 관리형 서비스를 사용하면 직접 구현 부담을 줄일 수 있다.
처리율 제한 알고리즘
처리율 제한을 구현하는 대표적인 알고리즘은 다음과 같다.
- 토큰 버킷
- 누출 버킷
- 고정 윈도 카운터
- 이동 윈도 로그
- 이동 윈도 카운터
각 알고리즘은 장단점이 다르기 때문에 서비스 특성에 맞게 선택해야 한다.
1. 토큰 버킷 알고리즘
토큰 버킷은 가장 널리 사용되는 처리율 제한 알고리즘 중 하나다.
토큰 버킷에서는 일정 크기의 버킷에 토큰이 주기적으로 채워진다. 요청 하나를 처리하려면 토큰 하나가 필요하다. 요청이 들어왔을 때 버킷에 토큰이 있으면 토큰을 하나 꺼내고 요청을 통과시킨다. 토큰이 없다면 요청은 버려진다.
예를 들어 버킷 크기가 4이고, 1분마다 토큰이 4개씩 채워진다고 하자. 처음에는 4개의 요청을 처리할 수 있지만, 토큰이 모두 소진되면 이후 요청은 다음 토큰이 채워질 때까지 제한된다.
장점
구현이 비교적 쉽고 메모리 사용량도 적다. 또한 짧은 시간에 요청이 몰리는 burst 트래픽도 어느 정도 처리할 수 있다. 버킷에 토큰이 남아 있다면 순간적으로 많은 요청이 들어와도 처리할 수 있기 때문이다.
단점
버킷 크기와 토큰 공급률을 적절하게 정해야 한다. 이 값이 너무 작으면 정상 요청도 자주 제한되고, 너무 크면 제한 효과가 약해질 수 있다.
2. 누출 버킷 알고리즘
누출 버킷은 요청을 큐에 담아두고 일정한 속도로 처리하는 방식이다. 토큰 버킷과 비슷해 보이지만, 가장 큰 차이는 처리 속도가 고정되어 있다는 점이다.
요청이 들어오면 먼저 큐에 빈자리가 있는지 확인한다. 빈자리가 있으면 큐에 요청을 넣고, 큐가 가득 차 있으면 새 요청을 버린다. 이후 큐에 쌓인 요청은 지정된 속도로 하나씩 처리된다.
장점
출력 속도가 안정적이다. 따라서 요청을 일정한 속도로 처리해야 하는 시스템에 적합하다. 큐 크기가 제한되어 있어 메모리 사용량도 예측 가능하다.
단점
순간적으로 많은 트래픽이 몰리면 오래된 요청이 큐에 쌓이고, 최신 요청은 버려질 수 있다. 또한 큐 크기와 처리율을 적절히 조정해야 한다.
3. 고정 윈도 카운터 알고리즘
고정 윈도 카운터는 시간을 고정된 구간으로 나누고, 각 구간마다 요청 수를 세는 방식이다.
예를 들어 1분에 최대 5개의 요청만 허용한다고 하자. 2:00:00부터 2:01:00까지 하나의 윈도, 2:01:00부터 2:02:00까지 또 다른 윈도로 나누고, 각 윈도마다 카운터를 증가시킨다. 카운터가 임계치에 도달하면 해당 윈도 안의 추가 요청은 거부된다.
장점
구현이 쉽고 이해하기도 쉽다. 메모리 효율도 좋다.
단점
윈도 경계 부근에서 문제가 생길 수 있다. 예를 들어 2:00:59에 5개의 요청이 들어오고, 2:01:00에 다시 5개의 요청이 들어오면 실제로는 1초 사이에 10개의 요청이 처리될 수 있다. 즉, 기대한 제한보다 더 많은 요청이 통과할 수 있다.
4. 이동 윈도 로그 알고리즘
이동 윈도 로그는 고정 윈도 카운터의 경계 문제를 해결하기 위한 방식이다.
이 알고리즘은 각 요청의 타임스탬프를 저장한다. 새 요청이 들어오면 현재 윈도 범위 밖에 있는 오래된 타임스탬프를 제거하고, 남아 있는 요청 수를 확인한다. 요청 수가 한도 이하라면 새 요청을 허용하고 타임스탬프를 추가한다. 한도를 넘으면 요청을 거부한다.
장점
매우 정교하게 처리율을 제한할 수 있다. 어느 시점을 기준으로 보더라도 허용된 요청 수가 시스템 한도를 넘지 않는다.
단점
모든 요청의 타임스탬프를 저장해야 하므로 메모리 사용량이 많다. 특히 거부된 요청의 타임스탬프까지 저장한다면 부담이 더 커질 수 있다.
5. 이동 윈도 카운터 알고리즘
이동 윈도 카운터는 고정 윈도 카운터와 이동 윈도 로그의 장점을 결합한 방식이다.
현재 윈도와 직전 윈도의 요청 수를 함께 고려해 현재 시점의 요청 수를 추정한다. 예를 들어 현재 윈도가 30% 진행된 시점이라면, 직전 윈도의 70%와 현재 윈도의 요청 수를 합산해 제한 여부를 판단한다.
장점
고정 윈도 방식보다 트래픽 급증에 더 잘 대응할 수 있고, 이동 윈도 로그보다 메모리 효율이 좋다.
단점
직전 윈도의 요청이 균등하게 분포되어 있다고 가정하기 때문에 계산 결과가 실제 요청 분포와 완전히 일치하지 않을 수 있다. 다만 실제로는 이 오차가 크지 않은 경우가 많다.
개략적인 아키텍처
처리율 제한 장치의 핵심 아이디어는 간단하다.
“누가 얼마나 요청했는지”를 저장하고, 그 값이 한도를 넘으면 요청을 거부하는 것이다.
이때 카운터를 어디에 저장할지가 중요하다. 데이터베이스는 디스크 접근 비용이 크기 때문에 처리율 제한 장치에는 적합하지 않다. 대신 빠르고 만료 시간을 설정할 수 있는 인메모리 저장소가 적합하다.
대표적으로 Redis를 사용할 수 있다. Redis는 INCR 명령(지정된 키에 저장된 정수를 증가) 으로 카운터를 증가시킬 수 있고, EXPIRE 명령으로 일정 시간이 지나면 카운터를 자동 삭제할 수 있다.
일반적인 흐름은 다음과 같다:
- 클라이언트가 요청을 보낸다.
- 요청은 먼저 처리율 제한 미들웨어에 도착한다.
- 미들웨어는 Redis에서 해당 사용자 또는 IP의 카운터를 조회한다.
- 한도에 도달하지 않았다면 요청을 API 서버로 전달한다.
- 한도에 도달했다면 요청을 거부하고 429 Too Many Requests를 반환한다.
처리율 제한 규칙
처리율 제한 규칙은 보통 설정 파일 형태로 관리할 수 있다.
예를 들어 마케팅 메시지는 하루 5개까지만 보낼 수 있도록 제한하거나, 로그인 요청은 분당 5회까지만 허용하도록 제한할 수 있다.
이처럼 제한 기준은 서비스 도메인, API 종류, 사용자 유형, 요청 목적에 따라 다르게 설정할 수 있다.
제한된 요청은 어떻게 처리할까?
요청이 한도를 초과하면 일반적으로 HTTP 429 Too Many Requests 응답을 반환한다.
이때 클라이언트가 상황을 이해하고 적절히 대응할 수 있도록 응답 헤더를 함께 제공하는 것이 좋다.
대표적인 헤더는 다음과 같다.
- X-Ratelimit-Remaining: 현재 윈도 안에서 남은 요청 수
- X-Ratelimit-Limit: 윈도마다 허용되는 최대 요청 수
- X-Ratelimit-Retry-After: 몇 초 뒤 다시 요청하면 되는지
이런 정보를 제공하면 클라이언트는 무작정 재시도하지 않고 적절한 시간 이후 다시 요청할 수 있다.
경우에 따라 제한된 요청을 바로 버리지 않고 메시지 큐에 넣어 나중에 처리할 수도 있다. 예를 들어 주문 처리처럼 반드시 처리되어야 하는 요청이라면 큐에 저장하는 방식이 적합할 수 있다.
분산 환경에서 고려할 점
단일 서버에서는 처리율 제한 구현이 비교적 쉽다. 하지만 여러 대의 서버가 동시에 요청을 처리하는 분산 환경에서는 추가 문제가 생긴다.
경쟁 조건
여러 서버나 스레드가 동시에 같은 카운터 값을 읽고 증가시키면 잘못된 결과가 발생할 수 있다.
예를 들어 Redis에 저장된 카운터 값이 3이라고 하자. 두 요청이 동시에 이 값을 읽고 각각 1을 더하면 실제로는 5가 되어야 하지만, 둘 다 4로 저장해버릴 수 있다.
이를 해결하려면 Redis의 원자적 연산, Lua Script, Sorted Set 같은 방식을 사용할 수 있다.
- 현재 값 확인 > 제한을 넘었는지 판단 > 증가 > 만료시간 설정 처럼 여러 단계가 필요하다. 하지만 각각 처리하는 동안 중간에 다른 값이 변경되면 원자적이지 않을 수 있다.
- Redis의 INCR 연산은 Redis가 안전하게 하나씩 처리하는 원자적 연산이라, 여러 요청이 동시에 와도 순서대로 처리시켜준다.
- Lua Script는 이러한 여러 단계들을 Redis안에서 하나의 작업처럼 한 번에 실행하게 해준다. 앞에서 말했던 4가지의 연산들을 하나로 묶어서 실행시켜준다.
- Sorted At 은 Redis의 정렬된 자료구조 로, 처리율 제한에서는 주로 각 요청시간의 시간 기록을 저장할 때 사용한다. 예를 들어 최근 1분 동안 요청 5개까지만 허용이라면, 아래와 같이 저장할 수 있다.
user:123 요청 기록
- 19:00:01
- 19:00:10
- 19:00:30
- 새 요청이 오면 Redis에서 1분보다 오래된 기록을 지우고, 남은 요청 갯수를 세서 5개 미만이면 허용하고 5개 이상이면 거부할 수 있다
동기화 문제
처리율 제한 장치가 여러 대 있을 때, 같은 사용자의 요청이 매번 다른 제한 장치로 전달될 수 있다. 이때 각 장치가 서로 다른 상태를 가지고 있으면 정확한 제한이 어렵다.
이를 해결하기 위해 고정 세션을 사용할 수도 있지만 확장성과 유연성이 떨어진다. 더 일반적인 방법은 Redis 같은 중앙 저장소를 두고 모든 처리율 제한 장치가 같은 데이터를 참조하도록 하는 것이다.
성능 최적화와 모니터링
처리율 제한 장치는 요청 경로의 앞단에 위치하기 때문에 성능이 중요하다. 제한 장치 자체가 병목이 되면 안 된다.
성능 최적화를 위해서는 사용자와 가까운 위치에서 요청을 처리하는 것이 좋다. 예를 들어 여러 지역에 서버를 둔 서비스라면, 가까운 엣지 서버에서 처리율 제한을 수행해 지연 시간을 줄일 수 있다.
또한 처리율 제한 장치가 잘 동작하는지 모니터링해야 한다. 특히 다음 두 가지를 확인해야 한다.
- 선택한 알고리즘이 서비스 트래픽 패턴에 적합한가
- 설정한 제한 규칙이 너무 엄격하거나 너무 느슨하지 않은가
예를 들어 정상 사용자 요청이 지나치게 많이 차단된다면 제한 규칙을 완화해야 한다. 반대로 비정상 트래픽이 제대로 차단되지 않는다면 제한 기준을 강화해야 한다.
마무리
처리율 제한 장치는 시스템을 안정적으로 운영하기 위한 중요한 장치다. 단순히 요청을 막는 기능이 아니라, 서버 자원을 보호하고 사용자 간 공정성을 유지하며 장애를 예방하는 역할을 한다.
알고리즘마다 특징이 다르기 때문에 서비스의 트래픽 패턴에 맞게 선택해야 한다. 순간적인 트래픽을 어느 정도 허용해야 한다면 토큰 버킷이 적합하고, 일정한 처리 속도가 중요하다면 누출 버킷이 적합하다. 구현이 단순해야 한다면 고정 윈도 카운터를 사용할 수 있지만, 경계 문제를 고려해야 한다. 더 정교한 제한이 필요하다면 이동 윈도 로그나 이동 윈도 카운터를 선택할 수 있다.
결국 좋은 처리율 제한 설계는 “얼마나 막을 것인가”뿐만 아니라 “어디서 막을 것인가”, “초과 요청을 어떻게 알려줄 것인가”, “분산 환경에서 어떻게 일관성을 유지할 것인가”까지 함께 고려해야 한다.
'F-LAB' 카테고리의 다른 글
| Kafka에 대해서 (0) | 2026.09.16 |
|---|---|
| [가상 면접 사례로 배우는 대규모 시스템 설계 기초] 2장 (0) | 2026.08.31 |
| Redis SET NX와 MySQL Unique 제약을 활용한 TID 중복 처리 검증 (0) | 2026.08.25 |
| Reveal2021 - 쿠팡의 대규모 트래픽을 다루는 백앤드 전략 (0) | 2026.08.19 |
| 토스증권의 실시간 시세 적용기 정리 (0) | 2026.08.18 |
꾸준하면 언젠가
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!