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

ทำไมต้องสนใจ Rate Limiting

ในการพัฒนาระบบ AI ของผมเอง มีเหตุการณ์ที่ทำให้ต้องทบทวนเรื่องนี้อย่างจริงจัง หลังจากลูกค้ารายใหญ่ทำการทดสอบ stress test ระบบ chatbot ของเราเริ่มส่ง request เกิน limit ของ API provider ส่งผลให้ IP ถูกบล็อกชั่วคราว 3 ชั่วโมง และบิลค่าบริการพุ่งสูงเกินความคาดหมาย 40%

สถานการณ์จริงที่ต้องใช้ Rate Limiting

1. ระบบ AI Customer Service ของ E-commerce

เมื่อมี Flash Sale หรือโปรโมชันพิเศษ ผู้ใช้งานจำนวนมากจะเข้ามาพร้อมกัน chatbot อาจได้รับ request พุ่งสูงถึง 10,000 คำขอต่อนาที หากไม่มี rate limit ที่เหมาะสม ทั้งระบบและ API provider จะล่ม

2. การเปิดตัว Enterprise RAG System

องค์กรขนาดใหญ่ที่ใช้ RAG (Retrieval-Augmented Generation) สำหรับค้นหาเอกสารภายใน มักต้องรองรับพนักงานหลายร้อยคน การใช้ token bucket จะช่วยให้การใช้งานเป็นไปอย่างราบรื่น แม้ในช่วงเวลาเร่งด่วน

3. โปรเจกต์ Independent Developer

สำหรับนักพัฒนาอิสระที่ใช้บริการ API อย่าง HolySheep AI ซึ่งมีราคาประหยัดมาก (ประหยัดสูงสุด 85%+ เมื่อเทียบกับ provider อื่น) การใช้ rate limiting ที่เหมาะสมจะช่วยควบคุมค่าใช้จ่ายและเพิ่มประสิทธิภาพการใช้เครดิตฟรีที่ได้รับเมื่อลงทะเบียน

อัลกอริทึม Sliding Window Algorithm

Sliding Window เป็นอัลกอริทึมที่ทำงานโดยการนับจำนวน request ในช่วงเวลาที่กำหนด โดยจะ "เลื่อน" หน้าต่างเวลาไปเรื่อยๆ ทำให้การควบคุมการใช้งานมีความยืดหยุ่นกว่าวิธี Fixed Window

/**
 * Sliding Window Rate Limiter Implementation
 * อัลกอริทึมนี้ใช้หน้าต่างเวลาที่เลื่อนได้ แม่นยำกว่า Fixed Window
 */
class SlidingWindowRateLimiter {
    private maxRequests: number;
    private windowSizeMs: number;
    private requests: number[] = [];

    constructor(maxRequests: number, windowSizeMs: number) {
        this.maxRequests = maxRequests;
        this.windowSizeMs = windowSizeMs;
    }

    async tryAcquire(): Promise<boolean> {
        const now = Date.now();
        const windowStart = now - this.windowSizeMs;
        
        // ลบ request ที่หมดอายุออกจากหน้าต่าง
        this.requests = this.requests.filter(timestamp => timestamp > windowStart);
        
        if (this.requests.length < this.maxRequests) {
            this.requests.push(now);
            return true;
        }
        
        return false;
    }

    getRetryAfter(): number {
        if (this.requests.length === 0) return 0;
        
        const oldestRequest = this.requests[0];
        const windowStart = Date.now() - this.windowSizeMs;
        
        return Math.max(0, oldestRequest - windowStart);
    }

    getRemainingRequests(): number {
        const now = Date.now();
        const windowStart = now - this.windowSizeMs;
        
        const activeRequests = this.requests.filter(timestamp => timestamp > windowStart);
        return Math.max(0, this.maxRequests - activeRequests.length);
    }
}

// ตัวอย่างการใช้งานกับ HolySheep AI API
async function callAIWithSlidingWindow(userQuery: string): Promise<string> {
    const limiter = new SlidingWindowRateLimiter(60, 60000); // 60 คำขอต่อนาที
    
    const canProceed = await limiter.tryAcquire();
    
    if (!canProceed) {
        const retryAfter = limiter.getRetryAfter() / 1000;
        throw new Error(Rate limit exceeded. Retry after ${retryAfter.toFixed(1)} seconds);
    }
    
    const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
        method: 'POST',
        headers: {
            'Authorization': Bearer ${process.env.YOUR_HOLYSHEEP_API_KEY},
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            model: 'gpt-4.1',
            messages: [{ role: 'user', content: userQuery }],
            max_tokens: 1000
        })
    });
    
    if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After');
        throw new Error(API rate limit. Wait ${retryAfter} seconds);
    }
    
    const data = await response.json();
    return data.choices[0].message.content;
}

อัลกอริทึม Token Bucket Algorithm

Token Bucket เป็นอัลกอริทึมที่ทำงานโดยการสร้าง "โทเค็น" ขึ้นมาตามจังหวะเวลาที่กำหนด เมื่อมี request เข้ามา ระบบจะตรวจสอบว่ามีโทเค็นเพียงพอหรือไม่ วิธีนี้เหมาะกับกรณีที่ต้องการยอมให้ request พุ่งสูงชั่วคราวได้ (burst)

/**
 * Token Bucket Rate Limiter Implementation
 * อัลกอริทึมนี้รองรับการ burst ได้ดีกว่า Sliding Window
 */
class TokenBucketRateLimiter {
    private tokens: number;
    private maxTokens: number;
    private refillRate: number; // tokens per second
    private lastRefillTime: number;
    
    constructor(maxTokens: number, refillRate: number) {
        this.maxTokens = maxTokens;
        this.tokens = maxTokens;
        this.refillRate = refillRate;
        this.lastRefillTime = Date.now();
    }

    private refill(): void {
        const now = Date.now();
        const elapsed = (now - this.lastRefillTime) / 1000;
        const newTokens = elapsed * this.refillRate;
        
        this.tokens = Math.min(this.maxTokens, this.tokens + newTokens);
        this.lastRefillTime = now;
    }

    async tryAcquire(tokensNeeded: number = 1): Promise<boolean> {
        this.refill();
        
        if (this.tokens >= tokensNeeded) {
            this.tokens -= tokensNeeded;
            return true;
        }
        
        return false;
    }

    getWaitTime(tokensNeeded: number = 1): number {
        this.refill();
        
        if (this.tokens >= tokensNeeded) return 0;
        
        const tokensShortage = tokensNeeded - this.tokens;
        return (tokensShortage / this.refillRate) * 1000; // milliseconds
    }

    getAvailableTokens(): number {
        this.refill();
        return this.tokens;
    }
}

// ตัวอย่างการใช้งานสำหรับ Enterprise RAG System
class EnterpriseRAGRateLimiter {
    private bucket: TokenBucketRateLimiter;
    private baseUrl = 'https://api.holysheep.ai/v1';
    private apiKey: string;
    
    constructor() {
        // อนุญาต burst ได้สูงสุด 100 tokens และเติม 10 tokens/วินาที
        this.bucket = new TokenBucketRateLimiter(100, 10);
        this.apiKey = process.env.YOUR_HOLYSHEEP_API_KEY || '';
    }

    async queryDocument(userId: string, query: string): Promise<string> {
        // คำนวณ approximate token usage
        const estimatedTokens = Math.ceil(query.length / 4) + 500; // +500 สำหรับ context
        
        const canProceed = await this.bucket.tryAcquire(estimatedTokens);
        
        if (!canProceed) {
            const waitMs = this.bucket.getWaitTime(estimatedTokens);
            throw new Error(Throttled. Wait ${(waitMs / 1000).toFixed(1)}s before retry. Available: ${this.bucket.getAvailableTokens().toFixed(0)} tokens);
        }
        
        const response = await fetch(${this.baseUrl}/chat/completions, {
            method: 'POST',
            headers: {
                'Authorization': Bearer ${this.apiKey},
                'Content-Type': 'application/json',
                'X-User-ID': userId
            },
            body: JSON.stringify({
                model: 'claude-sonnet-4.5',
                messages: [
                    { 
                        role: 'system', 
                        content: 'คุณเป็นผู้ช่วยค้นหาข้อมูลในเอกสารองค์กร' 
                    },
                    { role: 'user', content: query }
                ],
                temperature: 0.3,
                max_tokens: 2000
            })
        });
        
        if (!response.ok) {
            const errorData = await response.json().catch(() => ({}));
            throw new Error(API Error ${response.status}: ${errorData.error?.message || 'Unknown error'});
        }
        
        const data = await response.json();
        return data.choices[0].message.content;
    }
}

เปรียบเทียบความแตกต่างของทั้งสองอัลกอริทึม

คุณสมบัติ Sliding Window Token Bucket
ความแม่นยำ สูง - นับ request จริงในช่วงเวลา ปานกลาง - ขึ้นอยู่กับการประมาณ token
รองรับ Burst จำกัด - ตายตัวตาม window สูง - สามารถใช้ token สะสมได้
ความซับซ้อน ปานกลาง - ต้องจัดการ array ต่ำ - ใช้แค่ตัวแปรนับ
Memory Usage สูงกว่า - เก็บ timestamp ทุก request ต่ำกว่า - เก็บแค่จำนวน token
เหมาะกับ E-commerce, API ที่ต้องการความเสถียร RAG, AI processing, งานที่ต้องการ burst
Latency เพิ่มขึ้นตามจำนวน request ใน window คงที่ - O(1) operation

ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข

ข้อผิดพลาดที่ 1: Memory Leak ใน Sliding Window

ปัญหา: Array ของ timestamp เพิ่มขึ้นเรื่อยๆ โดยไม่มีการ cleanup ทำให้ memory usage พุ่งสูง

/**
 * วิธีแก้ไข: ใช้ Circular Buffer แทน Array ปกติ
 * และเพิ่มการ cleanup แบบ periodic
 */
class OptimizedSlidingWindowRateLimiter {
    private maxRequests: number;
    private windowSizeMs: number;
    private timestamps: number[];
    private head: number = 0;
    private size: number = 0;
    private lastCleanup: number;
    private cleanupIntervalMs: number = 300000; // cleanup ทุก 5 นาที
    
    constructor(maxRequests: number, windowSizeMs: number) {
        this.maxRequests = maxRequests;
        this.windowSizeMs = windowSizeMs;
        // Pre-allocate array เพื่อลด GC pressure
        this.timestamps = new Array(maxRequests * 2);
        this.lastCleanup = Date.now();
    }

    async tryAcquire(): Promise<boolean> {
        const now = Date.now();
        
        // Periodic cleanup
        if (now - this.lastCleanup > this.cleanupIntervalMs) {
            this.performCleanup(now);
        }
        
        const windowStart = now - this.windowSizeMs;
        
        // Remove expired entries
        while (this.size > 0) {
            const oldestIdx = (this.head - this.size + this.timestamps.length) % this.timestamps.length;
            if (this.timestamps[oldestIdx] > windowStart) break;
            this.size--;
        }
        
        if (this.size < this.maxRequests) {
            this.timestamps[this.head] = now;
            this.head = (this.head + 1) % this.timestamps.length;
            this.size++;
            return true;
        }
        
        return false;
    }

    private performCleanup(currentTime: number): void {
        const windowStart = currentTime - this.windowSizeMs;
        // ลบ entries ที่หมดอายุ
        this.size = 0;
        this.lastCleanup = currentTime;
    }
}

ข้อผิดพลาดที่ 2: Race Condition เมื่อใช้งานหลาย Instances

ปัญหา: เมื่อระบบมีหลาย server/instances ตัว limiter แต่ละตัวจะมี state แยกกัน ทำให้ total requests สูงเกิน limit

/**
 * วิธีแก้ไข: ใช้ Redis สำหรับ centralized rate limiting
 * รองรับ distributed systems
 */
import Redis from 'ioredis';

class RedisTokenBucketRateLimiter {
    private redis: Redis;
    private key: string;
    private maxTokens: number;
    private refillRate: number;
    private luaScript: string;
    
    constructor(redisUrl: string, key: string, maxTokens: number, refillRate: number) {
        this.redis = new Redis(redisUrl);
        this.key = ratelimit:${key};
        this.maxTokens = maxTokens;
        this.refillRate = refillRate;
        
        // Lua script สำหรับ atomic operation
        this.luaScript = `
            local key = KEYS[1]
            local maxTokens = tonumber(ARGV[1])
            local refillRate = tonumber(ARGV[2])
            local tokensRequested = tonumber(ARGV[3])
            local now = tonumber(ARGV[4])
            
            local data = redis.call('HMGET', key, 'tokens', 'lastRefill')
            local tokens = tonumber(data[1]) or maxTokens
            local lastRefill = tonumber(data[2]) or now
            
            -- Calculate token refill
            local elapsed = now - lastRefill
            local newTokens = math.min(maxTokens, tokens + (elapsed * refillRate / 1000))
            
            if newTokens >= tokensRequested then
                newTokens = newTokens - tokensRequested
                redis.call('HMSET', key, 'tokens', newTokens, 'lastRefill', now)
                redis.call('EXPIRE', key, 3600)
                return {1, newTokens}
            else
                redis.call('HMSET', key, 'tokens', newTokens, 'lastRefill', now)
                redis.call('EXPIRE', key, 3600)
                return {0, newTokens}
            end
        `;
    }

    async tryAcquire(tokensRequested: number = 1): Promise<{allowed: boolean; remaining: number}> {
        const result = await this.redis.eval(
            this.luaScript,
            1,
            this.key,
            this.maxTokens,
            this.refillRate,
            tokensRequested,
            Date.now()
        ) as [number, number];
        
        return {
            allowed: result[0] === 1,
            remaining: result[1]
        };
    }

    async close(): Promise<void> {
        await this.redis.quit();
    }
}

// การใช้งาน
async function exampleDistributedLimiter() {
    const limiter = new RedisTokenBucketRateLimiter(
        'redis://localhost:6379',
        'ai-api-production',
        1000,    // max tokens
        50       // refill 50 tokens/second
    );
    
    const result = await limiter.tryAcquire(100);
    
    if (!result.allowed) {
        throw new Error(Rate limit exceeded. ${result.remaining.toFixed(0)} tokens remaining);
    }
    
    // ดำเนินการ API call ต่อไป
    const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
        method: 'POST',
        headers: {
            'Authorization': Bearer ${process.env.YOUR_HOLYSHEEP_API_KEY},
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            model: 'deepseek-v3.2',
            messages: [{ role: 'user', content: 'Hello' }],
            max_tokens: 100
        })
    });
    
    await limiter.close();
}

ข้อผิดพลาดที่ 3: ไม่จัดการ Exponential Backoff

ปัญหา: เมื่อถูก rate limit แล้ว retry ทันที ทำให้ถูก limit ซ้ำและอาจถูกบล็อกถาวร

/**
 * วิธีแก้ไข: ใช้ Exponential Backoff พร้อม Jitter
 */
class ResilientAPIClient {
    private baseUrl = 'https://api.holysheep.ai/v1';
    private apiKey: string;
    private maxRetries: number = 5;
    private baseDelayMs: number = 1000;
    private maxDelayMs: number = 30000;
    
    constructor() {
        this.apiKey = process.env.YOUR_HOLYSHEEP_API_KEY || '';
    }

    private calculateBackoff(attempt: number): number {
        // Exponential backoff: 1s, 2s, 4s, 8s, 16s, 32s (capped)
        const exponentialDelay = this.baseDelayMs * Math.pow(2, attempt);
        const cappedDelay = Math.min(exponentialDelay, this.maxDelayMs);
        
        // Add jitter (±25%) เพื่อป้องกัน thundering herd
        const jitter = cappedDelay * 0.25 * (Math.random() * 2 - 1);
        
        return Math.floor(cappedDelay + jitter);
    }

    async fetchWithRetry(
        endpoint: string, 
        options: RequestInit,
        attempt: number = 0
    ): Promise<Response> {
        try {
            const response = await fetch(${this.baseUrl}${endpoint}, {
                ...options,
                headers: {
                    ...options.headers,
                    'Authorization': Bearer ${this.apiKey}
                }
            });
            
            // Success
            if (response.ok) {
                return response;
            }
            
            // Rate limit (429) - ต้องรอตาม Retry-After header
            if (response.status === 429) {
                const retryAfter = response.headers.get('Retry-After');
                const waitMs = retryAfter 
                    ? parseInt(retryAfter) * 1000 
                    : this.calculateBackoff(attempt);
                
                console.log(Rate limited. Waiting ${waitMs}ms before retry (attempt ${attempt + 1}/${this.maxRetries}));
                
                if (attempt < this.maxRetries) {
                    await this.sleep(waitMs);
                    return this.fetchWithRetry(endpoint, options, attempt + 1);
                }
            }
            
            // Server error (5xx) - retry with backoff
            if (response.status >= 500 && response.status < 600) {
                if (attempt < this.maxRetries) {
                    const delayMs = this.calculateBackoff(attempt);
                    console.log(Server error ${response.status}. Retrying in ${delayMs}ms...);
                    await this.sleep(delayMs);
                    return this.fetchWithRetry(endpoint, options, attempt + 1);
                }
            }
            
            // Other errors - return response for handling
            return response;
            
        } catch (error) {
            // Network error - retry
            if (attempt < this.maxRetries && this.isRetryableError(error)) {
                const delayMs = this.calculateBackoff(attempt);
                console.log(Network error. Retrying in ${delayMs}ms...);
                await this.sleep(delayMs);
                return this.fetchWithRetry(endpoint, options, attempt + 1);
            }
            throw error;
        }
    }

    private sleep(ms: number): Promise<void> {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    private isRetryableError(error: unknown): boolean {
        if (error instanceof TypeError) return true; // Network errors
        if (error instanceof Error && error.message.includes('ECONNREFUSED')) return true;
        return false;
    }

    async chat(model: string, messages: any[]): Promise<any> {
        const response = await this.fetchWithRetry('/chat/completions', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ model, messages, max_tokens: 1000 })
        });
        
        if (!response.ok) {
            const error = await response.json().catch(() => ({}));
            throw new Error(API Error: ${response.status} - ${error.error?.message || 'Unknown'});
        }
        
        return response.json();
    }
}

คำแนะนำในการเลือกอัลกอริทึม

สรุป

การเลือก rate limiter ที่เหมาะสมขึ้นอยู่กับลักษณะการใช้งานและข้อกำหนดของระบบ สำหรับนักพัฒนาที่ใช้ HolySheep AI ซึ่งมี latency ต่ำกว่า 50ms และราคาประหยัดมาก การตั้ง rate limiter ที่เหมาะสมจะช่วยให้ใช้งาน API ได้อย่างมีประสิทธิภาพสูงสุด ทั้งยังช่