Home > Blog > tech

Rate Limiting คืออะไร? สอนป้องกัน API ด้วย Rate Limit, Throttling และ DDoS Protection 2026

rate limiting api security guide
Rate Limiting API Security Guide 2026
2026-04-08 | tech | 3500 words

ในยุคที่ API เป็นหัวใจของทุกแอปพลิเคชัน ตั้งแต่แอปมือถือ เว็บแอป ไปจนถึงระบบ IoT การป้องกัน API จากการถูกโจมตีหรือใช้งานเกินขีดจำกัดเป็นสิ่งที่นักพัฒนาทุกคนต้องให้ความสำคัญ Rate Limiting คือเทคนิคแรกที่ต้องทำเมื่อเปิด API ให้โลกภายนอกเข้าถึงได้ หากไม่มี Rate Limiting แม้แต่ผู้ใช้คนเดียวก็อาจทำให้เซิร์ฟเวอร์ล่มได้ภายในไม่กี่วินาที

บทความนี้จะอธิบายทุกอย่างเกี่ยวกับ Rate Limiting ตั้งแต่แนวคิดพื้นฐาน Algorithm ที่ใช้ การ Implement ด้วย Node.js และ Python การใช้ Redis สำหรับ Distributed System การตั้งค่า Nginx Rate Limiting ไปจนถึง DDoS Protection แบบครบวงจร เนื้อหาทั้งหมดอ้างอิงจากแนวปฏิบัติที่ดีที่สุดในปี 2026

ทำไม Rate Limiting ถึงสำคัญ?

Rate Limiting คือกระบวนการจำกัดจำนวน Request ที่ผู้ใช้หรือ Client สามารถส่งมายัง API ได้ภายในช่วงเวลาหนึ่ง เช่น อนุญาตให้ส่งได้ 100 Request ต่อนาที หากเกินกว่านั้นจะตอบกลับด้วย HTTP 429 Too Many Requests

เหตุผลหลักที่ต้องมี Rate Limiting มีดังนี้:

Rate Limiting Algorithms อธิบายทุกแบบ

มี Algorithm หลายแบบที่ใช้ในการ Implement Rate Limiting แต่ละแบบมีข้อดีข้อเสียต่างกัน การเลือกใช้ขึ้นอยู่กับความต้องการของระบบ

1. Fixed Window Counter

เป็นวิธีที่ง่ายที่สุด แบ่งเวลาเป็นช่วงคงที่ (เช่น ทุก 1 นาที) แล้วนับจำนวน Request ในแต่ละช่วง ถ้าเกิน Limit จะปฏิเสธ

# Fixed Window Counter - Python pseudocode
import time

class FixedWindowCounter:
    def __init__(self, limit, window_seconds):
        self.limit = limit
        self.window = window_seconds
        self.counters = {}  # key -> (window_start, count)

    def is_allowed(self, key):
        now = time.time()
        window_start = int(now // self.window) * self.window

        if key not in self.counters or self.counters[key][0] != window_start:
            self.counters[key] = (window_start, 0)

        if self.counters[key][1] >= self.limit:
            return False

        self.counters[key] = (window_start, self.counters[key][1] + 1)
        return True

# ใช้งาน: 100 requests ต่อ 60 วินาที
limiter = FixedWindowCounter(limit=100, window_seconds=60)

ข้อดี: Implement ง่าย ใช้ Memory น้อย ทำงานเร็ว เหมาะกับระบบที่ไม่ต้องการความแม่นยำสูง

ข้อเสีย: มีปัญหา "Boundary Issue" คือ ถ้าส่ง Request จำนวนมากตอนท้ายของ Window หนึ่ง และต้นของ Window ถัดไป อาจได้ Traffic เป็น 2 เท่าของ Limit ในช่วงเวลาสั้นๆ ตัวอย่างเช่น ถ้า Limit 100 ต่อนาที ผู้ใช้ส่ง 100 Request ที่วินาทีที่ 59 และอีก 100 Request ที่วินาทีที่ 60 ก็เท่ากับ 200 Request ใน 2 วินาที

2. Sliding Window Log

บันทึก Timestamp ของทุก Request แล้วนับจำนวน Request ย้อนหลังไปตาม Window Size แก้ปัญหา Boundary Issue ของ Fixed Window

# Sliding Window Log - Python pseudocode
import time
from collections import defaultdict

class SlidingWindowLog:
    def __init__(self, limit, window_seconds):
        self.limit = limit
        self.window = window_seconds
        self.logs = defaultdict(list)  # key -> [timestamps]

    def is_allowed(self, key):
        now = time.time()
        cutoff = now - self.window

        # ลบ Timestamp ที่เก่าเกินไป
        self.logs[key] = [ts for ts in self.logs[key] if ts > cutoff]

        if len(self.logs[key]) >= self.limit:
            return False

        self.logs[key].append(now)
        return True

# ใช้งาน: 100 requests ต่อ 60 วินาที
limiter = SlidingWindowLog(limit=100, window_seconds=60)

ข้อดี: แม่นยำมาก ไม่มีปัญหา Boundary Issue เห็นข้อมูลชัดเจน

ข้อเสีย: ใช้ Memory มาก เพราะต้องเก็บ Timestamp ทุก Request ไม่เหมาะกับระบบที่มี Traffic สูงมาก

3. Sliding Window Counter

ผสมผสานข้อดีของ Fixed Window Counter (ประหยัด Memory) และ Sliding Window Log (แม่นยำ) โดยใช้การคำนวณน้ำหนักของ Window ปัจจุบันและ Window ก่อนหน้า

# Sliding Window Counter - Python pseudocode
import time

class SlidingWindowCounter:
    def __init__(self, limit, window_seconds):
        self.limit = limit
        self.window = window_seconds
        self.counters = {}  # key -> {window: count}

    def is_allowed(self, key):
        now = time.time()
        current_window = int(now // self.window) * self.window
        previous_window = current_window - self.window

        if key not in self.counters:
            self.counters[key] = {}

        prev_count = self.counters[key].get(previous_window, 0)
        curr_count = self.counters[key].get(current_window, 0)

        # คำนวณน้ำหนัก: เวลาที่ผ่านไปของ window ปัจจุบัน
        elapsed = now - current_window
        weight = 1 - (elapsed / self.window)
        estimated = prev_count * weight + curr_count

        if estimated >= self.limit:
            return False

        self.counters[key][current_window] = curr_count + 1
        return True

ข้อดี: ใช้ Memory น้อย แม่นยำพอสมควร เป็นตัวเลือกยอดนิยมในระบบ Production

ข้อเสีย: ค่าที่ได้เป็นการประมาณ ไม่แม่นยำ 100% แต่ในทางปฏิบัติเพียงพอสำหรับงานส่วนใหญ่

4. Token Bucket Algorithm

เปรียบเสมือนถังที่เติม Token เข้าไปอย่างสม่ำเสมอ แต่ละ Request ใช้ 1 Token ถ้า Token หมดก็ต้องรอ อนุญาตให้มี Burst ได้ตามจำนวน Token ที่สะสม

# Token Bucket - Python pseudocode
import time

class TokenBucket:
    def __init__(self, capacity, refill_rate):
        self.capacity = capacity        # จำนวน Token สูงสุด
        self.refill_rate = refill_rate  # Token ที่เติมต่อวินาที
        self.buckets = {}              # key -> (tokens, last_refill)

    def is_allowed(self, key, tokens_needed=1):
        now = time.time()

        if key not in self.buckets:
            self.buckets[key] = (self.capacity, now)

        current_tokens, last_refill = self.buckets[key]

        # เติม Token ตามเวลาที่ผ่านไป
        elapsed = now - last_refill
        new_tokens = min(self.capacity, current_tokens + elapsed * self.refill_rate)

        if new_tokens < tokens_needed:
            return False

        self.buckets[key] = (new_tokens - tokens_needed, now)
        return True

# ใช้งาน: ถังจุ 100 Token, เติม 10 Token ต่อวินาที
limiter = TokenBucket(capacity=100, refill_rate=10)

ข้อดี: รองรับ Burst Traffic ได้ดี ใช้ Memory น้อย เป็นที่นิยมมากในระบบจริง Amazon API Gateway ใช้ Algorithm นี้

ข้อเสีย: การตั้งค่า Capacity และ Refill Rate ต้องคิดดีๆ ไม่งั้นอาจทำให้ Burst มากเกินไปหรือรัดเกินไป

5. Leaky Bucket Algorithm

เปรียบเสมือนถังที่มีรูรั่ว Request เข้ามาเติมในถัง และจะถูกประมวลผลในอัตราคงที่ (Leak Rate) ถ้าถังเต็มก็ปฏิเสธ Request ใหม่ ทำให้ Output Rate คงที่สม่ำเสมอ

# Leaky Bucket - Python pseudocode
import time
from collections import deque

class LeakyBucket:
    def __init__(self, capacity, leak_rate):
        self.capacity = capacity      # ขนาดถัง
        self.leak_rate = leak_rate    # จำนวนที่ปล่อยต่อวินาที
        self.buckets = {}            # key -> (queue_size, last_leak)

    def is_allowed(self, key):
        now = time.time()

        if key not in self.buckets:
            self.buckets[key] = (0, now)

        queue_size, last_leak = self.buckets[key]

        # ลด Queue ตามเวลาที่ผ่านไป
        elapsed = now - last_leak
        leaked = int(elapsed * self.leak_rate)
        queue_size = max(0, queue_size - leaked)

        if leaked > 0:
            last_leak = now

        if queue_size >= self.capacity:
            return False

        self.buckets[key] = (queue_size + 1, last_leak)
        return True

# ใช้งาน: ถังจุ 50, ปล่อย 5 ต่อวินาที
limiter = LeakyBucket(capacity=50, leak_rate=5)

ข้อดี: Output Rate คงที่สม่ำเสมอ เหมาะกับระบบที่ต้องการควบคุม Processing Rate อย่างเข้มงวด

ข้อเสีย: ไม่รองรับ Burst เลย ทำให้ประสบการณ์ผู้ใช้อาจไม่ดีในบางสถานการณ์

เปรียบเทียบ Algorithm ทั้ง 5 แบบ

AlgorithmMemoryความแม่นยำBurstความยากใช้บ่อย
Fixed Windowน้อยต่ำมี Boundaryง่ายระบบเล็ก
Sliding Logมากสูงมากไม่มีกลางระบบเล็ก
Sliding Counterน้อยดีเล็กน้อยกลางCloudflare
Token Bucketน้อยดีรองรับกลางAWS, Stripe
Leaky Bucketน้อยดีไม่รองรับกลางNginx

Implement Rate Limiting ด้วย Node.js

สำหรับ Node.js กับ Express สามารถใช้ Library express-rate-limit ที่เป็นมาตรฐานและใช้งานง่ายมาก

// ติดตั้ง
// npm install express-rate-limit

const express = require('express');
const rateLimit = require('express-rate-limit');
const app = express();

// Rate limiter พื้นฐาน
const generalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 นาที
  max: 100,                   // 100 requests ต่อ window
  standardHeaders: true,      // ส่ง RateLimit-* headers
  legacyHeaders: false,       // ปิด X-RateLimit-* headers เก่า
  message: {
    status: 429,
    error: 'Too Many Requests',
    message: 'กรุณารอสักครู่แล้วลองใหม่อีกครั้ง',
    retryAfter: '15 minutes'
  }
});

// Rate limiter สำหรับ Login (เข้มงวดกว่า)
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,    // 5 ครั้งต่อ 15 นาที
  message: {
    status: 429,
    error: 'Too many login attempts',
    message: 'ล็อกอินผิดหลายครั้งเกินไป กรุณารอ 15 นาที'
  },
  skipSuccessfulRequests: true  // ไม่นับ Request ที่สำเร็จ
});

// Rate limiter สำหรับ API Key (per key)
const apiKeyLimiter = rateLimit({
  windowMs: 60 * 1000,  // 1 นาที
  max: 60,
  keyGenerator: (req) => {
    return req.headers['x-api-key'] || req.ip;
  },
  handler: (req, res) => {
    res.status(429).json({
      error: 'Rate limit exceeded',
      retryAfter: Math.ceil(req.rateLimit.resetTime / 1000)
    });
  }
});

// ใช้งาน
app.use(generalLimiter);
app.use('/api/auth/login', loginLimiter);
app.use('/api/v1', apiKeyLimiter);

app.get('/api/data', (req, res) => {
  res.json({ message: 'Success', data: [] });
});

app.listen(3000);
เคล็ดลับ: ใช้ skipSuccessfulRequests: true สำหรับ Login Endpoint เพื่อไม่นับ Request ที่ Login สำเร็จ ผู้ใช้จริงจะไม่ถูกจำกัด เฉพาะคนที่พยายาม Brute Force เท่านั้นที่จะโดน

Implement Rate Limiting ด้วย Python

FastAPI + SlowApi

# pip install slowapi

from fastapi import FastAPI, Request
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded

limiter = Limiter(key_func=get_remote_address)
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

@app.get("/api/data")
@limiter.limit("100/minute")
async def get_data(request: Request):
    return {"message": "Success", "data": []}

@app.post("/api/auth/login")
@limiter.limit("5/15minutes")
async def login(request: Request):
    return {"token": "jwt_token_here"}

# Rate limit ต่าง key function
def get_api_key(request: Request):
    return request.headers.get("X-API-Key", get_remote_address(request))

@app.get("/api/premium")
@limiter.limit("1000/hour", key_func=get_api_key)
async def premium_endpoint(request: Request):
    return {"premium": True}

Flask + Flask-Limiter

# pip install Flask-Limiter

from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

app = Flask(__name__)
limiter = Limiter(
    get_remote_address,
    app=app,
    default_limits=["200 per day", "50 per hour"],
    storage_uri="redis://localhost:6379"  # ใช้ Redis
)

@app.route("/api/data")
@limiter.limit("100/minute")
def get_data():
    return {"message": "Success"}

@app.route("/api/upload", methods=["POST"])
@limiter.limit("10/hour")
def upload_file():
    return {"uploaded": True}

# Exempt จาก Rate Limit
@app.route("/health")
@limiter.exempt
def health_check():
    return {"status": "ok"}

Redis-based Distributed Rate Limiting

ในระบบ Production ที่มีหลายเซิร์ฟเวอร์ การเก็บ Counter ไว้ใน Memory ของเซิร์ฟเวอร์แต่ละตัวไม่ได้ผล เพราะผู้ใช้อาจถูก Load Balance ไปคนละเครื่อง ทำให้ Limit ไม่แม่นยำ ต้องใช้ Redis เป็น Central Store เพื่อให้ทุกเซิร์ฟเวอร์อ่านเขียน Counter ร่วมกัน

# Redis Token Bucket Implementation
import redis
import time

r = redis.Redis(host='localhost', port=6379, db=0)

# Lua Script สำหรับ Atomic Token Bucket (ป้องกัน Race Condition)
TOKEN_BUCKET_SCRIPT = """
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local refill_rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])

local bucket = redis.call('hmget', key, 'tokens', 'last_refill')
local tokens = tonumber(bucket[1])
local last_refill = tonumber(bucket[2])

if tokens == nil then
    tokens = capacity
    last_refill = now
end

local elapsed = now - last_refill
local new_tokens = math.min(capacity, tokens + elapsed * refill_rate)

if new_tokens >= requested then
    new_tokens = new_tokens - requested
    redis.call('hmset', key, 'tokens', new_tokens, 'last_refill', now)
    redis.call('expire', key, math.ceil(capacity / refill_rate) * 2)
    return 1
else
    redis.call('hmset', key, 'tokens', new_tokens, 'last_refill', now)
    redis.call('expire', key, math.ceil(capacity / refill_rate) * 2)
    return 0
end
"""

# ลงทะเบียน Lua Script
token_bucket_sha = r.register_script(TOKEN_BUCKET_SCRIPT)

def check_rate_limit(user_id, capacity=100, refill_rate=10):
    key = f"rate_limit:{user_id}"
    now = time.time()
    result = token_bucket_sha(
        keys=[key],
        args=[capacity, refill_rate, now, 1]
    )
    return bool(result)

# ใช้งาน
if check_rate_limit("user_123"):
    print("Request allowed")
else:
    print("Rate limit exceeded")
ทำไมต้องใช้ Lua Script? เพราะ Redis execute Lua Script แบบ Atomic ทำให้ไม่มี Race Condition แม้จะมีหลาย Server เรียกพร้อมกัน ถ้าใช้ GET แล้ว SET แยกกัน อาจเกิดปัญหาผู้ใช้ส่ง Request พร้อมกันหลาย Request แล้วทุก Request เห็น Token เหลือพอ

Redis Sliding Window Counter

# Redis Sliding Window Counter ด้วย Sorted Set
import redis
import time
import uuid

r = redis.Redis(host='localhost', port=6379, db=0)

SLIDING_WINDOW_SCRIPT = """
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
local member = ARGV[4]

-- ลบ Request ที่เก่าเกินไป
redis.call('zremrangebyscore', key, 0, now - window)

-- นับ Request ปัจจุบัน
local count = redis.call('zcard', key)

if count < limit then
    redis.call('zadd', key, now, member)
    redis.call('expire', key, window)
    return 1
else
    return 0
end
"""

sliding_sha = r.register_script(SLIDING_WINDOW_SCRIPT)

def check_sliding_window(user_id, limit=100, window=60):
    key = f"sliding:{user_id}"
    now = time.time()
    member = str(uuid.uuid4())
    result = sliding_sha(
        keys=[key],
        args=[now, window, limit, member]
    )
    return bool(result)

Nginx Rate Limiting

Nginx มีโมดูล ngx_http_limit_req_module ในตัว ใช้ Leaky Bucket Algorithm สามารถตั้งค่า Rate Limiting ได้โดยไม่ต้องเขียน Code ในแอปพลิเคชัน เป็นด่านแรกก่อนที่ Request จะเข้าถึงแอป

# nginx.conf

http {
    # กำหนด Zone สำหรับ Rate Limiting
    # $binary_remote_addr ใช้ IP address เป็น key (ใช้ memory น้อยกว่า $remote_addr)
    # zone=api:10m คือชื่อ zone "api" ใช้ memory 10MB (~160,000 IPs)
    # rate=10r/s คือ 10 requests ต่อวินาที
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

    # Zone สำหรับ Login
    limit_req_zone $binary_remote_addr zone=login:5m rate=1r/s;

    # Zone ตาม API Key
    map $http_x_api_key $api_key {
        default $binary_remote_addr;
        "~.+" $http_x_api_key;
    }
    limit_req_zone $api_key zone=api_key:10m rate=30r/s;

    # Custom error page สำหรับ 429
    limit_req_status 429;

    server {
        listen 80;
        server_name api.example.com;

        # API endpoints
        location /api/ {
            # burst=20 อนุญาต Burst 20 requests
            # nodelay ประมวลผล Burst ทันทีไม่ต้องรอ
            limit_req zone=api burst=20 nodelay;

            proxy_pass http://backend;
        }

        # Login endpoint (เข้มงวดกว่า)
        location /api/auth/login {
            limit_req zone=login burst=5 nodelay;
            proxy_pass http://backend;
        }

        # Premium API
        location /api/v2/ {
            limit_req zone=api_key burst=50 nodelay;
            proxy_pass http://backend;
        }

        # Health check ไม่ต้อง Rate Limit
        location /health {
            proxy_pass http://backend;
        }

        # Custom 429 response
        error_page 429 = @rate_limited;
        location @rate_limited {
            default_type application/json;
            return 429 '{"error":"Too Many Requests","retry_after":60}';
        }
    }
}
burst vs nodelay: burst=20 อนุญาตให้ Request เกิน Rate ได้ 20 ตัว โดยจะ Queue ไว้ เพิ่ม nodelay เพื่อประมวลผล Burst ทันทีแทนที่จะชะลอ ถ้าไม่ใส่ nodelay Request ที่เกิน Rate จะถูก Delay ให้เป็นไปตาม Rate ที่กำหนด

API Gateway Rate Limiting

Kong Gateway

Kong เป็น API Gateway ยอดนิยมที่มี Rate Limiting Plugin ในตัว รองรับทั้ง Local และ Redis-based Distributed Rate Limiting

# เปิดใช้ Rate Limiting Plugin ของ Kong
curl -X POST http://localhost:8001/services/my-api/plugins \
  --data "name=rate-limiting" \
  --data "config.minute=100" \
  --data "config.hour=1000" \
  --data "config.policy=redis" \
  --data "config.redis_host=redis" \
  --data "config.redis_port=6379" \
  --data "config.limit_by=consumer" \
  --data "config.fault_tolerant=true"

# Rate Limiting ตาม Consumer Group (Tiered)
# Free Tier: 100/hour
curl -X POST http://localhost:8001/consumer_groups/free/plugins \
  --data "name=rate-limiting-advanced" \
  --data "config.limit=[100]" \
  --data "config.window_size=[3600]"

# Pro Tier: 10,000/hour
curl -X POST http://localhost:8001/consumer_groups/pro/plugins \
  --data "name=rate-limiting-advanced" \
  --data "config.limit=[10000]" \
  --data "config.window_size=[3600]"

AWS API Gateway

# AWS API Gateway Throttling
# ตั้งค่าผ่าน AWS CLI

# Account Level: 10,000 requests/second, burst 5,000
aws apigateway update-account \
  --patch-operations op=replace,path=/throttle/rateLimit,value=10000 \
  op=replace,path=/throttle/burstLimit,value=5000

# Stage Level
aws apigateway update-stage \
  --rest-api-id abc123 \
  --stage-name prod \
  --patch-operations \
    op=replace,path=/*/throttling/rateLimit,value=1000 \
    op=replace,path=/*/throttling/burstLimit,value=500

# Usage Plan สำหรับ API Key
aws apigateway create-usage-plan \
  --name "Basic Plan" \
  --throttle burstLimit=50,rateLimit=100 \
  --quota limit=10000,period=MONTH

Rate Limiting Headers

เมื่อ Implement Rate Limiting ควรส่ง HTTP Headers กลับไปให้ Client ทราบสถานะ มีทั้ง Standard (RFC 6585, RFC 7231) และ De-facto Standard ที่นิยมใช้กัน

Headerความหมายตัวอย่าง
X-RateLimit-Limitจำนวน Request สูงสุดต่อ Window100
X-RateLimit-Remainingจำนวน Request ที่เหลือ45
X-RateLimit-Resetเวลาที่ Counter จะ Reset (Unix timestamp)1717200000
Retry-Afterจำนวนวินาทีที่ต้องรอก่อนลองใหม่30
RateLimit-Policyนโยบาย Rate Limit (IETF draft)100;w=60
// Express Middleware สำหรับ Rate Limit Headers
function rateLimitHeaders(req, res, next) {
    const limit = 100;
    const remaining = Math.max(0, limit - req.rateUsage);
    const resetTime = Math.ceil(Date.now() / 60000) * 60;

    res.set({
        'X-RateLimit-Limit': limit,
        'X-RateLimit-Remaining': remaining,
        'X-RateLimit-Reset': resetTime,
        'RateLimit-Policy': `${limit};w=60`
    });

    if (remaining === 0) {
        res.set('Retry-After', 60);
        return res.status(429).json({
            error: 'Rate limit exceeded',
            retryAfter: 60,
            limit: limit,
            resetAt: new Date(resetTime * 1000).toISOString()
        });
    }

    next();
}

Rate Limiting Strategies

การเลือก Strategy ที่เหมาะสมเป็นสิ่งสำคัญมาก ขึ้นอยู่กับลักษณะของ API และผู้ใช้

1. Per IP Address

เหมาะกับ Public API ที่ไม่ต้อง Authentication แต่มีข้อจำกัดคือ ผู้ใช้หลายคนอาจใช้ IP เดียวกัน (เช่น อยู่หลัง NAT หรือ Corporate Proxy) และผู้โจมตีอาจใช้หลาย IP (Rotating Proxy)

2. Per User (Authenticated)

เหมาะกับ API ที่ต้อง Login ทำให้ Rate Limit แม่นยำกว่า Per IP เพราะผูกกับตัวตนผู้ใช้จริง ไม่ว่าจะเปลี่ยน IP กี่ครั้งก็ตาม

3. Per API Key

เหมาะกับ B2B API ที่ให้บริการแก่ Developer หรือองค์กร ทำให้สามารถกำหนด Limit ต่าง Plan ได้ เช่น Free 100/ชม. Pro 10,000/ชม. Enterprise 100,000/ชม.

4. Tiered Rate Limiting

กำหนด Rate Limit หลายระดับตาม Plan ของผู้ใช้ เป็นวิธีที่นิยมที่สุดในบริการ SaaS สมัยใหม่

# Tiered Rate Limiting Configuration
RATE_LIMITS = {
    "free": {
        "requests_per_minute": 20,
        "requests_per_hour": 500,
        "requests_per_day": 5000,
        "burst": 5
    },
    "pro": {
        "requests_per_minute": 200,
        "requests_per_hour": 10000,
        "requests_per_day": 100000,
        "burst": 50
    },
    "enterprise": {
        "requests_per_minute": 2000,
        "requests_per_hour": 100000,
        "requests_per_day": 1000000,
        "burst": 500
    }
}

API Throttling vs Rate Limiting

หลายคนสับสนระหว่าง Throttling กับ Rate Limiting แม้ทั้งสองจะเกี่ยวข้องกับการจำกัด Request แต่มีความแตกต่างสำคัญ

ด้านRate LimitingThrottling
การทำงานปฏิเสธ Request ที่เกิน Limit (429)ชะลอ Request โดย Delay หรือ Queue
ผลลัพธ์Request ถูก Reject ทันทีRequest ถูกประมวลผลแต่ช้าลง
ประสบการณ์ผู้ใช้ได้ Error ต้อง Retryช้าลงแต่ได้ผลลัพธ์
ใช้เมื่อต้องการปกป้องระบบอย่างเข้มงวดต้องการให้ผู้ใช้ได้ผลลัพธ์เสมอ

ในทางปฏิบัติ ระบบที่ดีมักใช้ทั้งสองร่วมกัน เริ่มจาก Throttling ก่อน (ชะลอ) และถ้ายังมากเกินไปจึงใช้ Rate Limiting (ปฏิเสธ) แนวคิดนี้เรียกว่า Graceful Degradation ช่วยให้ผู้ใช้ได้รับประสบการณ์ที่ดีขึ้น ไม่ถูก Reject ทันทีที่เกิน Limit เล็กน้อย

DDoS Protection แบบครบวงจร

Rate Limiting เป็นเพียงส่วนหนึ่งของการป้องกัน DDoS Attack ระบบที่ดีต้องมีหลายชั้นป้องกัน ทำงานร่วมกันแบบ Defense in Depth

Layer 1: CDN / Edge Protection

Cloudflare เป็นตัวเลือกยอดนิยมที่ให้บริการ DDoS Protection ในระดับ Network (L3/L4) และ Application (L7) มี Rate Limiting Rule ที่ตั้งค่าได้ง่ายผ่าน Dashboard หรือ API รองรับ Bot Detection และ Challenge (CAPTCHA) อัตโนมัติ

# Cloudflare Rate Limiting Rule (API)
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets" \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "name": "API Rate Limit",
    "kind": "zone",
    "phase": "http_ratelimit",
    "rules": [
      {
        "action": "block",
        "ratelimit": {
          "characteristics": ["ip.src"],
          "period": 60,
          "requests_per_period": 100,
          "mitigation_timeout": 600
        },
        "expression": "(http.request.uri.path contains "/api/")"
      }
    ]
  }'

Layer 2: AWS Shield & WAF

AWS Shield Standard ให้บริการฟรีสำหรับ AWS Resources ทุกตัว ป้องกัน DDoS ระดับ L3/L4 ส่วน AWS Shield Advanced ให้การป้องกันระดับสูงขึ้นพร้อม DDoS Response Team (DRT) และ Cost Protection

AWS WAF (Web Application Firewall) ทำงานร่วมกับ CloudFront, ALB, API Gateway ตั้งค่า Rule ได้ละเอียด เช่น Rate-based Rule, IP Set Rule, Geographic Match Rule

# AWS WAF Rate-based Rule
aws wafv2 create-rule-group \
  --name "api-rate-limit" \
  --scope REGIONAL \
  --capacity 100 \
  --rules '[
    {
      "Name": "RateLimit100",
      "Priority": 1,
      "Action": {"Block": {}},
      "Statement": {
        "RateBasedStatement": {
          "Limit": 100,
          "AggregateKeyType": "IP"
        }
      },
      "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "RateLimit100"
      }
    }
  ]'

Layer 3: Application Level

นี่คือ Rate Limiting ในแอปพลิเคชันที่เราพูดถึงข้างต้น ทำงานเป็นด่านสุดท้าย ใช้ Logic เฉพาะทาง เช่น Rate Limit ตาม User Plan หรือตาม API Endpoint

Bot Detection และ CAPTCHA Integration

นอกจาก Rate Limiting แล้ว การตรวจจับ Bot เป็นอีกวิธีสำคัญในการป้องกัน API จากการใช้งานที่ไม่เหมาะสม วิธีตรวจจับ Bot มีหลายแบบ

// reCAPTCHA v3 Integration (ไม่ต้องให้ User คลิก)
// Frontend
async function submitForm() {
  const token = await grecaptcha.execute('SITE_KEY', {action: 'login'});

  const response = await fetch('/api/auth/login', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Recaptcha-Token': token
    },
    body: JSON.stringify({ email, password })
  });
}

// Backend (Node.js)
async function verifyRecaptcha(token) {
  const response = await fetch(
    `https://www.google.com/recaptcha/api/siteverify?secret=${SECRET_KEY}&response=${token}`,
    { method: 'POST' }
  );
  const data = await response.json();
  return data.success && data.score >= 0.5;  // score 0.0 = bot, 1.0 = human
}

API Key Management

การจัดการ API Key อย่างเป็นระบบเป็นส่วนสำคัญของ API Security ช่วยให้สามารถติดตาม ควบคุม และเพิกถอนสิทธิ์การเข้าถึง API ได้

import hashlib, secrets, datetime

def generate_api_key():
    """สร้าง API Key ที่ปลอดภัย"""
    prefix = "sk_live_"  # prefix ช่วยให้ระบุประเภท Key ได้
    key = secrets.token_urlsafe(32)
    full_key = f"{prefix}{key}"
    key_hash = hashlib.sha256(full_key.encode()).hexdigest()
    return full_key, key_hash

def store_api_key(user_id, key_hash, scopes, expires_days=90):
    """เก็บ Hash ของ Key ในฐานข้อมูล"""
    return {
        "user_id": user_id,
        "key_hash": key_hash,
        "scopes": scopes,
        "created_at": datetime.datetime.utcnow(),
        "expires_at": datetime.datetime.utcnow() + datetime.timedelta(days=expires_days),
        "is_active": True,
        "last_used": None
    }

Monitoring และ Alerting on Rate Limits

การ Monitor Rate Limiting เป็นสิ่งสำคัญเพื่อให้รู้ว่า Limit ที่ตั้งไว้เหมาะสมหรือไม่ และตรวจจับการโจมตีได้ทัน Metric สำคัญที่ต้องเก็บมีดังนี้

# Prometheus Metrics สำหรับ Rate Limiting (Python)
from prometheus_client import Counter, Histogram, Gauge

# Counter สำหรับ Request ทั้งหมด
http_requests_total = Counter(
    'http_requests_total',
    'Total HTTP requests',
    ['method', 'endpoint', 'status']
)

# Counter สำหรับ Rate Limited Requests
rate_limited_total = Counter(
    'rate_limited_requests_total',
    'Total rate limited requests',
    ['endpoint', 'limit_type']
)

# Gauge สำหรับ Current Usage
rate_limit_usage = Gauge(
    'rate_limit_usage_ratio',
    'Current rate limit usage ratio',
    ['user_id', 'plan']
)

# ตั้ง Alert ใน Prometheus
# alert.rules.yml
# groups:
#   - name: rate_limiting
#     rules:
#       - alert: HighRateLimitRate
#         expr: rate(rate_limited_requests_total[5m]) > 100
#         for: 5m
#         labels:
#           severity: warning
#         annotations:
#           summary: "High rate limiting detected"

การออกแบบ Rate Limit Policy

การออกแบบ Policy ที่ดีต้องคำนึงถึงหลายปัจจัย ไม่ใช่แค่กำหนดตัวเลขสุ่มๆ แนวทางมีดังนี้

Client-side Handling of 429 Responses

ฝั่ง Client ก็ต้องจัดการ 429 Response อย่างเหมาะสม ไม่ใช่แค่ Retry ทันที เพราะจะยิ่งทำให้ปัญหาแย่ลง

// JavaScript: Exponential Backoff with Jitter
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);

      if (response.status === 429) {
        if (attempt === maxRetries) {
          throw new Error('Rate limit exceeded after max retries');
        }

        // อ่าน Retry-After header
        const retryAfter = response.headers.get('Retry-After');
        let waitTime;

        if (retryAfter) {
          waitTime = parseInt(retryAfter) * 1000;
        } else {
          // Exponential Backoff with Jitter
          const baseDelay = 1000;  // 1 second
          const maxDelay = 30000;  // 30 seconds
          waitTime = Math.min(
            maxDelay,
            baseDelay * Math.pow(2, attempt) + Math.random() * 1000
          );
        }

        console.log(`Rate limited. Waiting ${waitTime}ms before retry...`);
        await new Promise(resolve => setTimeout(resolve, waitTime));
        continue;
      }

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }

      return response;
    } catch (error) {
      if (attempt === maxRetries) throw error;
    }
  }
}

// ใช้งาน
const data = await fetchWithRetry('/api/data');
const json = await data.json();
# Python: httpx with Retry
import httpx
from tenacity import retry, wait_exponential, retry_if_result

def is_rate_limited(response):
    return response.status_code == 429

@retry(
    retry=retry_if_result(is_rate_limited),
    wait=wait_exponential(multiplier=1, min=1, max=60)
)
def fetch_data(url):
    response = httpx.get(url, headers={"X-API-Key": "your_key"})
    return response

# ใช้งาน
result = fetch_data("https://api.example.com/data")
Jitter คืออะไร? Jitter คือการเพิ่มเวลาสุ่มเล็กน้อยใน Retry Delay เพื่อป้องกัน "Thundering Herd Problem" หากไม่มี Jitter Client หลายตัวที่ถูก Rate Limit พร้อมกันจะ Retry พร้อมกัน ทำให้เกิด Burst อีกครั้ง

Best Practices สรุปรวม

  1. ใช้หลายชั้นป้องกัน — CDN (Cloudflare) + Nginx Rate Limit + Application Rate Limit + Database Connection Limit
  2. เลือก Algorithm ให้เหมาะ — Token Bucket สำหรับ API ทั่วไป Leaky Bucket สำหรับระบบที่ต้องการ Consistent Rate
  3. ใช้ Redis สำหรับ Distributed System — อย่าเก็บ Counter ไว้ใน Memory ของ App Server
  4. ส่ง Rate Limit Headers ทุกครั้ง — ช่วยให้ Client จัดการ 429 ได้อย่างเหมาะสม
  5. Monitor อย่างต่อเนื่อง — ตั้ง Alert สำหรับ 429 Rate ที่สูงผิดปกติ
  6. Document ให้ชัดเจน — เขียนในเอกสาร API ว่าแต่ละ Plan มี Limit เท่าไหร่
  7. Test Rate Limiting — ใช้ Load Testing Tool เช่น k6 หรือ wrk ทดสอบว่า Rate Limit ทำงานถูกต้อง
  8. อย่า Rate Limit Health Check — Endpoint สำหรับ Monitoring ไม่ควรถูก Rate Limit
  9. Log Rate Limit Events — เก็บ Log ว่าใครถูก Rate Limit เมื่อไหร่ เพื่อวิเคราะห์ย้อนหลัง
  10. ปรับ Limit ตาม Feedback — ถ้าผู้ใช้จริงถูก Rate Limit บ่อย อาจต้องเพิ่ม Limit ให้สูงขึ้น

สรุป

Rate Limiting เป็นด่านแรกและสำคัญที่สุดในการป้องกัน API ทุก API ที่เปิดให้ภายนอกเข้าถึงต้องมี Rate Limiting ไม่ว่าจะเป็น Internal API ก็ควรมีเพื่อป้องกัน Cascading Failure การเลือก Algorithm ที่เหมาะสม ไม่ว่าจะเป็น Token Bucket สำหรับความยืดหยุ่น หรือ Sliding Window Counter สำหรับความแม่นยำ ขึ้นอยู่กับลักษณะของระบบ สิ่งสำคัญคือต้องมีหลายชั้นป้องกัน ตั้งแต่ CDN Level, Nginx Level ไปจนถึง Application Level และต้อง Monitor อย่างต่อเนื่องเพื่อปรับ Policy ให้เหมาะสมกับ Traffic จริง

เริ่มต้นวันนี้ด้วยการเพิ่ม Rate Limiting ให้ API ของคุณ แม้จะเป็นโปรเจกต์เล็กๆ ก็ตาม เพราะเมื่อ API โดน Abuse แล้ว การเพิ่ม Rate Limiting ย้อนหลังมักจะยากและมีผลกระทบต่อผู้ใช้จริง การวางระบบตั้งแต่แรกจะง่ายและปลอดภัยกว่ามาก


Back to Blog | iCafe Forex | SiamLanCard | Siam2R