대규모 시스템 설계 스터디 04. 처리율 제한 장치
처리율 제한 장치는 사용자가 일정 시간 안에 보낼 수 있는 요청 수를 제한하는 시스템입니다. 로그인 API를 1분에 5번만 허용하거나, 특정 사용자의 요청을 초당 100개까지만 받는 방식이 대표적입니다.
4장은 처리율 제한 장치를 설계하는 방법을 다룹니다. 어디에 둘지, 어떤 알고리즘을 선택할지, Redis 같은 저장소로 카운터를 어떻게 관리할지, 여러 서버가 동시에 동작할 때 어떤 문제가 생기는지 확인합니다.
핵심은 “요청을 막는다”가 아닙니다. 정상 사용자는 불편하지 않게 두면서, 폭주 요청과 악성 요청이 전체 시스템을 망가뜨리지 못하게 만드는 것입니다.
먼저 알아야 할 기본 용어
| 용어 | 뜻 |
|---|---|
| 처리율 | 일정 시간 동안 처리하는 요청 수입니다. 초당 100건, 분당 1,000건처럼 표현합니다. |
| Rate Limiter | 처리율 제한 장치입니다. 정해진 한도를 넘는 요청을 거부하거나 지연시킵니다. |
| API | 클라이언트가 서버 기능을 호출하는 약속된 요청 경로입니다. 로그인 API, 결제 API가 예시입니다. |
| DoS | Denial of Service의 약자입니다. 과도한 요청으로 서비스를 사용할 수 없게 만드는 공격 또는 장애 상황입니다. |
| Burst | 짧은 시간에 요청이 몰리는 현상입니다. 평균 요청 수는 낮아도 순간 요청 수가 높을 수 있습니다. |
| API Gateway | 여러 API 앞단에서 인증, 로깅, 라우팅, 처리율 제한 등을 담당하는 관문입니다. |
| Redis | 메모리 기반 저장소입니다. 빠른 카운터 증가와 만료 처리가 필요할 때 자주 사용합니다. |
| HTTP 429 | Too Many Requests를 뜻하는 HTTP 상태 코드입니다. 요청이 너무 많아 거부되었음을 알립니다. |
처리율 제한은 비용과 장애를 함께 줄입니다
처리율 제한 장치가 필요한 이유는 크게 세 가지입니다.
| 이유 | 설명 |
|---|---|
| 서비스 보호 | 갑작스러운 요청 폭주가 서버와 데이터베이스를 무너뜨리지 않게 합니다. |
| 비용 통제 | 외부 API, 검색, 이미지 처리처럼 호출 비용이 큰 기능의 사용량을 제한합니다. |
| 공정 사용 | 한 사용자가 전체 자원을 독점하지 못하게 합니다. |
로그인 API를 예로 들면 더 분명합니다. 한 사용자가 1분에 5번 로그인 시도하는 것은 자연스러울 수 있습니다. 하지만 같은 계정으로 1분에 10,000번 시도한다면 공격이나 자동화 요청일 가능성이 높습니다.
처리율 제한 장치는 이런 요청을 초기에 막아 서버, 데이터베이스, 외부 인증 시스템을 보호합니다.
위치는 클라이언트보다 서버 앞단이 안전합니다
처리율 제한 장치를 어디에 둘지도 설계 선택입니다.
클라이언트 -> 처리율 제한 장치 -> API 서버 -> 데이터베이스
선택지는 세 가지로 나눌 수 있습니다.
| 위치 | 장점 | 한계 |
|---|---|---|
| 클라이언트 | 사용자에게 빠르게 제한 상태를 보여줄 수 있습니다. | 사용자가 조작할 수 있으므로 신뢰할 수 없습니다. |
| 서버 미들웨어 | 서비스별 규칙을 세밀하게 적용할 수 있습니다. | 여러 서비스에 같은 기능이 중복될 수 있습니다. |
| API Gateway | 인증, 로깅, 라우팅과 함께 중앙에서 관리할 수 있습니다. | Gateway 자체의 운영과 장애 대응이 필요합니다. |
실무에서는 API Gateway나 서버 앞단 미들웨어를 많이 사용합니다. 클라이언트 제한은 사용자 경험을 돕는 보조 장치로는 쓸 수 있지만, 보안 기준으로 신뢰하기는 어렵습니다.
Envoy, Kong, AWS API Gateway 같은 도구는 처리율 제한 기능을 제공합니다. 직접 구현하기 전에 기존 인프라가 제공하는 기능을 먼저 확인하는 편이 좋습니다.
토큰 버킷은 순간적인 요청 몰림을 허용합니다
토큰 버킷은 가장 널리 쓰이는 방식 중 하나입니다.
동작은 단순합니다. 버킷에 토큰이 일정 속도로 채워집니다. 요청 1개가 들어오면 토큰 1개를 소비합니다. 토큰이 있으면 요청을 통과시키고, 토큰이 없으면 요청을 거부합니다.
버킷 크기: 10개
토큰 공급률: 초당 2개
요청 1개 처리: 토큰 1개 소비
토큰 버킷에는 두 가지 파라미터가 있습니다.
| 파라미터 | 뜻 |
|---|---|
| 버킷 크기 | 한 번에 모아둘 수 있는 최대 토큰 수입니다. |
| 공급률 | 일정 시간마다 새로 채우는 토큰 수입니다. |
장점은 burst를 허용한다는 점입니다. 평소 요청이 적어서 토큰이 쌓여 있었다면, 순간적으로 요청이 몰려도 일정 범위 안에서는 처리할 수 있습니다.
단점은 파라미터를 잘못 잡으면 제한이 너무 느슨하거나 너무 빡빡해질 수 있다는 점입니다. 버킷 크기가 크면 순간 요청이 많이 통과합니다. 공급률이 높으면 장기 평균 처리량이 커집니다.
누출 버킷은 출력 속도를 일정하게 만듭니다
누출 버킷은 요청을 큐에 넣고 일정한 속도로 처리하는 방식입니다.
큐는 먼저 들어온 요청을 먼저 처리하는 줄입니다. FIFO는 First In, First Out의 약자이며, 먼저 들어온 것이 먼저 나간다는 뜻입니다.
요청 -> FIFO 큐 -> 고정 속도로 처리
누출 버킷은 서버 입장에서 예측하기 쉽습니다. 들어오는 요청이 불규칙해도 나가는 요청은 일정하기 때문입니다.
하지만 단점도 있습니다. 큐가 가득 찬 상태에서 새 요청이 들어오면 버려집니다. 또한 사용자가 짧은 시간에 몰아서 보낸 요청은 큐에서 대기해야 하므로 응답이 늦어질 수 있습니다.
따라서 누출 버킷은 “순간 요청을 허용하기보다 안정적인 처리율을 유지하는 것”이 더 중요할 때 적합합니다.
고정 윈도 카운터는 단순하지만 경계 문제가 있습니다
고정 윈도 카운터는 시간을 고정 구간으로 나눕니다. 예를 들어 1분 단위로 나누고, 각 1분 동안 들어온 요청 수를 셉니다.
10:00:00 ~ 10:00:59 -> 최대 5건
10:01:00 ~ 10:01:59 -> 최대 5건
구현은 쉽습니다. Redis에서 카운터를 증가시키고, 1분 뒤 만료되게 하면 됩니다.
INCR rate:user:123:10:00
EXPIRE rate:user:123:10:00 60
문제는 경계입니다. 1분에 5건만 허용한다고 가정하겠습니다. 사용자가 10:00:58에 5건을 보내고, 10:01:01에 다시 5건을 보내면 3초 안에 10건이 통과합니다.
규칙상 각 1분 창에서는 5건을 넘지 않았지만, 실제 사용자 경험에서는 짧은 시간에 너무 많은 요청이 지나갔습니다. 이것이 고정 윈도 방식의 경계 문제입니다.
이동 윈도 로그는 정확하지만 메모리를 많이 씁니다
이동 윈도 로그는 요청이 들어올 때마다 타임스탬프를 저장합니다. 타임스탬프는 요청이 들어온 시간을 기록한 값입니다.
요청이 들어오면 현재 시각 기준으로 최근 1분 안에 들어온 요청만 세고, 오래된 기록은 제거합니다.
현재 시각: 10:01:10
검사 범위: 10:00:10 ~ 10:01:10
이 방식은 정확합니다. 고정 윈도처럼 1분 경계에서 요청이 몰리는 문제를 줄일 수 있습니다.
단점은 저장 공간입니다. 요청이 많으면 사용자별로 많은 타임스탬프를 저장해야 합니다. 거부된 요청까지 기록해야 정책을 정확히 적용할 수 있습니다.
정확성이 매우 중요하고 요청량이 감당 가능한 수준이라면 사용할 수 있습니다. 하지만 대규모 트래픽에서는 메모리 비용이 부담될 수 있습니다.
이동 윈도 카운터는 정확성과 비용 사이의 절충안입니다
이동 윈도 카운터는 고정 윈도 카운터와 이동 윈도 로그의 중간입니다.
이전 윈도와 현재 윈도의 카운터를 사용하되, 현재 시점에 겹치는 비율을 계산합니다.
예상 요청 수 = 현재 윈도 요청 수 + 이전 윈도 요청 수 x 겹치는 비율
예를 들어 현재 윈도 요청이 3건이고, 이전 윈도 요청이 5건이며, 이전 윈도와 겹치는 비율이 60%라면 다음처럼 계산합니다.
3 + 5 x 0.6 = 6
이 방식은 이동 윈도 로그만큼 모든 요청 기록을 저장하지 않아도 됩니다. 동시에 고정 윈도 카운터보다 경계 문제를 줄일 수 있습니다.
그래서 정확성과 메모리 효율의 균형이 필요할 때 적합합니다.
분산 환경에서는 카운터가 원자적으로 증가해야 합니다
서버 한 대에서만 처리율 제한을 한다면 구조가 단순합니다. 하지만 실제 서비스는 처리율 제한 서버도 여러 대일 수 있습니다.
이때 두 가지 문제가 생깁니다.
| 문제 | 설명 | 대응 |
|---|---|---|
| 경쟁 조건 | 두 요청이 동시에 같은 카운터를 읽고 모두 통과할 수 있습니다. | Redis Lua 스크립트처럼 읽기와 쓰기를 한 번에 처리하는 방법을 씁니다. |
| 상태 불일치 | 서버마다 카운터가 다르면 사용자가 어느 서버로 가느냐에 따라 제한 결과가 달라집니다. | 중앙 저장소를 두고 모든 서버가 같은 카운터를 보게 합니다. |
원자적이라는 말은 중간에 끼어들 수 없는 하나의 작업으로 처리된다는 뜻입니다. “카운터를 읽고, 한도와 비교하고, 증가시키는 과정”이 나뉘어 있으면 동시에 들어온 요청 때문에 오류가 생길 수 있습니다.
Redis Lua 스크립트는 이 과정을 한 번에 실행하게 해 줍니다. 그래서 분산 환경의 경쟁 조건을 줄이는 데 자주 사용합니다.
거부된 요청은 HTTP 429로 명확히 알려야 합니다
요청을 거부할 때는 사용자나 클라이언트가 이유를 알 수 있어야 합니다. 이때 HTTP 429 Too Many Requests를 사용합니다.
응답 헤더에는 현재 제한 상태를 함께 보낼 수 있습니다.
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Retry-After: 30
각 헤더의 의미는 다음과 같습니다.
| 헤더 | 뜻 |
|---|---|
X-RateLimit-Limit | 허용된 전체 요청 수입니다. |
X-RateLimit-Remaining | 남은 요청 수입니다. |
X-RateLimit-Retry-After | 몇 초 뒤 다시 시도할 수 있는지 알려 줍니다. |
이 정보를 주면 클라이언트는 무작정 재시도하지 않고 기다렸다가 다시 요청할 수 있습니다. 처리율 제한은 서버만 보호하는 기능이 아니라, 클라이언트가 올바르게 행동할 수 있게 안내하는 계약이기도 합니다.
알고리즘 선택은 트래픽 성격에 맞춰야 합니다
5가지 알고리즘은 우열 관계보다 목적이 다릅니다.
| 상황 | 적합한 방식 |
|---|---|
| 순간 요청 몰림을 어느 정도 허용해야 합니다 | 토큰 버킷 |
| 일정한 출력 속도가 중요합니다 | 누출 버킷 |
| 구현 단순성이 가장 중요합니다 | 고정 윈도 카운터 |
| 정확한 요청 기록이 필요합니다 | 이동 윈도 로그 |
| 정확성과 메모리 비용의 균형이 필요합니다 | 이동 윈도 카운터 |
처리율 제한 장치를 설계할 때는 먼저 정책을 정해야 합니다. 사용자별로 제한할지, IP별로 제한할지, API별로 제한할지, 로그인 같은 민감한 기능에만 강하게 적용할지 결정해야 합니다.
그다음 알고리즘을 선택합니다. 마지막으로 분산 환경에서 카운터를 어떻게 일관되게 관리할지 확인합니다.
다음 글에서는 서버가 추가되거나 제거되어도 키 재배치를 최소화하는 안정 해시를 다룹니다. 분산 캐시와 데이터 파티셔닝을 이해하는 데 중요한 개념입니다.
이어 읽기
시리즈는 순서대로, 편집 추천은 맥락대로, 비슷한 주제는 태그 기준으로 정리합니다.
시리즈 전체
대규모 시스템 설계 스터디4/5편- 1.대규모 시스템 설계 스터디 01. 1명에서 수백만 명까지
- 2.대규모 시스템 설계 스터디 02. 개략적인 규모 추정
- 3.대규모 시스템 설계 스터디 03. 설계 질문을 풀어가는 순서
- 4.대규모 시스템 설계 스터디 04. 처리율 제한 장치
- 5.대규모 시스템 설계 스터디 05. 안정 해시
비슷한 주제의 글
태그가 겹치는 글입니다. 시리즈와 편집 추천에 이미 나온 글은 제외합니다.
LLM 공부 06. MoE와 GPU 클러스터는 거대 모델을 나누어 실행한다
MoE expert가 MLP/FFN과 어떻게 연결되는지 설명하고, 거대 LLM serving이 GPU memory, sharding, replication, interconnect, KV cache, scheduler를 다루는 분산컴퓨팅 문제가 되는 이유를 정리합니다.
AI 웹개발 기초 프론트엔드 1.3. jQuery에서 React로 넘어간 진짜 이유
jQuery의 DOM 직접 조작과 AJAX가 해결한 문제를 인정하고, UI가 커지면서 React, Vue, Angular 같은 state/data 기반 component UI가 왜 필요해졌는지 설명하는 AI 웹개발 기초 시리즈 프론트엔드 1-3.
AI 웹개발 기초 프론트엔드 1.4. React를 쓰는데 왜 Node.js와 npm이 필요할까
React와 Vue 같은 현대 프론트엔드 프로젝트에서 Node.js와 npm이 왜 필요한지, 브라우저 실행 코드와 Node.js 기반 개발 도구를 나누어 설명하고 package.json, lockfile, Vite build, bundler/compiler, tree shaking, code splitting, environment variable, source map, CI cache까지 연결하는 AI 웹개발 기초 시리즈 프론트엔드 1-4.