PlusMagi's Blog By Pitt Phunsanit Backend,Database PHP: การทำ API Rate Limiting เพื่อป้องกันการเรียกใช้งานเกินโควตา

PHP: การทำ API Rate Limiting เพื่อป้องกันการเรียกใช้งานเกินโควตา

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

Rate Limiting คือกลไกการควบคุมอัตราการเรียกใช้งาน (Request Rate) ที่ทำหน้าที่เหมือน ‘ยามหน้าประตู’ ของ API โดยมันจะจำกัดจำนวนคำขอที่ผู้ใช้แต่ละราย (ระบุผ่าน API Key หรือ IP Address) สามารถส่งเข้ามาได้ภายในช่วงเวลาที่กำหนด

⚙️ หลักการทำงานของ Rate Limiting ด้วย PostgreSQL

ในการนำ Rate Limiting มาใช้จริง เราต้องมีระบบจัดเก็บข้อมูลที่สามารถนับจำนวนคำขอได้อย่างแม่นยำและเป็น Atomic (ดำเนินการเสร็จสมบูรณ์ในครั้งเดียว) ซึ่ง PostgreSQL เป็นเครื่องมือที่ยอดเยี่ยมสำหรับงานนี้ เนื่องจากเราสามารถใช้ Transaction และฟังก์ชันที่รับประกันความถูกต้องของข้อมูลได้

💡 โครงสร้างตาราง (Schema Design)

เราจะสร้างตารางเพื่อเก็บข้อมูลการนับโควตา โดยใช้คีย์หลักที่รวม User ID และช่วงเวลา (Time Window) เพื่อให้การนับเป็นไปอย่างเฉพาะเจาะจง

SQL

CREATE TABLE api_limits (
    user_id VARCHAR(255) NOT NULL,
    time_window TIMESTAMP WITHOUT TIME ZONE NOT NULL,
    request_count INTEGER DEFAULT 1,
    PRIMARY KEY (user_id, time_window);

💻 การตรวจสอบและเพิ่มจำนวน (Atomic Check & Increment)

หัวใจสำคัญคือการทำให้การตรวจสอบโควตาและการเพิ่มจำนวนเป็นไปอย่างอะตอมมิก เพื่อป้องกัน Race Condition ในกรณีที่ผู้ใช้ส่งคำขอเข้ามาพร้อมกันหลายคำขอ (Concurrent Requests) เราจะใช้คำสั่ง INSERT ... ON CONFLICT ของ PostgreSQL

SQL

-- สมมติว่าโควตาคือ 100 ครั้งต่อ 1 ชั่วโมง (1 ชั่วโมงจากตอนนี้)
SELECT * FROM api_limits WHERE user_id = :user_id AND time_window = :time_window;</code>
<pre><code class="language-sql">-- คำสั่งที่ใช้ในการเพิ่มจำนวนอย่างปลอดภัย
INSERT INTO api_limits (user_id, time_window, request_count)
VALUES (:user_id, NOW() AT TIME ZONE 'UTC', 1)
ON CONFLICT (user_id, time_window)
DO UPDATE SET request_count = api_limits.request_count + 1
RETURNING request_count;

🚀 การนำไปใช้ใน PHP (PHP Implementation Logic)

ในโค้ด PHP เราจะเรียกใช้คำสั่ง SQL ข้างต้น และตรวจสอบค่าที่ส่งกลับมา (request_count) หากค่าที่ได้เกินขีดจำกัด (เช่น 100) เราจะส่ง HTTP Status Code 429 (Too Many Requests) กลับไปให้ผู้ใช้ทันที

PHP

// 1. กำหนดโควตาและพารามิเตอร์
$limit = 100; // โควตาสูงสุด
$userId = $_GET['api_key']; // ดึง User ID จาก Request
$timeWindow = date('Y-m-d H:i:s'); // กำหนดช่วงเวลา (เช่น 1 ชั่วโมง)

try {
    // 2. Execute SQL (using PDO or similar library)
    $stmt = $pdo->prepare("INSERT INTO api_limits (user_id, time_window, request_count) VALUES (?, ?, 1) ON CONFLICT (user_id, time_window) DO UPDATE SET request_count = api_limits.request_count + 1 RETURNING request_count;");
    $stmt->execute([$userId, $timeWindow]);
    $currentCount = $stmt->fetchColumn();

    if ($currentCount > $limit) {
        header('HTTP/1.1 429 Too Many Requests');
        echo 'Error: คุณใช้โควตาเกินกำหนดแล้ว';
        exit();
    } else {
        // 3. Process Request (API Logic)
        echo 'Access Granted. Remaining quota: ' . ($limit - $currentCount);
    }
} catch (PDOException $e) {
    // Handle DB error
    echo 'Database Error: ' . $e->getMessage();
}©

✅ บทสรุปและข้อควรระวัง

การใช้ PostgreSQL ในการทำ Rate Limiting ด้วยเทคนิค ON CONFLICT ทำให้เรามั่นใจได้ว่าการนับจำนวนคำขอจะเป็นไปอย่างถูกต้องและปลอดภัยจากปัญหา Race Condition ซึ่งเป็นหัวใจสำคัญของการออกแบบระบบ API ที่มีความน่าเชื่อถือสูง

ในฐานะ Senior Architect ควรพิจารณาการทำ Rate Limiting ในหลายระดับ เช่น การจำกัดที่ระดับ Edge (เช่น Nginx/Cloudflare) และการจำกัดที่ระดับแอปพลิเคชัน (PHP/PostgreSQL) เพื่อให้เกิดการป้องกันที่ครอบคลุมที่สุด