레이트 리밋 설계 가이드를 실행합니다.
Scanned 6/6/2026
Install via CLI
openskills install excatt/superclaude-plusplus---
name: "rate-limit"
description: "레이트 리밋 설계 가이드를 실행합니다."
user-invocable: true
---
# Rate Limiting Skill
레이트 리밋 설계 가이드를 실행합니다.
## 레이트 리밋 알고리즘
### Token Bucket
```
버킷에 토큰이 일정 속도로 추가
요청 시 토큰 소비
토큰 없으면 거부
┌─────────────┐
│ ○ ○ ○ ○ ○ │ ← 토큰 추가 (초당 10개)
│ Bucket │
└──────┬──────┘
↓ 요청당 1토큰 소비
[Request]
장점: 버스트 허용, 평균 속도 제한
구현: Redis + Lua Script
```
### Leaky Bucket
```
요청이 버킷에 쌓임
일정 속도로 처리
버킷 넘치면 거부
[Request]
↓
┌─────────────┐
│ ░ ░ ░ ░ ░ │ ← 큐 (고정 크기)
└──────┬──────┘
↓ 일정 속도로 처리
[Processed]
장점: 일정한 출력 속도
단점: 버스트 불가
```
### Fixed Window
```
고정 시간 창에서 요청 수 제한
|----60초----|----60초----|
| 100 요청 | 100 요청 |
|____________|____________|
단점: 경계에서 2배 버스트 가능
(59초에 100개 + 61초에 100개)
```
### Sliding Window Log
```
각 요청의 타임스탬프 기록
윈도우 내 요청 수 계산
현재 시간: 10:05:30
윈도우: 60초
[10:04:31, 10:04:45, 10:05:10, 10:05:25]
→ 윈도우 내 4개 요청
장점: 정확함
단점: 메모리 사용 많음
```
### Sliding Window Counter
```
이전 윈도우와 현재 윈도우 가중 평균
이전 윈도우 (70개) × 30% + 현재 윈도우 (50개) × 70%
= 21 + 35 = 56개
장점: 메모리 효율적, 상당히 정확
```
## 구현 예시
### Redis + Token Bucket
```lua
-- rate_limit.lua
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local bucket = redis.call('HMGET', key, 'tokens', 'last_update')
local tokens = tonumber(bucket[1]) or limit
local last_update = tonumber(bucket[2]) or now
-- 토큰 리필
local elapsed = now - last_update
local refill = elapsed * (limit / window)
tokens = math.min(limit, tokens + refill)
if tokens >= 1 then
tokens = tokens - 1
redis.call('HMSET', key, 'tokens', tokens, 'last_update', now)
redis.call('EXPIRE', key, window)
return {1, tokens} -- 허용, 남은 토큰
else
return {0, 0} -- 거부
end
```
### Node.js 미들웨어
```javascript
const Redis = require('ioredis');
const redis = new Redis();
const rateLimitScript = fs.readFileSync('rate_limit.lua', 'utf8');
async function rateLimit(options) {
const { key, limit, window } = options;
return async (req, res, next) => {
const identifier = req.ip; // 또는 req.user.id
const redisKey = `ratelimit:${key}:${identifier}`;
const [allowed, remaining] = await redis.eval(
rateLimitScript,
1,
redisKey,
limit,
window,
Date.now() / 1000
);
res.set('X-RateLimit-Limit', limit);
res.set('X-RateLimit-Remaining', remaining);
if (!allowed) {
res.set('Retry-After', window);
return res.status(429).json({
error: 'Too Many Requests',
retryAfter: window,
});
}
next();
};
}
// 사용
app.use('/api/', rateLimit({ key: 'api', limit: 100, window: 60 }));
app.use('/api/search', rateLimit({ key: 'search', limit: 10, window: 60 }));
```
### Express Rate Limit
```javascript
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const limiter = rateLimit({
store: new RedisStore({
client: redis,
prefix: 'rl:',
}),
windowMs: 60 * 1000, // 1분
max: 100, // 100 요청
message: { error: 'Too many requests' },
standardHeaders: true,
legacyHeaders: false,
});
app.use('/api/', limiter);
```
## 레이트 리밋 전략
### 계층별 제한
```javascript
const limits = {
global: { limit: 10000, window: 60 }, // 전체 API
authenticated: { limit: 1000, window: 60 }, // 인증된 사용자
anonymous: { limit: 100, window: 60 }, // 익명 사용자
endpoint: { // 엔드포인트별
'/api/search': { limit: 10, window: 60 },
'/api/upload': { limit: 5, window: 60 },
},
};
```
### 사용자 티어별
```javascript
const tierLimits = {
free: { limit: 100, window: 3600 }, // 시간당 100
basic: { limit: 1000, window: 3600 }, // 시간당 1000
pro: { limit: 10000, window: 3600 }, // 시간당 10000
enterprise: { limit: 100000, window: 3600 },
};
function getRateLimit(user) {
return tierLimits[user.tier] || tierLimits.free;
}
```
### 동적 제한
```javascript
// 부하에 따른 동적 조정
async function getDynamicLimit() {
const load = await getSystemLoad();
if (load > 0.9) return { limit: 50, window: 60 }; // 높은 부하
if (load > 0.7) return { limit: 75, window: 60 }; // 중간 부하
return { limit: 100, window: 60 }; // 정상
}
```
## 응답 헤더
### 표준 헤더
```
X-RateLimit-Limit: 100 # 최대 요청 수
X-RateLimit-Remaining: 45 # 남은 요청 수
X-RateLimit-Reset: 1640000000 # 리셋 시간 (Unix timestamp)
Retry-After: 30 # 재시도까지 초 (429 응답 시)
```
### 응답 예시
```json
// 429 Too Many Requests
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Try again in 30 seconds.",
"retryAfter": 30,
"limit": 100,
"remaining": 0,
"reset": 1640000000
}
}
```
## 분산 환경
### Redis Cluster
```javascript
// 모든 인스턴스가 같은 Redis 참조
const redis = new Redis.Cluster([
{ host: 'redis-1', port: 6379 },
{ host: 'redis-2', port: 6379 },
{ host: 'redis-3', port: 6379 },
]);
```
### 일관성 고려
```
Strong Consistency: 모든 노드가 동일한 카운트 (느림)
Eventual Consistency: 일시적 불일치 허용 (빠름)
대부분의 레이트 리밋은 Eventual Consistency로 충분
약간의 초과 허용이 성능보다 덜 중요
```
## 체크리스트
### 설계
- [ ] 알고리즘 선택
- [ ] 제한 수치 결정
- [ ] 키 식별자 정의 (IP, 사용자, API 키)
- [ ] 티어별 제한 설계
### 구현
- [ ] 분산 저장소 (Redis)
- [ ] 응답 헤더 설정
- [ ] 에러 응답 형식
- [ ] 바이패스 규칙 (내부, 헬스체크)
### 운영
- [ ] 모니터링/알림
- [ ] 제한 초과 로깅
- [ ] 동적 조정 메커니즘
## 출력 형식
```
## Rate Limit Design
### Strategy
- 알고리즘: Token Bucket
- 저장소: Redis
### Limits
| 대상 | 제한 | 윈도우 | 키 |
|------|------|--------|-----|
| Global | 10000 | 분 | IP |
| Auth User | 1000 | 분 | user_id |
| Search API | 10 | 분 | user_id |
### Implementation
```javascript
// 핵심 구현 코드
```
### Response Format
```json
// 429 응답 예시
```
```
---
요청에 맞는 레이트 리밋 전략을 설계하세요.
No comments yet. Be the first to comment!