API Rate Limit
๐ ๊ฐ์
API Rate Limiting์ ํน์ ์๊ฐ ํ๋ ์ ๋ด์์ ํด๋ผ์ด์ธํธ๊ฐ API์ ์์ฒญํ ์ ์๋ ํ์๋ฅผ ์ ์ดํ๋ ๊ธฐ์ ์ ๋๋ค. ์๋ฒ ๋ฆฌ์์ค๋ฅผ ๋ณดํธํ๊ณ , ๊ณต์ ํ ์ฌ์ฉ์ ๋ณด์ฅํ๋ฉฐ, ์ ์์ ์ธ ๊ณต๊ฒฉ์ผ๋ก๋ถํฐ ์์คํ ์ ๋ณดํธํ๋ ํต์ฌ์ ์ธ ๋ฐฉ์ด ๋ฉ์ปค๋์ฆ์ ๋๋ค.
๐ฏ ์ฃผ์ ๋ชฉ์
๋ฆฌ์์ค ๋ณดํธ
- ์๋ฒ ๊ณผ๋ถํ ๋ฐฉ์ง: ๋์ ์์ฒญ์ผ๋ก ์ธํ ์์คํ ๋ค์ด ์๋ฐฉ
- ์ฑ๋ฅ ์ ์ง: ๋ชจ๋ ์ฌ์ฉ์์๊ฒ ์์ ์ ์ธ ์๋ต ์๊ฐ ๋ณด์ฅ
- ๋น์ฉ ๊ด๋ฆฌ: ํด๋ผ์ฐ๋ ๋ฆฌ์์ค ์ฌ์ฉ๋ ์ต์ ํ
๋ณด์ ๊ฐํ
- DDoS ๊ณต๊ฒฉ ๋ฐฉ์ด: ๋๋์ ์์ฒญ์ผ๋ก๋ถํฐ ์์คํ ๋ณดํธ
- ๋ธ๋ฃจํธ ํฌ์ค ๊ณต๊ฒฉ ์ฐจ๋จ: ๋ฐ๋ณต์ ์ธ ๋ก๊ทธ์ธ ์๋ ์ ํ
- ์ ์์ ํธ๋ํฝ ์๋ณ: ๋น์ ์์ ์ธ ์ฌ์ฉ ํจํด ํ์ง
๊ณต์ ํ ์ฌ์ฉ
- ์ฌ์ฉ์ ๊ฐ ํํ์ฑ: ํน์ ์ฌ์ฉ์์ ๋ ์ ์ ๋ฆฌ์์ค ์ฌ์ฉ ๋ฐฉ์ง
- ์๋น์ค ํ์ง ์ ์ง: ๋ชจ๋ ํด๋ผ์ด์ธํธ์๊ฒ ๋๋ฑํ ์ ๊ทผ ๊ธฐํ ์ ๊ณต
๐ Rate Limiting ์ ํ
1. ์๊ฐ ๊ธฐ๋ฐ ์ ํ (Time-based)
โข ์ด๋น ์์ฒญ ์ (RPS): 10 requests/second
โข ๋ถ๋น ์์ฒญ ์: 600 requests/minute
โข ์๊ฐ๋น ์์ฒญ ์: 36,000 requests/hour
โข ์ผ์ผ ์์ฒญ ์: 100,000 requests/day
2. ๋์ ์์ฒญ ์ ํ (Concurrent)
โข ์ฌ์ฉ์๋น ๋์ ์ฐ๊ฒฐ: 5๊ฐ
โข IP๋น ๋์ ์์ฒญ: 10๊ฐ
โข ์ ์ฒด ์์คํ
๋์ ์ฒ๋ฆฌ: 1000๊ฐ
3. ์ฌ์ฉ์๋ณ ์ ํ (User-based)
Free Tier: 100 requests/hour
Basic Plan: 1,000 requests/hour
Pro Plan: 10,000 requests/hour
Enterprise: ๋ฌด์ ํ ๋๋ ๋งค์ฐ ๋์ ์ ํ
4. IP ๊ธฐ๋ฐ ์ ํ (IP-based)
โข ๋จ์ผ IP: 1,000 requests/hour
โข ์์ฌ์ค๋ฌ์ด IP: ์ฆ์ ์ฐจ๋จ
โข ์ง์ญ๋ณ ์ ํ: ๊ตญ๊ฐ/์ง์ญ์ ๋ฐ๋ฅธ ์ฐจ๋ฑ ์ ์ฉ
โ๏ธ ๊ตฌํ ์๊ณ ๋ฆฌ์ฆ
Token Bucket Algorithm
ํ ํฐ์ด ๋ด๊ธด ๋ฒํท์์ ์์ฒญ๋ง๋ค ํ ํฐ์ ์๋ชจํ๋ ๋ฐฉ์์ ๋๋ค. ํ ํฐ์ ์ผ์ ํ ์๋๋ก ๋ฒํท์ ์ฑ์์ง๋ฉฐ, ์งง์ ์๊ฐ ๋์์ ํธ๋ํฝ ๋ฒ์คํธ๋ฅผ ํ์ฉํ๋ฉด์๋ ์ฅ๊ธฐ์ ์ผ๋ก๋ ์ผ์ ํ ์๋๋ฅผ ์ ์งํ ์ ์๋ ์ ์ฐํ ์๊ณ ๋ฆฌ์ฆ์ ๋๋ค.
Fixed Window Counter Algorithm
๊ณ ์ ๋ ์๊ฐ ์๋์ฐ(์: 1๋ถ) ๋ด์์ ์์ฒญ ํ์๋ฅผ ์นด์ดํธํ๋ ๋จ์ํ ๋ฐฉ์์ ๋๋ค. ๊ตฌํ์ด ๊ฐ๋จํ๊ณ ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ ์ด์ง๋ง, ์๋์ฐ ๊ฒฝ๊ณ์์ ์๊ฐ์ ์ผ๋ก ๋ง์ ์์ฒญ์ด ์ง์ค๋ ์ ์๋ ๋จ์ ์ด ์์ต๋๋ค.
Sliding Window Log Algorithm
๊ฐ ์์ฒญ์ ํ์์คํฌํ๋ฅผ ๊ธฐ๋กํ๊ณ , ํ์ฌ ์์ ์์ ์๋์ฐ ํฌ๊ธฐ๋งํผ ์ด์ ์ ์์ฒญ๋ค์ ํ์ธํ๋ ๋ฐฉ์์ ๋๋ค. ์ ํํ ์ ํ์ด ๊ฐ๋ฅํ์ง๋ง ๋ชจ๋ ์์ฒญ์ ์ ์ฅํด์ผ ํ๋ฏ๋ก ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๋ง์ต๋๋ค.
Sliding Window Counter Algorithm
Fixed Window๊ณผ Sliding Window Log์ ํ์ด๋ธ๋ฆฌ๋ ์ ๊ทผ๋ฒ์ ๋๋ค. ์ด์ ์๋์ฐ์ ์นด์ดํธ์ ํ์ฌ ์๋์ฐ์ ์นด์ดํธ๋ฅผ ๊ฐ์คํ๊ท ์ผ๋ก ๊ณ์ฐํ์ฌ ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ฑ๊ณผ ์ ํ์ฑ์ ๊ท ํ์ ๋ง์ถ ์๊ณ ๋ฆฌ์ฆ์ ๋๋ค.
Leaky Bucket Algorithm
์์ฒญ์ ํ์ ๋ด๊ณ ์ผ์ ํ ์๋๋ก ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ ๋๋ค. ๋คํธ์ํฌ ํธ๋ํฝ ์ ์ด์ ์ฃผ๋ก ์ฌ์ฉ๋๋ฉฐ, ์ถ๋ ฅ ์๋๊ฐ ์ผ์ ํ๊ฒ ์ ์ง๋๋ ํน์ง์ด ์์ด ์๋ฒ ๋ถํ๋ฅผ ์์ธก ๊ฐ๋ฅํ๊ฒ ๋ง๋ค์ด์ค๋๋ค.
๐ข ์ค์ ์๋น์ค ์ฌ๋ก
์ฃผ์ ํ๋ซํผ๋ณ ์ ํ
Salesforce
- ๋์ API ์์ฒญ: 25๊ฐ (ํ๋ก๋์ ํ๊ฒฝ)
- ์ฅ๊ธฐ ์คํ ์์ฒญ: 20์ด ์ด์ ์ง์ ์ ์ ํ ์ ์ฉ
- ์ผ์ผ ์์ฒญ: 15,000๊ฐ (๊ธฐ๋ณธ), ์ถ๊ฐ ๊ตฌ๋งค ๊ฐ๋ฅ
HubSpot
- 10์ด ์ ํ: 100-200๊ฐ ์์ฒญ (ํ๋๋ณ ์ฐจ๋ฑ)
- ์ผ์ผ ์ ํ: 250,000-1,000,000๊ฐ ์์ฒญ
- ๋ฌด๋ฃ ํ๋: ๊ฐ์ฅ ๋ฎ์ ์ ํ ์ ์ฉ
QuickBooks Online
- ๋ถ๋น ์ ํ: ์ฌ์ฉ์ ID๋น 500๊ฐ ์์ฒญ
- ์๋ํฌ์ธํธ๋ณ: ๋ชจ๋ ์์ญ ํฉ์ณ์ 500๊ฐ/๋ถ
GitHub
- ์ธ์ฆ๋ ์์ฒญ: ๋ ๋์ ์ ํ ์ ์ฉ
- ๋ฏธ์ธ์ฆ ์์ฒญ: ๋ฎ์ ์ ํ์ผ๋ก ์ ํ์ ์ฌ์ฉ
๐ป ๊ตฌํ ์์
Express.js + Redis
const express = require('express');
const redis = require('redis');
const client = redis.createClient();
const rateLimiter = (limit = 100, window = 3600) => {
return async (req, res, next) => {
const key = `rate_limit:${req.ip}`;
try {
const current = await client.incr(key);
if (current === 1) {
await client.expire(key, window);
}
if (current > limit) {
return res.status(429).json({
error: 'Rate limit exceeded',
retryAfter: window
});
}
res.set({
'X-RateLimit-Limit': limit,
'X-RateLimit-Remaining': Math.max(0, limit - current),
'X-RateLimit-Reset': Date.now() + (window * 1000)
});
next();
} catch (error) {
next(error);
}
};
};
app.use(rateLimiter(1000, 3600)); // ์๊ฐ๋น 1000๊ฐ ์์ฒญ
Python Flask + Redis
from flask import Flask, request, jsonify
import redis
import time
from functools import wraps
app = Flask(__name__)
redis_client = redis.Redis(host='localhost', port=6379, db=0)
def rate_limit(max_requests=100, window=3600):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
key = f"rate_limit:{request.remote_addr}"
current = redis_client.incr(key)
if current == 1:
redis_client.expire(key, window)
if current > max_requests:
return jsonify({
'error': 'Rate limit exceeded',
'retry_after': window
}), 429
return f(*args, **kwargs)
return decorated_function
return decorator
@app.route('/api/data')
@rate_limit(max_requests=1000, window=3600)
def get_data():
return jsonify({'data': 'response'})
๐จ HTTP ์๋ต ์ฒ๋ฆฌ
429 Too Many Requests
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1691172000
{
"error": "Rate limit exceeded",
"message": "API rate limit exceeded for user",
"retry_after": 60,
"limit": 1000,
"remaining": 0,
"reset_time": "2024-08-04T15:00:00Z"
}
ํด๋ผ์ด์ธํธ ์ธก ์ฒ๋ฆฌ
async function apiCall(url, options = {}) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
console.log(`Rate limited. Retry after ${retryAfter} seconds`);
// ์ง์ ๋ฐฑ์คํ๋ก ์ฌ์๋
await new Promise(resolve =>
setTimeout(resolve, retryAfter * 1000)
);
return apiCall(url, options); // ์ฌ์๋
}
return response.json();
} catch (error) {
console.error('API call failed:', error);
throw error;
}
}
๐ ์ ํ ์ค์ ๊ฐ์ด๋๋ผ์ธ
์ผ๋ฐ์ ์ธ ๊ถ์ฅ์ฌํญ
๋์ ์์ฒญ ์
์ผ๋ฐ API: 5-10๊ฐ ๋์ ์์ฒญ
๋ฌด๊ฑฐ์ด ์์
: 1-3๊ฐ ๋์ ์์ฒญ
์ค์๊ฐ API: 10-50๊ฐ ๋์ ์ฐ๊ฒฐ
ํ์ผ ์ ์ก: 2-5๊ฐ ๋์ ์ ์ก
์๊ฐ๋น ์์ฒญ ์
์์
๋ฏธ๋์ด/๋ด์ค: 100-300 req/min
์ด์ปค๋จธ์ค: 60-180 req/min
๊ธ์ต์๋น์ค: 30-100 req/min
AI/ML API: 10-60 req/min
์๋ฒ ๋ฆฌ์์ค๋ณ
CPU ์ง์ฝ์ : ๋์ 1-2๊ฐ (์ด๋ฏธ์ง ์ฒ๋ฆฌ, AI)
๋ฉ๋ชจ๋ฆฌ ์ง์ฝ์ : ๋์ 2-3๊ฐ (๋์ฉ๋ ๋ฐ์ดํฐ)
I/O ์ง์ฝ์ : ๋์ 5-10๊ฐ (DB ์ฟผ๋ฆฌ)
๋คํธ์ํฌ ์ง์ฝ์ : ๋์ 3-8๊ฐ (์ธ๋ถ API)
์ฌ์ฉ์ ๊ณ์ธต๋ณ ์ค์
Free Tier: ๋์ 1-2๊ฐ, 100 req/hour
Basic: ๋์ 3-5๊ฐ, 1,000 req/hour
Pro: ๋์ 5-10๊ฐ, 10,000 req/hour
Enterprise: ๋์ 10-50๊ฐ, ๋ฌด์ ํ
๐ ๏ธ ๊ตฌํ ๋๊ตฌ
API Gateway ์๋ฃจ์
- Kong: ์คํ์์ค API ๊ฒ์ดํธ์จ์ด, ๋ค์ํ rate limiting ํ๋ฌ๊ทธ์ธ
- Nginx Plus: ์์ฉ ์น์๋ฒ์ rate limiting ๋ชจ๋
- AWS API Gateway: ํด๋ผ์ฐ๋ ๋ค์ดํฐ๋ธ ์๋ฃจ์
- Azure API Management: Microsoft ํด๋ผ์ฐ๋ ํ๋ซํผ
๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐ ๋ฏธ๋ค์จ์ด
- Express Rate Limit: Node.js Express ํ๋ ์์ํฌ์ฉ
- Flask-Limiter: Python Flask ํ๋ ์์ํฌ์ฉ
- Django Ratelimit: Django ์น ํ๋ ์์ํฌ์ฉ
- Go Rate: Go ์ธ์ด์ฉ rate limiting ๋ผ์ด๋ธ๋ฌ๋ฆฌ
๐ ๋ชจ๋ํฐ๋ง ๋ฐ ๋ถ์
ํต์ฌ ์งํ
โข ํ๊ท ์๋ต ์๊ฐ
โข 429 ์๋ฌ ๋ฐ์๋ฅ
โข ์ฌ์ฉ์๋ณ ์์ฒญ ํจํด
โข ์๊ฐ๋๋ณ ํธ๋ํฝ ๋ถํฌ
โข ๋์ ์ฐ๊ฒฐ ์ ์ถ์ด
์๋ ์ค์
# Prometheus + Grafana ์์
rate_limit_violations = Counter(
'api_rate_limit_violations_total',
'Total number of rate limit violations',
['user_id', 'endpoint']
)
# ์๊ณ๊ฐ ์ด๊ณผ ์ ์๋
if violation_rate > 5: # 5% ์ด๊ณผ
send_alert("High rate limit violation detected")
๐๏ธ ๋์ ์กฐ์
์๋ฒ ๋ถํ ๊ธฐ๋ฐ ์กฐ์
def dynamic_rate_limit():
cpu_usage = get_cpu_usage()
memory_usage = get_memory_usage()
if cpu_usage > 80 or memory_usage > 85:
return base_limit * 0.5 # 50% ๊ฐ์
elif cpu_usage < 50 and memory_usage < 60:
return base_limit * 1.5 # 50% ์ฆ๊ฐ
return base_limit
์๊ฐ๋๋ณ ์กฐ์
def time_based_limit():
current_hour = datetime.now().hour
# ํผํฌ ์๊ฐ (9-18์)
if 9 <= current_hour <= 18:
return base_limit * 0.8
# ํ๊ฐํ ์๊ฐ (์๋ฒฝ 2-6์)
elif 2 <= current_hour <= 6:
return base_limit * 2.0
return base_limit
๐ ์ฐธ๊ณ ์๋ฃ
๊ณต์ ๋ฌธ์ ๋ฐ ๊ฐ์ด๋
- Tyk API Management - Rate Limiting Guide
- RESTful API - Rate Limit Guidelines
- Stoplight - Rate Limiting vs Throttling
ํด๋ผ์ฐ๋ ํ๋ซํผ ๋ฌธ์
๊ตฌํ ๋๊ตฌ ๋ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
๋ชจ๋ฒ ์ฌ๋ก ๊ฐ์ด๋
- Testfully - API Rate Limiting Strategies
- HubSpot - API Rate Limit Guide
- Merge - Rate Limit Best Practices