API ๋ณดํธ ๋ฐ ํธ๋ํฝ ๊ด๋ฆฌ๋ฅผ ์ํ Python ์๋ ์ ํ ๊ธฐ์ ์ ํ๊ตฌํ๊ณ , ํ ํฐ ๋ฒํท๊ณผ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ์๊ณ ๋ฆฌ์ฆ์ ๋น๊ตํฉ๋๋ค.
Python ์๋ ์ ํ: ํ ํฐ ๋ฒํท vs. ์ฌ๋ผ์ด๋ฉ ์๋์ฐ - ์ข ํฉ ๊ฐ์ด๋
์ค๋๋ ์ํธ ์ฐ๊ฒฐ๋ ์ธ์์์ ๊ฒฌ๊ณ ํ API๋ ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๊ณต์ ํ์์ ์ ๋๋ค. ๊ทธ๋ฌ๋ ํต์ ๋์ง ์์ API ์ก์ธ์ค๋ ์๋ฒ ๊ณผ๋ถํ, ์๋น์ค ์ ํ, ์ฌ์ง์ด ์๋น์ค ๊ฑฐ๋ถ(DoS) ๊ณต๊ฒฉ์ผ๋ก ์ด์ด์ง ์ ์์ต๋๋ค. ์๋ ์ ํ์ ํน์ ์๊ฐ ๋ด์ ์ฌ์ฉ์ ๋๋ ์๋น์ค๊ฐ ๋ณด๋ผ ์ ์๋ ์์ฒญ ์๋ฅผ ์ ํํ์ฌ API๋ฅผ ๋ณดํธํ๋ ์ค์ํ ๊ธฐ์ ์ ๋๋ค. ์ด ๊ธ์์๋ Python์์ ๋๋ฆฌ ์ฌ์ฉ๋๋ ๋ ๊ฐ์ง ์๋ ์ ํ ์๊ณ ๋ฆฌ์ฆ์ธ ํ ํฐ ๋ฒํท(Token Bucket)๊ณผ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ(Sliding Window)๋ฅผ ์ฌ์ธต์ ์ผ๋ก ๋ค๋ฃจ๊ณ , ํฌ๊ด์ ์ธ ๋น๊ต์ ์ค์ ๊ตฌํ ์์๋ฅผ ์ ๊ณตํฉ๋๋ค.
์๋ ์ ํ์ด ์ค์ํ ์ด์
์๋ ์ ํ์ ๋ค์๊ณผ ๊ฐ์ ์๋ง์ ์ด์ ์ ์ ๊ณตํฉ๋๋ค:
- ์ ์ฉ ๋ฐฉ์ง: ์ ์ฑ ์ฌ์ฉ์๋ ๋ด์ด ๊ณผ๋ํ ์์ฒญ์ผ๋ก ์๋ฒ๋ฅผ ์๋ํ๋ ๊ฒ์ ์ ํํฉ๋๋ค.
- ๊ณต์ ํ ์ฌ์ฉ ๋ณด์ฅ: ์ฌ์ฉ์์๊ฒ ๋ฆฌ์์ค๋ฅผ ๊ณต์ ํ๊ฒ ๋ถ๋ฐฐํ์ฌ ๋จ์ผ ์ฌ์ฉ์๊ฐ ์์คํ ์ ๋ ์ ํ๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค.
- ์ธํ๋ผ ๋ณดํธ: ์๋ฒ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ๊ณผ๋ถํ๋ก ์ธํด ์ถฉ๋ํ๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค.
- ๋น์ฉ ์ ์ด: ์์์น ๋ชปํ ๋ฆฌ์์ค ์ฌ์ฉ๋ ๊ธ์ฆ์ ๋ฐฉ์งํ์ฌ ๋น์ฉ ์ ๊ฐ ํจ๊ณผ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
- ์ฑ๋ฅ ํฅ์: ๋ฆฌ์์ค ๊ณ ๊ฐ์ ๋ฐฉ์งํ๊ณ ์ผ๊ด๋ ์๋ต ์๊ฐ์ ๋ณด์ฅํ์ฌ ์์ ์ ์ธ ์ฑ๋ฅ์ ์ ์งํฉ๋๋ค.
์๋ ์ ํ ์๊ณ ๋ฆฌ์ฆ ์ดํด
์ฌ๋ฌ ์๋ ์ ํ ์๊ณ ๋ฆฌ์ฆ์ด ์กด์ฌํ๋ฉฐ, ๊ฐ๊ธฐ ์ฅ๋จ์ ์ ๊ฐ์ง๊ณ ์์ต๋๋ค. ์ด ๊ธ์์๋ ๊ฐ์ฅ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋๋ ๋ ๊ฐ์ง ์๊ณ ๋ฆฌ์ฆ์ธ ํ ํฐ ๋ฒํท๊ณผ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ์ ์ด์ ์ ๋ง์ถ ๊ฒ์ ๋๋ค.
1. ํ ํฐ ๋ฒํท ์๊ณ ๋ฆฌ์ฆ
ํ ํฐ ๋ฒํท ์๊ณ ๋ฆฌ์ฆ์ ๊ฐ๋จํ๊ณ ๋๋ฆฌ ์ฌ์ฉ๋๋ ์๋ ์ ํ ๊ธฐ์ ์ ๋๋ค. ํ ํฐ์ ๋ด๋ "๋ฒํท"์ ์ ์งํ์ฌ ์๋ํฉ๋๋ค. ๊ฐ ํ ํฐ์ ํ๋์ ์์ฒญ์ ๋ณด๋ผ ์ ์๋ ๊ถํ์ ๋ํ๋ ๋๋ค. ๋ฒํท์ ์ต๋ ์ฉ๋์ ๊ฐ์ง๋ฉฐ, ํ ํฐ์ ๊ณ ์ ๋ ์๋๋ก ๋ฒํท์ ์ถ๊ฐ๋ฉ๋๋ค.
์์ฒญ์ด ๋์ฐฉํ๋ฉด ์๋ ์ ํ๊ธฐ๋ ๋ฒํท์ ์ถฉ๋ถํ ํ ํฐ์ด ์๋์ง ํ์ธํฉ๋๋ค. ํ ํฐ์ด ์ถฉ๋ถํ๋ฉด ์์ฒญ์ด ํ์ฉ๋๊ณ , ํด๋น ๊ฐ์์ ํ ํฐ์ด ๋ฒํท์์ ์ ๊ฑฐ๋ฉ๋๋ค. ๋ฒํท์ด ๋น์ด ์์ผ๋ฉด ์ถฉ๋ถํ ํ ํฐ์ด ์ฌ์ฉ ๊ฐ๋ฅํด์ง ๋๊น์ง ์์ฒญ์ด ๊ฑฐ๋ถ๋๊ฑฐ๋ ์ง์ฐ๋ฉ๋๋ค.
Python์์์ ํ ํฐ ๋ฒํท ๊ตฌํ
๋ค์์ threading ๋ชจ๋์ ์ฌ์ฉํ์ฌ ๋์์ฑ์ ๊ด๋ฆฌํ๋ ํ ํฐ ๋ฒํท ์๊ณ ๋ฆฌ์ฆ์ ๊ธฐ๋ณธ์ ์ธ Python ๊ตฌํ์
๋๋ค:
import time
import threading
class TokenBucket:
def __init__(self, capacity, fill_rate):
self.capacity = float(capacity)
self._tokens = float(capacity)
self.fill_rate = float(fill_rate)
self.last_refill = time.monotonic()
self.lock = threading.Lock()
def _refill(self):
now = time.monotonic()
delta = now - self.last_refill
tokens_to_add = delta * self.fill_rate
self._tokens = min(self.capacity, self._tokens + tokens_to_add)
self.last_refill = now
def consume(self, tokens):
with self.lock:
self._refill()
if self._tokens >= tokens:
self._tokens -= tokens
return True
return False
# ์์ ์ฌ์ฉ๋ฒ
bucket = TokenBucket(capacity=10, fill_rate=2) # ํ ํฐ 10๊ฐ, ์ด๋น 2๊ฐ ํ ํฐ์ผ๋ก ์ฑ์์ง
for i in range(15):
if bucket.consume(1):
print(f"์์ฒญ {i+1}: ํ์ฉ๋จ")
else:
print(f"์์ฒญ {i+1}: ์๋ ์ ํ๋จ")
time.sleep(0.2)
์ค๋ช :
TokenBucket(capacity, fill_rate): ์ต๋ ์ฉ๋๊ณผ ์ฑ์ฐ๊ธฐ ์๋(์ด๋น ํ ํฐ ์)๋ก ๋ฒํท์ ์ด๊ธฐํํฉ๋๋ค._refill(): ๋ง์ง๋ง ์ฑ์ฐ๊ธฐ ์ดํ ๊ฒฝ๊ณผ๋ ์๊ฐ์ ๊ธฐ์ค์ผ๋ก ๋ฒํท์ ํ ํฐ์ผ๋ก ์ฑ์๋๋ค.consume(tokens): ์ง์ ๋ ์์ ํ ํฐ์ ์๋นํ๋ ค๊ณ ์๋ํฉ๋๋ค. ์ฑ๊ณตํ๋ฉด (์์ฒญ ํ์ฉ)True๋ฅผ ๋ฐํํ๊ณ , ๊ทธ๋ ์ง ์์ผ๋ฉด (์์ฒญ ์๋ ์ ํ)False๋ฅผ ๋ฐํํฉ๋๋ค.- ์ค๋ ๋ฉ ๋ฝ: ๋์์ฑ ํ๊ฒฝ์์ ์ค๋ ๋ ์์ ์ฑ์ ๋ณด์ฅํ๊ธฐ ์ํด ์ค๋ ๋ฉ ๋ฝ(
self.lock)์ ์ฌ์ฉํฉ๋๋ค.
ํ ํฐ ๋ฒํท์ ์ฅ์
- ๊ตฌํ ์ฉ์ด์ฑ: ์ดํดํ๊ณ ๊ตฌํํ๊ธฐ ๋น๊ต์ ๊ฐ๋จํฉ๋๋ค.
- ๋ฒ์คํธ ์ฒ๋ฆฌ: ๋ฒํท์ ์ถฉ๋ถํ ํ ํฐ์ด ์๋ ํ, ๊ฐ๋ ๋ฐ์ํ๋ ํธ๋ํฝ ๋ฒ์คํธ๋ฅผ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
- ๊ตฌ์ฑ ๊ฐ๋ฅ: ์ฉ๋๊ณผ ์ฑ์ฐ๊ธฐ ์๋๋ ํน์ ์๊ตฌ ์ฌํญ์ ๋ง์ถฐ ์ฝ๊ฒ ์กฐ์ ํ ์ ์์ต๋๋ค.
ํ ํฐ ๋ฒํท์ ๋จ์
- ์๋ฒฝํ ์ ํ์ฑ ๋ถ์กฑ: ์ฌ์ถฉ์ ๋ฉ์ปค๋์ฆ์ผ๋ก ์ธํด ๊ตฌ์ฑ๋ ์๋๋ณด๋ค ์ฝ๊ฐ ๋ ๋ง์ ์์ฒญ์ ํ์ฉํ ์ ์์ต๋๋ค.
- ๋งค๊ฐ๋ณ์ ํ๋: ์ํ๋ ์๋ ์ ํ ๋์์ ๋ฌ์ฑํ๋ ค๋ฉด ์ฉ๋๊ณผ ์ฑ์ฐ๊ธฐ ์๋๋ฅผ ์ ์คํ๊ฒ ์ ํํด์ผ ํฉ๋๋ค.
2. ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ์๊ณ ๋ฆฌ์ฆ
์ฌ๋ผ์ด๋ฉ ์๋์ฐ ์๊ณ ๋ฆฌ์ฆ์ ์๊ฐ์ ๊ณ ์ ๋ ํฌ๊ธฐ์ ์๋์ฐ๋ก ๋๋๋ ๋ณด๋ค ์ ํํ ์๋ ์ ํ ๊ธฐ์ ์ ๋๋ค. ๊ฐ ์๋์ฐ ๋ด์์ ์ด๋ฃจ์ด์ง ์์ฒญ ์๋ฅผ ์ถ์ ํฉ๋๋ค. ์ ์์ฒญ์ด ๋์ฐฉํ๋ฉด ์๊ณ ๋ฆฌ์ฆ์ ํ์ฌ ์๋์ฐ ๋ด์ ์์ฒญ ์๊ฐ ์ ํ์ ์ด๊ณผํ๋์ง ํ์ธํฉ๋๋ค. ์ด๊ณผํ๋ฉด ์์ฒญ์ ๊ฑฐ๋ถ๋๊ฑฐ๋ ์ง์ฐ๋ฉ๋๋ค.
"์ฌ๋ผ์ด๋ฉ"์ด๋ผ๋ ์ธก๋ฉด์ ์ ์์ฒญ์ด ๋์ฐฉํจ์ ๋ฐ๋ผ ์๋์ฐ๊ฐ ์๊ฐ์์ผ๋ก ์์ผ๋ก ์ด๋ํ๋ค๋ ์ฌ์ค์์ ๋น๋กฏ๋ฉ๋๋ค. ํ์ฌ ์๋์ฐ๊ฐ ๋๋๋ฉด ์ ์๋์ฐ๊ฐ ์์๋๊ณ ์นด์ดํฐ๋ ์ฌ์ค์ ๋ฉ๋๋ค. ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ์๊ณ ๋ฆฌ์ฆ์๋ ๋ ๊ฐ์ง ์ฃผ์ ๋ณํ์ด ์์ต๋๋ค: ์ฌ๋ผ์ด๋ฉ ๋ก๊ทธ(Sliding Log)์ ๊ณ ์ ์๋์ฐ ์นด์ดํฐ(Fixed Window Counter).
2.1. ์ฌ๋ผ์ด๋ฉ ๋ก๊ทธ
์ฌ๋ผ์ด๋ฉ ๋ก๊ทธ ์๊ณ ๋ฆฌ์ฆ์ ํน์ ์๊ฐ ์๋์ฐ ๋ด์์ ์ด๋ฃจ์ด์ง ๋ชจ๋ ์์ฒญ์ ํ์์คํฌํ๊ฐ ์ฐํ ๋ก๊ทธ๋ฅผ ์ ์งํฉ๋๋ค. ์ ์์ฒญ์ด ๋ค์ด์ค๋ฉด ์๋์ฐ ๋ด์ ์ํ๋ ๋ก๊ทธ์ ๋ชจ๋ ์์ฒญ์ ํฉ์ฐํ์ฌ ์๋ ์ ํ๊ณผ ๋น๊ตํฉ๋๋ค. ์ด ๋ฐฉ์์ ์ ํํ์ง๋ง, ๋ฉ๋ชจ๋ฆฌ ๋ฐ ์ฒ๋ฆฌ ๋ฅ๋ ฅ ์ธก๋ฉด์์ ๋น์ฉ์ด ๋ง์ด ๋ค ์ ์์ต๋๋ค.
2.2. ๊ณ ์ ์๋์ฐ ์นด์ดํฐ
๊ณ ์ ์๋์ฐ ์นด์ดํฐ ์๊ณ ๋ฆฌ์ฆ์ ์๊ฐ์ ๊ณ ์ ๋ ์๋์ฐ๋ก ๋๋๊ณ ๊ฐ ์๋์ฐ์ ๋ํ ์นด์ดํฐ๋ฅผ ์ ์งํฉ๋๋ค. ์ ์์ฒญ์ด ๋์ฐฉํ๋ฉด ์๊ณ ๋ฆฌ์ฆ์ ํ์ฌ ์๋์ฐ์ ๋ํ ์นด์ดํฐ๋ฅผ ์ฆ๊ฐ์ํต๋๋ค. ์นด์ดํฐ๊ฐ ์ ํ์ ์ด๊ณผํ๋ฉด ์์ฒญ์ ๊ฑฐ๋ถ๋ฉ๋๋ค. ์ด ๋ฐฉ์์ ์ฌ๋ผ์ด๋ฉ ๋ก๊ทธ๋ณด๋ค ๊ฐ๋จํ์ง๋ง, ๋ ์๋์ฐ์ ๊ฒฝ๊ณ์์ ์์ฒญ ๋ฒ์คํธ๋ฅผ ํ์ฉํ ์ ์์ต๋๋ค.
Python์์์ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ๊ตฌํ (๊ณ ์ ์๋์ฐ ์นด์ดํฐ)
๋ค์์ ๊ณ ์ ์๋์ฐ ์นด์ดํฐ ๋ฐฉ์์ ์ฌ์ฉํ๋ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ์๊ณ ๋ฆฌ์ฆ์ Python ๊ตฌํ์ ๋๋ค:
import time
import threading
class SlidingWindowCounter:
def __init__(self, window_size, max_requests):
self.window_size = window_size # ์ด
self.max_requests = max_requests
self.request_counts = {}
self.lock = threading.Lock()
def is_allowed(self, client_id):
with self.lock:
current_time = int(time.time())
window_start = current_time - self.window_size
# ์ค๋๋ ์์ฒญ ์ ๋ฆฌ
self.request_counts = {ts: count for ts, count in self.request_counts.items() if ts > window_start}
total_requests = sum(self.request_counts.values())
if total_requests < self.max_requests:
self.request_counts[current_time] = self.request_counts.get(current_time, 0) + 1
return True
else:
return False
# ์์ ์ฌ์ฉ๋ฒ
window_size = 60 # 60์ด
max_requests = 10 # ๋ถ๋น 10๊ฐ ์์ฒญ
rate_limiter = SlidingWindowCounter(window_size, max_requests)
client_id = "user123"
for i in range(15):
if rate_limiter.is_allowed(client_id):
print(f"์์ฒญ {i+1}: ํ์ฉ๋จ")
else:
print(f"์์ฒญ {i+1}: ์๋ ์ ํ๋จ")
time.sleep(5)
์ค๋ช :
SlidingWindowCounter(window_size, max_requests): ์๋์ฐ ํฌ๊ธฐ(์ด ๋จ์)์ ์๋์ฐ ๋ด์์ ํ์ฉ๋๋ ์ต๋ ์์ฒญ ์๋ฅผ ์ด๊ธฐํํฉ๋๋ค.is_allowed(client_id): ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญ์ ๋ณด๋ผ ์ ์๋์ง ํ์ธํฉ๋๋ค. ์๋์ฐ ๋ฐ์ ์ค๋๋ ์์ฒญ์ ์ ๋ฆฌํ๊ณ , ๋จ์์๋ ์์ฒญ์ ํฉ์ฐํ๋ฉฐ, ์ ํ์ด ์ด๊ณผ๋์ง ์์ ๊ฒฝ์ฐ ํ์ฌ ์๋์ฐ์ ์นด์ดํธ๋ฅผ ์ฆ๊ฐ์ํต๋๋ค.self.request_counts: ์์ฒญ ํ์์คํฌํ์ ๊ทธ ์นด์ดํธ๋ฅผ ์ ์ฅํ๋ ๋์ ๋๋ฆฌ๋ก, ์ค๋๋ ์์ฒญ์ ์ง๊ณํ๊ณ ์ ๋ฆฌํ ์ ์์ต๋๋ค.- ์ค๋ ๋ฉ ๋ฝ: ๋์์ฑ ํ๊ฒฝ์์ ์ค๋ ๋ ์์ ์ฑ์ ๋ณด์ฅํ๊ธฐ ์ํด ์ค๋ ๋ฉ ๋ฝ(
self.lock)์ ์ฌ์ฉํฉ๋๋ค.
์ฌ๋ผ์ด๋ฉ ์๋์ฐ์ ์ฅ์
- ๋ ๋์ ์ ํ์ฑ: ํนํ ์ฌ๋ผ์ด๋ฉ ๋ก๊ทธ ๊ตฌํ์ ๊ฒฝ์ฐ ํ ํฐ ๋ฒํท๋ณด๋ค ๋ ์ ํํ ์๋ ์ ํ์ ์ ๊ณตํฉ๋๋ค.
- ๊ฒฝ๊ณ ๋ฒ์คํธ ๋ฐฉ์ง: ๋ ์๊ฐ ์๋์ฐ ๊ฒฝ๊ณ์์ ๋ฒ์คํธ ๋ฐ์ ๊ฐ๋ฅ์ฑ์ ์ค์ ๋๋ค (์ฌ๋ผ์ด๋ฉ ๋ก๊ทธ์์ ๋ ํจ๊ณผ์ ).
์ฌ๋ผ์ด๋ฉ ์๋์ฐ์ ๋จ์
- ๋ ๋ณต์กํจ: ํ ํฐ ๋ฒํท์ ๋นํด ๊ตฌํํ๊ณ ์ดํดํ๊ธฐ ๋ ๋ณต์กํฉ๋๋ค.
- ๋ ๋์ ์ค๋ฒํค๋: ์์ฒญ ๋ก๊ทธ๋ฅผ ์ ์ฅํ๊ณ ์ฒ๋ฆฌํด์ผ ํ๋ฏ๋ก ํนํ ์ฌ๋ผ์ด๋ฉ ๋ก๊ทธ ๊ตฌํ์์ ์ค๋ฒํค๋๊ฐ ๋ ๋์ ์ ์์ต๋๋ค.
ํ ํฐ ๋ฒํท vs. ์ฌ๋ผ์ด๋ฉ ์๋์ฐ: ์์ธ ๋น๊ต
๋ค์์ ํ ํฐ ๋ฒํท๊ณผ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ์๊ณ ๋ฆฌ์ฆ ๊ฐ์ ์ฃผ์ ์ฐจ์ด์ ์ ์์ฝํ ํ์ ๋๋ค:
| ํน์ง | ํ ํฐ ๋ฒํท | ์ฌ๋ผ์ด๋ฉ ์๋์ฐ |
|---|---|---|
| ๋ณต์ก์ฑ | ๋ ๊ฐ๋จํจ | ๋ ๋ณต์กํจ |
| ์ ํ์ฑ | ๋ ์ ํํจ | ๋ ์ ํํจ |
| ๋ฒ์คํธ ์ฒ๋ฆฌ | ์ข์ | ์ข์ (ํนํ ์ฌ๋ผ์ด๋ฉ ๋ก๊ทธ) |
| ์ค๋ฒํค๋ | ๋ ๋ฎ์ | ๋ ๋์ (ํนํ ์ฌ๋ผ์ด๋ฉ ๋ก๊ทธ) |
| ๊ตฌํ ๋ ธ๋ ฅ | ๋ ์ฌ์ | ๋ ์ด๋ ค์ |
์ฌ๋ฐ๋ฅธ ์๊ณ ๋ฆฌ์ฆ ์ ํ
ํ ํฐ ๋ฒํท๊ณผ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ์ค ์ด๋ค ๊ฒ์ ์ ํํ ์ง๋ ํน์ ์๊ตฌ ์ฌํญ๊ณผ ์ฐ์ ์์์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋๋ค. ๋ค์ ์์๋ฅผ ๊ณ ๋ คํ์ญ์์ค:
- ์ ํ์ฑ: ๋งค์ฐ ์ ํํ ์๋ ์ ํ์ด ํ์ํ ๊ฒฝ์ฐ, ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ์๊ณ ๋ฆฌ์ฆ์ด ์ผ๋ฐ์ ์ผ๋ก ์ ํธ๋ฉ๋๋ค.
- ๋ณต์ก์ฑ: ๋จ์์ฑ์ด ์ฐ์ ์ด๋ผ๋ฉด, ํ ํฐ ๋ฒํท ์๊ณ ๋ฆฌ์ฆ์ด ์ข์ ์ ํ์ ๋๋ค.
- ์ฑ๋ฅ: ์ฑ๋ฅ์ด ์ค์ํ๋ค๋ฉด, ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ์๊ณ ๋ฆฌ์ฆ, ํนํ ์ฌ๋ผ์ด๋ฉ ๋ก๊ทธ ๊ตฌํ์ ์ค๋ฒํค๋๋ฅผ ์ ์คํ๊ฒ ๊ณ ๋ คํ์ญ์์ค.
- ๋ฒ์คํธ ์ฒ๋ฆฌ: ๋ ์๊ณ ๋ฆฌ์ฆ ๋ชจ๋ ํธ๋ํฝ ๋ฒ์คํธ๋ฅผ ์ฒ๋ฆฌํ ์ ์์ง๋ง, ์ฌ๋ผ์ด๋ฉ ์๋์ฐ(์ฌ๋ผ์ด๋ฉ ๋ก๊ทธ)๋ ๋ฒ์คํธ ์ํฉ์์ ๋ ์ผ๊ด๋ ์๋ ์ ํ์ ์ ๊ณตํฉ๋๋ค.
- ํ์ฅ์ฑ: ๊ณ ๋๋ก ํ์ฅ ๊ฐ๋ฅํ ์์คํ ์ ๊ฒฝ์ฐ, ๋ถ์ฐ ์๋ ์ ํ ๊ธฐ์ (์๋์์ ๋ ผ์๋จ) ์ฌ์ฉ์ ๊ณ ๋ คํ์ญ์์ค.
๋ง์ ๊ฒฝ์ฐ์ ํ ํฐ ๋ฒํท ์๊ณ ๋ฆฌ์ฆ์ ๋น๊ต์ ๋ฎ์ ๊ตฌํ ๋น์ฉ์ผ๋ก ์ถฉ๋ถํ ์์ค์ ์๋ ์ ํ์ ์ ๊ณตํฉ๋๋ค. ๊ทธ๋ฌ๋ ๋ ์ ํํ ์๋ ์ ํ์ด ํ์ํ๊ณ ์ฆ๊ฐ๋ ๋ณต์ก์ฑ์ ๊ฐ์ํ ์ ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฒฝ์ฐ, ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ์๊ณ ๋ฆฌ์ฆ์ด ๋ ๋์ ์ ํ์ ๋๋ค.
๋ถ์ฐ ์๋ ์ ํ
์ฌ๋ฌ ์๋ฒ๊ฐ ์์ฒญ์ ์ฒ๋ฆฌํ๋ ๋ถ์ฐ ์์คํ ์์๋ ๋ชจ๋ ์๋ฒ์ ๊ฑธ์ณ ์ผ๊ด๋ ์๋ ์ ํ์ ๋ณด์ฅํ๊ธฐ ์ํด ์ค์ ์ง์ค์ ์๋ ์ ํ ๋ฉ์ปค๋์ฆ์ด ์ข ์ข ํ์ํฉ๋๋ค. ๋ถ์ฐ ์๋ ์ ํ์๋ ์ฌ๋ฌ ์ ๊ทผ ๋ฐฉ์์ด ์ฌ์ฉ๋ ์ ์์ต๋๋ค:
- ์ค์ ์ง์ค์ ๋ฐ์ดํฐ ์ ์ฅ์: Redis ๋๋ Memcached์ ๊ฐ์ ์ค์ ์ง์ค์ ๋ฐ์ดํฐ ์ ์ฅ์๋ฅผ ์ฌ์ฉํ์ฌ ์๋ ์ ํ ์ํ(์: ํ ํฐ ์ ๋๋ ์์ฒญ ๋ก๊ทธ)๋ฅผ ์ ์ฅํฉ๋๋ค. ๋ชจ๋ ์๋ฒ๋ ๊ณต์ ๋ฐ์ดํฐ ์ ์ฅ์์ ์ก์ธ์คํ๊ณ ์ ๋ฐ์ดํธํ์ฌ ์๋ ์ ํ์ ์ ์ฉํฉ๋๋ค.
- ๋ก๋ ๋ฐธ๋ฐ์ ์๋ ์ ํ: IP ์ฃผ์, ์ฌ์ฉ์ ID ๋๋ ๊ธฐํ ๊ธฐ์ค์ ๋ฐ๋ผ ์๋ ์ ํ์ ์ํํ๋๋ก ๋ก๋ ๋ฐธ๋ฐ์๋ฅผ ๊ตฌ์ฑํฉ๋๋ค. ์ด ์ ๊ทผ ๋ฐฉ์์ ์ ํ๋ฆฌ์ผ์ด์ ์๋ฒ์์ ์๋ ์ ํ ์์ ์ ์คํ๋ก๋ํ ์ ์์ต๋๋ค.
- ์ ์ฉ ์๋ ์ ํ ์๋น์ค: ๋ชจ๋ ์๋ ์ ํ ์์ฒญ์ ์ฒ๋ฆฌํ๋ ์ ์ฉ ์๋ ์ ํ ์๋น์ค๋ฅผ ์์ฑํฉ๋๋ค. ์ด ์๋น์ค๋ ๋ ๋ฆฝ์ ์ผ๋ก ํ์ฅํ๊ณ ์ฑ๋ฅ์ ์ต์ ํ๋ ์ ์์ต๋๋ค.
- ํด๋ผ์ด์ธํธ ์ธก ์๋ ์ ํ: ์ฃผ์ ๋ฐฉ์ด์ฑ
์ ์๋์ง๋ง, HTTP ํค๋(์:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset)๋ฅผ ํตํด ํด๋ผ์ด์ธํธ์๊ฒ ์๋ ์ ํ์ ์๋ ค์ค๋๋ค. ์ด๋ ํด๋ผ์ด์ธํธ๊ฐ ์์ฒด์ ์ผ๋ก ์ค๋กํ๋งํ๊ณ ๋ถํ์ํ ์์ฒญ์ ์ค์ด๋๋ก ์ฅ๋ คํ ์ ์์ต๋๋ค.
๋ค์์ ๋ถ์ฐ ์๋ ์ ํ์ ์ํด Redis์ ํ ํฐ ๋ฒํท ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํ๋ ์์์ ๋๋ค:
import redis
import time
class RedisTokenBucket:
def __init__(self, redis_client, bucket_key, capacity, fill_rate):
self.redis_client = redis_client
self.bucket_key = bucket_key
self.capacity = capacity
self.fill_rate = fill_rate
def consume(self, tokens):
now = time.time()
capacity = self.capacity
fill_rate = self.fill_rate
# Redis์์ ํ ํฐ ๋ฒํท์ ์์์ ์ผ๋ก ์
๋ฐ์ดํธํ๊ธฐ ์ํ Lua ์คํฌ๋ฆฝํธ
script = '''
local bucket_key = KEYS[1]
local capacity = tonumber(ARGV[1])
local fill_rate = tonumber(ARGV[2])
local tokens_to_consume = tonumber(ARGV[3])
local now = tonumber(ARGV[4])
local last_refill = redis.call('get', bucket_key .. ':last_refill')
if not last_refill then
last_refill = now
redis.call('set', bucket_key .. ':last_refill', now)
else
last_refill = tonumber(last_refill)
end
local tokens = redis.call('get', bucket_key .. ':tokens')
if not tokens then
tokens = capacity
redis.call('set', bucket_key .. ':tokens', capacity)
else
tokens = tonumber(tokens)
end
-- ๋ฒํท ์ฑ์ฐ๊ธฐ
local time_since_last_refill = now - last_refill
local tokens_to_add = time_since_last_refill * fill_rate
tokens = math.min(capacity, tokens + tokens_to_add)
-- ํ ํฐ ์๋น
if tokens >= tokens_to_consume then
tokens = tokens - tokens_to_consume
redis.call('set', bucket_key .. ':tokens', tokens)
redis.call('set', bucket_key .. ':last_refill', now)
return 1 -- ์ฑ๊ณต
else
return 0 -- ์๋ ์ ํ๋จ
end
'''
# Lua ์คํฌ๋ฆฝํธ ์คํ
consume_script = self.redis_client.register_script(script)
result = consume_script(keys=[self.bucket_key], args=[capacity, fill_rate, tokens, now])
return result == 1
# ์์ ์ฌ์ฉ๋ฒ
redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
bucket = RedisTokenBucket(redis_client, bucket_key='my_api:user123', capacity=10, fill_rate=2)
for i in range(15):
if bucket.consume(1):
print(f"์์ฒญ {i+1}: ํ์ฉ๋จ")
else:
print(f"์์ฒญ {i+1}: ์๋ ์ ํ๋จ")
time.sleep(0.2)
๋ถ์ฐ ์์คํ ์ ์ํ ์ค์ํ ๊ณ ๋ ค ์ฌํญ:
- ์์์ฑ: ๊ฒฝ์ ์กฐ๊ฑด์ ๋ฐฉ์งํ๊ธฐ ์ํด ํ ํฐ ์๋น ๋๋ ์์ฒญ ์นด์ดํ ์์ ์ด ์์์ ์ผ๋ก ์ด๋ฃจ์ด์ง๋๋ก ๋ณด์ฅํฉ๋๋ค. Redis Lua ์คํฌ๋ฆฝํธ๋ ์์์ ์ฐ์ฐ์ ์ ๊ณตํฉ๋๋ค.
- ์ง์ฐ ์๊ฐ: ์ค์ ์ง์ค์ ๋ฐ์ดํฐ ์ ์ฅ์์ ์ก์ธ์คํ ๋ ๋คํธ์ํฌ ์ง์ฐ ์๊ฐ์ ์ต์ํํฉ๋๋ค.
- ํ์ฅ์ฑ: ์์๋๋ ๋ถํ๋ฅผ ์ฒ๋ฆฌํ ์ ์๋๋ก ํ์ฅ ๊ฐ๋ฅํ ๋ฐ์ดํฐ ์ ์ฅ์๋ฅผ ์ ํํ์ญ์์ค.
- ๋ฐ์ดํฐ ์ผ๊ด์ฑ: ๋ถ์ฐ ํ๊ฒฝ์์ ๋ฐ์ํ ์ ์๋ ๋ฐ์ดํฐ ์ผ๊ด์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๋ค.
์๋ ์ ํ์ ์ํ ๋ชจ๋ฒ ์ฌ๋ก
์๋ ์ ํ์ ๊ตฌํํ ๋ ๋ฐ๋ผ์ผ ํ ๋ช ๊ฐ์ง ๋ชจ๋ฒ ์ฌ๋ก๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ์๋ ์ ํ ์๊ตฌ ์ฌํญ ์๋ณ: ์ฌ์ฉ ํจํด ๋ฐ ๋ฆฌ์์ค ์๋น๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ค์ํ API ์๋ํฌ์ธํธ ๋ฐ ์ฌ์ฉ์ ๊ทธ๋ฃน์ ๋ํ ์ ์ ํ ์๋ ์ ํ์ ๊ฒฐ์ ํฉ๋๋ค. ๊ตฌ๋ ์์ค์ ๋ฐ๋ผ ๊ณ์ธตํ๋ ์ก์ธ์ค๋ฅผ ์ ๊ณตํ๋ ๊ฒ์ ๊ณ ๋ คํ์ญ์์ค.
- ์๋ฏธ ์๋ HTTP ์ํ ์ฝ๋ ์ฌ์ฉ:
429 Too Many Requests์ ๊ฐ์ด ์๋ ์ ํ์ ๋ํ๋ด๋ ์ ์ ํ HTTP ์ํ ์ฝ๋๋ฅผ ๋ฐํํฉ๋๋ค. - ์๋ ์ ํ ํค๋ ํฌํจ: API ์๋ต์ ์๋ ์ ํ ํค๋๋ฅผ ํฌํจํ์ฌ ํด๋ผ์ด์ธํธ์๊ฒ ํ์ฌ ์๋ ์ ํ ์ํ(์:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset)๋ฅผ ์๋ฆฝ๋๋ค. - ๋ช ํํ ์ค๋ฅ ๋ฉ์์ง ์ ๊ณต: ํด๋ผ์ด์ธํธ๊ฐ ์๋ ์ ํ๋ ๋, ์ด์ ๋ฅผ ์ค๋ช ํ๊ณ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ์ ์ํ๋ ์ ์ฉํ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ง์์ ์ํ ์ฐ๋ฝ์ฒ ์ ๋ณด๋ฅผ ์ ๊ณตํ์ญ์์ค.
- ์ ์ง์ ์ฑ๋ฅ ์ ํ ๊ตฌํ: ์๋ ์ ํ์ด ์ ์ฉ๋ ๋, ์์ฒญ์ ์์ ํ ์ฐจ๋จํ๋ ๋์ ์ ํ๋ ์๋น์ค๋ฅผ ์ ๊ณตํ๋ ๊ฒ์ ๊ณ ๋ คํ์ญ์์ค. ์๋ฅผ ๋ค์ด, ์บ์๋ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๊ฑฐ๋ ๊ธฐ๋ฅ ์ผ๋ถ๋ฅผ ์ถ์ํ ์ ์์ต๋๋ค.
- ์๋ ์ ํ ๋ชจ๋ํฐ๋ง ๋ฐ ๋ถ์: ์๋ ์ ํ ์์คํ ์ ๋ชจ๋ํฐ๋งํ์ฌ ์ ์ฌ์ ์ธ ๋ฌธ์ ๋ฅผ ์๋ณํ๊ณ ์ฑ๋ฅ์ ์ต์ ํํฉ๋๋ค. ์ฌ์ฉ ํจํด์ ๋ถ์ํ์ฌ ํ์์ ๋ฐ๋ผ ์๋ ์ ํ์ ์กฐ์ ํฉ๋๋ค.
- ์๋ ์ ํ ๋ณด์: ์์ฒญ์ ๊ฒ์ฆํ๊ณ ์ ์ ํ ๋ณด์ ์กฐ์น๋ฅผ ๊ตฌํํ์ฌ ์ฌ์ฉ์๊ฐ ์๋ ์ ํ์ ์ฐํํ๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค.
- ์๋ ์ ํ ๋ฌธ์ํ: API ๋ฌธ์์ ์๋ ์ ํ ์ ์ฑ ์ ๋ช ํํ๊ฒ ๋ฌธ์ํํฉ๋๋ค. ํด๋ผ์ด์ธํธ๊ฐ ์๋ ์ ํ์ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ฃผ๋ ์์ ์ฝ๋๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ๊ตฌํ ํ ์คํธ: ๋ค์ํ ๋ถํ ์กฐ๊ฑด์์ ์๋ ์ ํ ๊ตฌํ์ ์ฒ ์ ํ ํ ์คํธํ์ฌ ์ฌ๋ฐ๋ฅด๊ฒ ์๋ํ๋์ง ํ์ธํฉ๋๋ค.
- ์ง์ญ์ ์ฐจ์ด ๊ณ ๋ ค: ์ ์ญ์ ์ผ๋ก ๋ฐฐํฌํ ๋ ๋คํธ์ํฌ ์ง์ฐ ์๊ฐ ๋ฐ ์ฌ์ฉ์ ํ๋์ ์ง์ญ์ ์ฐจ์ด๋ฅผ ๊ณ ๋ คํ์ญ์์ค. ์ง์ญ์ ๋ฐ๋ผ ์๋ ์ ํ์ ์กฐ์ ํด์ผ ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์ธ๋์ ๊ฐ์ ๋ชจ๋ฐ์ผ ์ฐ์ ์์ฅ์ ํ๊ตญ๊ณผ ๊ฐ์ ๊ณ ๋์ญํญ ์ง์ญ์ ๋นํด ๋ค๋ฅธ ์๋ ์ ํ์ ์๊ตฌํ ์ ์์ต๋๋ค.
์ค์ ์ฌ๋ก
- ํธ์ํฐ(Twitter): ํธ์ํฐ๋ API๋ฅผ ๋จ์ฉ์ผ๋ก๋ถํฐ ๋ณดํธํ๊ณ ๊ณต์ ํ ์ฌ์ฉ์ ๋ณด์ฅํ๊ธฐ ์ํด ์๋ ์ ํ์ ๊ด๋ฒ์ํ๊ฒ ์ฌ์ฉํฉ๋๋ค. ๊ทธ๋ค์ ์๋ ์ ํ์ ๋ํ ์์ธํ ๋ฌธ์๋ฅผ ์ ๊ณตํ๊ณ HTTP ํค๋๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ๋ฐ์์๊ฒ ์๋ ์ ํ ์ํ๋ฅผ ์๋ฆฝ๋๋ค.
- ๊นํ๋ธ(GitHub): ๊นํ๋ธ ๋ํ API ๋จ์ฉ์ ๋ฐฉ์งํ๊ณ ์์ ์ฑ์ ์ ์งํ๊ธฐ ์ํด ์๋ ์ ํ์ ์ ์ฉํฉ๋๋ค. ๊ทธ๋ค์ IP ๊ธฐ๋ฐ ๋ฐ ์ฌ์ฉ์ ๊ธฐ๋ฐ ์๋ ์ ํ์ ์กฐํฉํ์ฌ ์ฌ์ฉํฉ๋๋ค.
- ์คํธ๋ผ์ดํ(Stripe): ์คํธ๋ผ์ดํ๋ ๊ฒฐ์ ์ฒ๋ฆฌ API๋ฅผ ์ฌ๊ธฐ์ฑ ํ๋์ผ๋ก๋ถํฐ ๋ณดํธํ๊ณ ๊ณ ๊ฐ์๊ฒ ์์ ์ ์ธ ์๋น์ค๋ฅผ ๋ณด์ฅํ๊ธฐ ์ํด ์๋ ์ ํ์ ์ฌ์ฉํฉ๋๋ค.
- ์ ์์๊ฑฐ๋ ํ๋ซํผ: ๋ง์ ์ ์์๊ฑฐ๋ ํ๋ซํผ์ ์ ํ ์ ๋ณด๋ฅผ ์คํฌ๋ฉํ๊ฑฐ๋ ํ๋์ ์ธ์ผ ์ค ์๋น์ค ๊ฑฐ๋ถ ๊ณต๊ฒฉ์ ์๋ํ๋ ๋ด ๊ณต๊ฒฉ์ผ๋ก๋ถํฐ ๋ณดํธํ๊ธฐ ์ํด ์๋ ์ ํ์ ์ฌ์ฉํฉ๋๋ค.
- ๊ธ์ต ๊ธฐ๊ด: ๊ธ์ต ๊ธฐ๊ด์ ๋ฏผ๊ฐํ ๊ธ์ต ๋ฐ์ดํฐ์ ๋ํ ๋ฌด๋จ ์ก์ธ์ค๋ฅผ ๋ฐฉ์งํ๊ณ ๊ท์ ์๊ตฌ ์ฌํญ์ ์ค์ํ๊ธฐ ์ํด API์ ์๋ ์ ํ์ ๊ตฌํํฉ๋๋ค.
๊ฒฐ๋ก
์๋ ์ ํ์ API๋ฅผ ๋ณดํธํ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ ์ฑ๊ณผ ์ ๋ขฐ์ฑ์ ๋ณด์ฅํ๋ ํ์์ ์ธ ๊ธฐ์ ์ ๋๋ค. ํ ํฐ ๋ฒํท๊ณผ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ์๊ณ ๋ฆฌ์ฆ์ ๊ฐ๊ธฐ ์ฅ๋จ์ ์ ๊ฐ์ง ๋ ๊ฐ์ง ์ธ๊ธฐ ์๋ ์ต์ ์ ๋๋ค. ์ด๋ฌํ ์๊ณ ๋ฆฌ์ฆ์ ์ดํดํ๊ณ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ฐ๋ฅด๋ฉด Python ์ ํ๋ฆฌ์ผ์ด์ ์์ ์๋ ์ ํ์ ํจ๊ณผ์ ์ผ๋ก ๊ตฌํํ๊ณ ๋ ํ๋ ฅ์ ์ด๊ณ ์์ ํ ์์คํ ์ ๊ตฌ์ถํ ์ ์์ต๋๋ค. ํน์ ์๊ตฌ ์ฌํญ์ ๊ณ ๋ คํ๊ณ , ์ ์ ํ ์๊ณ ๋ฆฌ์ฆ์ ์ ์คํ๊ฒ ์ ํํ๋ฉฐ, ๊ตฌํ์ด ์๊ตฌ ์ฌํญ์ ์ถฉ์กฑํ๋์ง ๋ชจ๋ํฐ๋งํ๋ ๊ฒ์ ์์ง ๋ง์ญ์์ค. ์ ํ๋ฆฌ์ผ์ด์ ์ด ํ์ฅ๋จ์ ๋ฐ๋ผ ๋ชจ๋ ์๋ฒ์์ ์ผ๊ด๋ ์๋ ์ ํ์ ์ ์งํ๊ธฐ ์ํด ๋ถ์ฐ ์๋ ์ ํ ๊ธฐ์ ์ ์ฑํํ๋ ๊ฒ์ ๊ณ ๋ คํ์ญ์์ค. ์๋ ์ ํ ํค๋์ ์ ์ตํ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ํตํด API ์๋น์๋ค๊ณผ ๋ช ํํ๊ฒ ์ํตํ๋ ๊ฒ์ ์ค์์ฑ์ ๊ฐ๊ณผํ์ง ๋ง์ญ์์ค.