[Daily morning study] API Rate Limiting ๊ตฌํ ๋ฐฉ๋ฒ
#daily morning study
API Rate Limiting ๊ตฌํ ๋ฐฉ๋ฒ
Rate Limiting์ด๋?
Rate Limiting์ ํด๋ผ์ด์ธํธ๊ฐ ์ผ์ ์๊ฐ ๋์ API๋ฅผ ํธ์ถํ ์ ์๋ ํ์๋ฅผ ์ ํํ๋ ๊ธฐ๋ฒ์ด๋ค.
์๋ฒ ์์์ ๋ณดํธํ๊ณ , ํน์ ์ฌ์ฉ์์ ๊ณผ๋ํ ์์ฒญ์ผ๋ก ์ธํด ๋ค๋ฅธ ์ฌ์ฉ์๊ฐ ์๋น์ค๋ฅผ ์ด์ฉํ์ง ๋ชปํ๋ ์ํฉ์ ๋ฐฉ์งํ๋ค.
์ ํ์ํ๊ฐ
- DoS / DDoS ๋ฐฉ์ด: ์ ์์ ์ธ ๋๋ ์์ฒญ์ผ๋ก๋ถํฐ ์๋ฒ๋ฅผ ๋ณดํธํ๋ค.
- ๊ณต์ ํ ์์ ๋ถ๋ฐฐ: ํน์ ํด๋ผ์ด์ธํธ๊ฐ API๋ฅผ ๋ ์ ํ๋ ๊ฒ์ ๋ง๋๋ค.
- ๋น์ฉ ์ ์ด: ํด๋ผ์ฐ๋ ํ๊ฒฝ์์ ๋ฌด๋ถ๋ณํ ์์ฒญ์ผ๋ก ์ธํ ๋น์ฉ ํญ์ฆ์ ๋ฐฉ์งํ๋ค.
- ์์ ์ฑ ๋ณด์ฅ: ํธ๋ํฝ ๊ธ์ฆ ์ ์๋น์ค ์ ํ๋ฅผ ๋ง๋๋ค.
์ฃผ์ ์๊ณ ๋ฆฌ์ฆ
1. Fixed Window Counter
๊ฐ์ฅ ๋จ์ํ ๋ฐฉ์์ด๋ค. ๊ณ ์ ๋ ์๊ฐ ์ฐฝ(์: 1๋ถ)์ ๊ธฐ์ค์ผ๋ก ์์ฒญ ์๋ฅผ ์นด์ดํธํ๋ค.
|-- 1๋ถ --| |-- 1๋ถ --|
50 req 50 req
๋ฌธ์ ์ : ์ฐฝ ๊ฒฝ๊ณ ๊ทผ์ฒ์์ ๋ ๋ฐฐ ์์ฒญ์ด ํต๊ณผ๋ ์ ์๋ค.
์๋ฅผ ๋ค์ด 00:59์ 50๊ฐ, 01:00์ 50๊ฐ ์์ฒญ์ด ์ค๋ฉด 2์ด ์์ 100๊ฐ๊ฐ ์ฒ๋ฆฌ๋๋ค.
2. Sliding Window Log
๋ชจ๋ ์์ฒญ์ ํ์์คํฌํ๋ฅผ ๋ก๊ทธ๋ก ๋จ๊ธฐ๊ณ , ํ์ฌ ์๊ฐ ๊ธฐ์ค์ผ๋ก ์ต๊ทผ N์ด ์์ ์์ฒญ๋ง ์ผ๋ค.
import time
from collections import deque
class SlidingWindowLog:
def __init__(self, limit, window_sec):
self.limit = limit
self.window = window_sec
self.log = deque()
def allow(self):
now = time.time()
# ์ค๋๋ ํ์์คํฌํ ์ ๊ฑฐ
while self.log and self.log[0] < now - self.window:
self.log.popleft()
if len(self.log) < self.limit:
self.log.append(now)
return True
return False
์ ํํ์ง๋ง ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๋ง๋ค. ์์ฒญ์ด ๋ง์์๋ก ๋ก๊ทธ ํฌ๊ธฐ๊ฐ ์ปค์ง๋ค.
3. Token Bucket
๋ฒํท์ ํ ํฐ์ด ์ผ์ ์๋๋ก ์ฑ์์ง๊ณ , ์์ฒญ๋ง๋ค ํ ํฐ์ ํ๋์ฉ ์๋นํ๋ ๋ฐฉ์์ด๋ค.
ํ ํฐ ์ถฉ์ ์๋: 10 tokens/sec
๋ฒํท ์ฉ๋: 100 tokens
์์ฒญ ๋ค์ด์ด โ ํ ํฐ ์์ผ๋ฉด ์๋น ํ ํ์ฉ
โ ํ ํฐ ์์ผ๋ฉด ๊ฑฐ์
ํน์ง
- ๋ฒ์คํธ ํธ๋ํฝ์ ์ด๋ ์ ๋ ํ์ฉํ๋ค (๋ฒํท ์ฉ๋๋งํผ).
- ๊ฐ์ฅ ๋ง์ด ์ฐ์ด๋ ์๊ณ ๋ฆฌ์ฆ ์ค ํ๋๋ค.
- AWS API Gateway, Stripe ๋ฑ์์ ์ฌ์ฉํ๋ค.
import time
class TokenBucket:
def __init__(self, capacity, refill_rate):
self.capacity = capacity
self.tokens = capacity
self.refill_rate = refill_rate # tokens per second
self.last_refill = time.time()
def allow(self):
now = time.time()
elapsed = now - self.last_refill
self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_rate)
self.last_refill = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False
4. Leaky Bucket
์์ฒญ์ ํ์ ๋ฃ๊ณ ์ผ์ ํ ์๋๋ก๋ง ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ด๋ค. ๋คํธ์ํฌ ํธ๋ํฝ ์ ฐ์ดํ์์ ์ ๋ํ๋ค.
์์ฒญ โ [ํ] โ ์ผ์ ์๋๋ก ์ฒ๋ฆฌ
(์ด๋น 10๊ฐ๋ง ๋๊ฐ)
- ์ฒ๋ฆฌ ์๋๊ฐ ์ผ์ ํ๋ค๋ ์ฅ์ ์ด ์์ง๋ง, ๋ฒ์คํธ๋ฅผ ์์ ํ ํก์ํ์ง ๋ชปํ๊ณ ํ๊ฐ ๋์น๋ฉด ์์ฒญ์ด ๋ฒ๋ ค์ง๋ค.
- Token Bucket๊ณผ ๊ฐ๋ ์ด ๋น์ทํ์ง๋ง ๋ฐฉํฅ์ด ๋ฐ๋๋ค.
5. Sliding Window Counter (์ค๋ฌด ๊ถ์ฅ)
Fixed Window์ ๊ฒฝ๊ณ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ฉด์๋ Sliding Window Log๋ณด๋ค ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ ๊ฒ ์ด๋ค.
ํ์ฌ ์ฐฝ๊ณผ ์ด์ ์ฐฝ์ ์นด์ดํธ๋ฅผ ๊ฐ์ค ํ๊ท ์ผ๋ก ํฉ์ฐํ๋ค.
ํ์ฌ ์ฐฝ ๊ฒฝ๊ณผ ๋น์จ = 0.3 (30% ์ง๋จ)
์ด์ ์ฐฝ ๊ฐ์ค์น = 1 - 0.3 = 0.7
์ถ์ ์์ฒญ ์ = ์ด์ _์นด์ดํธ ร 0.7 + ํ์ฌ_์นด์ดํธ
Redis์ INCR + EXPIRE ์กฐํฉ์ผ๋ก ๊ตฌํํ๊ธฐ ์ฌ์์ ์ค๋ฌด์์ ์์ฃผ ์ด๋ค.
Redis๋ฅผ ์ด์ฉํ ๊ตฌํ ์์
๋ถ์ฐ ํ๊ฒฝ์์๋ ๊ฐ ์๋ฒ๊ฐ ๋ณ๋ ์นด์ดํฐ๋ฅผ ๊ฐ์ง๋ฉด ์ ๋๋ฏ๋ก, Redis ๊ฐ์ ์ค์ ์ ์ฅ์๋ฅผ ์ฌ์ฉํ๋ค.
import redis
import time
r = redis.Redis(host='localhost', port=6379)
def is_allowed(user_id: str, limit: int, window_sec: int) -> bool:
key = f"rate_limit:{user_id}:{int(time.time() // window_sec)}"
pipe = r.pipeline()
pipe.incr(key)
pipe.expire(key, window_sec * 2)
results = pipe.execute()
current_count = results[0]
return current_count <= limit
Lua ์คํฌ๋ฆฝํธ๋ฅผ ์ฐ๋ฉด INCR๊ณผ EXPIRE๋ฅผ ์์์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋ค.
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local count = redis.call('INCR', key)
if count == 1 then
redis.call('EXPIRE', key, window)
end
if count > limit then
return 0
else
return 1
end
HTTP ์๋ต ํค๋
Rate Limit ์ ๋ณด๋ ์๋ต ํค๋๋ก ํด๋ผ์ด์ธํธ์ ์๋ ค์ฃผ๋ ๊ฒ์ด ๊ด๋ก๋ค.
| ํค๋ | ์๋ฏธ |
|---|---|
X-RateLimit-Limit | ํ์ฉ๋ ์ต๋ ์์ฒญ ์ |
X-RateLimit-Remaining | ๋จ์ ์์ฒญ ํ์ |
X-RateLimit-Reset | ์นด์ดํฐ ์ด๊ธฐํ ์๊ฐ (Unix timestamp) |
Retry-After | 429 ์๋ต ์ ์ฌ์๋๊น์ง ๋๊ธฐ ์๊ฐ (์ด) |
ํ๋ ์ด๊ณผ ์ HTTP 429 Too Many Requests๋ฅผ ๋ฐํํ๋ค.
์ค๋ฌด ์ ์ฉ ์ ๊ณ ๋ ค์ฌํญ
์๋ณ ๊ธฐ์ค ์ ํ
- IP ๊ธฐ๋ฐ: ๊ตฌํ์ด ๋จ์ํ์ง๋ง NAT ํ๊ฒฝ์์ ์ฌ๋ฌ ์ฌ์ฉ์๊ฐ ๊ฐ์ IP๋ฅผ ๊ณต์ ํ ์ ์๋ค.
- API ํค ๊ธฐ๋ฐ: ์ธ์ฆ๋ ํด๋ผ์ด์ธํธ๋ฅผ ์ ํํ ๊ตฌ๋ถํ ์ ์๋ค.
- ์ฌ์ฉ์ ID ๊ธฐ๋ฐ: ๋ก๊ทธ์ธ ์ฌ์ฉ์์๊ฒ ์ ํฉํ๋ค.
๋ถ์ฐ ํ๊ฒฝ ์ฃผ์์ฌํญ
- ์ฌ๋ฌ ์ธ์คํด์ค๊ฐ ๋์์ ธ ์์ผ๋ฉด ๋ฐ๋์ Redis ๊ฐ์ ๊ณต์ ์ ์ฅ์๋ฅผ ์จ์ผ ํ๋ค.
- Redis ์ฅ์ ์ fail-open(ํ์ฉ) ๋๋ fail-closed(๊ฑฐ๋ถ) ์ ์ฑ ์ ๋ฏธ๋ฆฌ ๊ฒฐ์ ํด์ผ ํ๋ค.
๊ณ์ธต๋ณ ์ ์ฉ
- API Gateway ๋ ๋ฒจ์์ ์ ์ฉํ๋ฉด ์๋ฒ๊น์ง ์์ฒญ์ด ๋๋ฌํ์ง ์์ ํจ์จ์ ์ด๋ค.
- Nginx์
limit_req_zone, Kong, AWS API Gateway ๋ฑ์ด ๋ด์ฅ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
์ฐจ๋ณํ๋ ํ๋
- ๋ฌด๋ฃ/์ ๋ฃ ํ๋๋ง๋ค ๋ค๋ฅธ ํ๋๋ฅผ ์ ์ฉํ๊ฑฐ๋, ์๋ํฌ์ธํธ๋ณ๋ก ๋ค๋ฅธ ์ ์ฑ ์ ์ ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค.
์๊ณ ๋ฆฌ์ฆ ๋น๊ต
| ์๊ณ ๋ฆฌ์ฆ | ์ ํ๋ | ๋ฉ๋ชจ๋ฆฌ | ๋ฒ์คํธ ํ์ฉ | ๊ตฌํ ๋์ด๋ |
|---|---|---|---|---|
| Fixed Window | ๋ฎ์ | ๋ฎ์ | O | ๋งค์ฐ ์ฌ์ |
| Sliding Window Log | ๋์ | ๋์ | X | ๋ณดํต |
| Token Bucket | ๋์ | ๋ฎ์ | O (์ฉ๋๋งํผ) | ๋ณดํต |
| Leaky Bucket | ๋์ | ๋ณดํต | X | ๋ณดํต |
| Sliding Window Counter | ๋์ | ๋ฎ์ | X | ๋ณดํต |
์ค๋ฌด์์๋ Token Bucket ๋๋ Sliding Window Counter + Redis ์กฐํฉ์ด ๊ฐ์ฅ ๋ง์ด ์ฐ์ธ๋ค.