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

๐Ÿ”— ์ฐธ๊ณ  ์ž๋ฃŒ

๊ณต์‹ ๋ฌธ์„œ ๋ฐ ๊ฐ€์ด๋“œ

ํด๋ผ์šฐ๋“œ ํ”Œ๋žซํผ ๋ฌธ์„œ

๊ตฌํ˜„ ๋„๊ตฌ ๋ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

๋ชจ๋ฒ” ์‚ฌ๋ก€ ๊ฐ€์ด๋“œ

๊ณ ๊ธ‰ ์ฃผ์ œ

์ฐธ๊ณ  ๋ธ”๋กœ๊ทธ