World-Class Master Architecture & Traffic Management
11.4 Enterprise API Master Architecture (Thai)
สุดยอดเทคนิคระดับ Master ในการออกแบบสถาปัตยกรรม API สมัยใหม่ ทั้งเรื่อง Caching, การขยายระบบ (Scale) และการควบคุม Traffic
1. Advanced Caching (Cache-Aside, Write-Through/Behind, SWR)
ทฤษฎีและกลไกการทำงาน (Theory & Mechanism)
Cache-Aside: แอปวิ่งหา Cache ก่อน ถ้าไม่เจอ (Miss) ค่อยไปหา Database แล้วเอามาอัปเดต Cache (นิยมที่สุด)
Write-Through: เมื่อมีการเขียนข้อมูล จะเขียนลง Cache และ DB พร้อมกันเสมอ มั่นใจได้ว่าข้อมูลใหม่สุด แต่การเขียนจะช้าลง
Stale-While-Revalidate (SWR): คืนข้อมูลเก่าจาก Cache ให้ Client ทันที (Stale) จากนั้นระบบจะไปดึงข้อมูลใหม่จาก DB แบบ Background (Revalidate) ทำให้ฝั่ง Client ได้ข้อมูลเร็วเสมอ
ตัวอย่างโค้ดหรือการตั้งค่า (Code/Config Example)
# การตั้งค่า SWR ผ่าน HTTP Header (ให้ CDN จัดการ)
# บอกให้ CDN เก็บ Cache 60 วินาที
# แต่ถ้าเลย 60 วินาทีไปแล้ว อนุญาตให้เสิร์ฟของเก่าได้สูงสุด 3600 วินาที ระหว่างที่ CDN กำลังดึงของใหม่
Cache-Control: max-age=60, stale-while-revalidate=3600
Use Case ในชีวิตจริง (Real-world Scenario)
หน้าแสดงโปรไฟล์ผู้ขายใน Shopee หรือหน้าแคตตาล็อกสินค้า การเปลี่ยนข้อมูลไม่ได้เกิดขึ้นระดับวินาที การใช้ SWR ทำให้ผู้เข้าชมได้รับหน้าเว็บจาก CDN ทันทีในระดับมิลลิวินาที แม้ข้อมูลจะเก่าไป 1 นาทีก็รับได้ และอัปเดตใหม่เองใน Background
ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations)
Pitfall: ปัญหา Cache Invalidation หรือ Thundering Herd Problem (เมื่อ Cache หมดอายุ แล้ว Traffic มหาศาลวิ่งเข้า DB พร้อมกัน)
Mitigation: ใช้ Distributed Lock เพื่อให้ Request เดียวเท่านั้นที่ไปดึง DB มาอัปเดต Cache หรือใช้ SWR เป็นตัวช่วยลดแรงกระแทก
2. Rate Limiting Algorithms (Token Bucket & Sliding Window)
ทฤษฎีและกลไกการทำงาน (Theory & Mechanism)
เพื่อป้องกันระบบล่มจากการยิง API ถล่มทลาย (DDoS หรือบั๊ก)
Token Bucket: มีถังใส่ Token ตามจำนวนโควต้า ทุก Request ต้องหยิบ 1 Token ถังจะถูกเติมด้วยเรทที่คงที่ รองรับ Burst Traffic ได้
Sliding Window Log: บันทึก Timestamp ของทุก Request ช่วยแก้ปัญหาขอบหน้าต่างเวลา (Boundary issues) ที่มักเกิดใน Fixed Window แต่กิน Memory เยอะ
ตัวอย่างโค้ดหรือการตั้งค่า (Code/Config Example)
# ตัวอย่าง Token Bucket ด้วย Redis (Pseudo-code)
def check_rate_limit(user_id):
# เติม 10 Tokens ต่อวินาที, ถังจุได้ 100 Tokens
current_tokens = redis.get(f"tokens:{user_id}")
if current_tokens > 0:
redis.decr(f"tokens:{user_id}")
return True # อนุญาตให้ผ่าน
else:
return False # Rate Limit (429 Too Many Requests)
Use Case ในชีวิตจริง (Real-world Scenario)
Public API เช่น GitHub หรือ OpenAI บังคับใช้งานแบบ 100 Req/min ด้วย Token Bucket ทำให้เวลาผู้ใช้อัพโหลดโค้ดรัวๆ ในช่วงสั้นๆ ทำได้ตราบเท่าที่ Token ในถังยังมีเหลือ แต่ถ้าส่งตลอดเวลาแบบบอทจะถูกบล็อค
ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations)
Pitfall: การเช็ค Rate Limit ต้องเร็วมาก หาก Database ช้า จะกลายเป็นคอขวดเสียเอง
Mitigation: ควรทำ Rate Limit ที่ระดับ API Gateway (เช่น Kong, Nginx) หรือใช้ In-memory Datastore ที่ประมวลผลเร็วระดับ Microseconds อย่าง Redis
3. CQRS Architecture (Command Query Responsibility Segregation)
ทฤษฎีและกลไกการทำงาน (Theory & Mechanism)
สถาปัตยกรรมที่แยกโมเดลและ Database สำหรับฝั่งเขียน (Command) และฝั่งอ่าน (Query) ออกจากกันชัดเจน ฝั่งเขียนเน้น Normalize ข้อมูลเพื่อรักษาความถูกต้อง (Write DB) ฝั่งอ่านเน้น Denormalize ข้อมูล เช่น NoSQL (Read Replica/DB) เพื่อความเร็วระดับสูงสุด ข้อมูลซิงค์กันผ่าน Event-Driven (เช่น Kafka/RabbitMQ) แบบ Eventual Consistency
ตัวอย่างโค้ดหรือการตั้งค่า (Code/Config Example)
# ฝั่ง Command (Write)
@app.post("/orders")
async def create_order(order: OrderCmd):
# 1. เขียนลง Postgres
save_to_postgres(order)
# 2. ยิง Event ลง Kafka
kafka_producer.send("order_created", order)
return {"msg": "Accepted"}
# ฝั่ง Query (Read)
@app.get("/orders/{user_id}/summary")
async def get_order_summary(user_id: int):
# 1. อ่านจาก Elasticsearch หรือ Redis (ที่ถูกอัปเดตจาก Kafka Worker)
return read_from_elasticsearch(user_id)
Use Case ในชีวิตจริง (Real-world Scenario)
ระบบแบงก์กิ้งที่ข้อมูล Transaction การโอนเงิน (Command) ต้องถูกบันทึกลง SQL อย่างแม่นยำ แต่หน้าแสดง Statement ประวัติย้อนหลัง 5 ปี (Query) ถูกคนกดดูบ่อยมากและต้องการสรุปรวม การใช้ CQRS จะช่วยลดภาระ SQL และทำให้การอ่านข้อมูลมหาศาลไวขึ้น
ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations)
Pitfall: การทำ CQRS แลกมาด้วยความซับซ้อนของระบบ (Complexity) และปัญหาข้อมูลไม่ตรงกันชั่วขณะ (Eventual Consistency) ทำให้ผู้ใช้พึ่งโอนเงินเสร็จ แต่ยอดเงินหน้าแรกรอ 2 วินาทีกว่าจะเป็นข้อมูลใหม่
Mitigation: แก้ไข UX ให้เนียนขึ้น เช่น โชว์สถานะ "กำลังประมวลผล" และไม่ควรใช้ CQRS กับทุกฟีเจอร์ ให้ใช้เฉพาะจุดที่เป็นคอขวดจริงๆ เท่านั้น
4. Graceful Deprecation (Sunset Header & Dark Launching)
ทฤษฎีและกลไกการทำงาน (Theory & Mechanism)
เมื่อจะยกเลิก API เก่า ต้องทำอย่างละมุนละม่อม (Graceful) การใช้ `Deprecation` Header เพื่อแจ้งเตือน Client ก่อนว่า API นี้กำลังจะถูกยกเลิก พร้อมแนบ `Sunset` Header กำหนดวันตายที่แน่นอน
ส่วนตอนปล่อย API ใหม่ ควรทำ Dark Launching / Canary Release คือส่ง Traffic จริงบางส่วนไปยัง API ตัวใหม่แบบเงียบๆ เพื่อเช็ค Error และ Performance ก่อนเปิดตัวเต็มรูปแบบ
ตัวอย่างโค้ดหรือการตั้งค่า (Code/Config Example)
# Response จาก API รุ่นเก่า (v1) ที่กำลังจะยกเลิก
HTTP/1.1 200 OK
Deprecation: true
# ยกเลิกใช้งานจริงวันที่ 1 ธันวาคม 2026
Sunset: Tue, 01 Dec 2026 23:59:59 GMT
Link: ; rel="deprecation"; type="text/html"
{"status": "success", "data": "..."}
Use Case ในชีวิตจริง (Real-world Scenario)
Stripe หรือ GitHub API มีการออก Version ใหม่ตลอดเวลา การปิด API ทันทีจะทำให้บริษัทคู่ค้าพังหมด การใช้ Sunset Header ประกอบกับการทำ Email Campaign ตามไปแจ้งเตือน Developer เป็นแนวทางปฏิบัติระดับ Enterprise
ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations)
Pitfall: Client หลายเจ้ายิง API แบบอัตโนมัติ และไม่เคยมีคนมานั่งเช็ค Response Headers เลย ทำให้วันจริงพังอยู่ดี
Mitigation: ทำ "Brownout" คือการจงใจปิด API ช่วงสั้นๆ (เช่น ปิด 5 นาที) เป็นระยะๆ ก่อนถึงวันจริง เพื่อกระตุ้นให้คนที่ไม่ได้อ่านคู่มือรู้ตัวว่าระบบกำลังจะดับจริงๆ
🛠️ Weekend Sandbox Challenge
โจทย์: สร้าง API Rate Limiter แบบง่ายๆ ด้วย Redis
- ใช้ FastAPI + Middleware หรือ Decorator
- จำกัดให้ 1 IP Address เรียก API `/data` ได้ 5 ครั้ง ต่อ 1 นาที (Fixed Window)
- หากเกินกำหนด ให้คืน HTTP 429 Too Many Requests
- เคล็ดลับ: ใช้คำสั่ง `INCR` และ `EXPIRE` ใน Redis
💡 Senior Technical Interview Q&A
Q: ปัญหา Thundering Herd ในเรื่อง Caching คืออะไร และแก้ไขอย่างไร?
A: คือสถานการณ์ที่ Cache ของข้อมูลที่คนเข้าใช้งานเยอะมากๆ หมดอายุลง (Expire) พร้อมกัน ส่งผลให้ Request นับพันพุ่งตรงไปดึงข้อมูลจาก Database ภายในเสี้ยววินาทีจน DB น็อค แก้ไขได้โดยใช้ SWR (Stale-While-Revalidate) ให้เสิร์ฟของเก่าไปก่อน หรือตั้ง Mutex Lock ให้มีแค่ Thread เดียวหลุดไปอัปเดต Cache
Q: อธิบายความแตกต่างของ Database Connection Pooling กับเปิด Connection ปกติ?
A: การเปิด Connection ใหม่ต่อ 1 Request (TCP Handshake, Auth) กินเวลานานและเปลือง Resource ระบบ Connection Pooling เป็นการเปิด Connection เตรียมทิ้งไว้ (Pool) เมื่อแอปต้องการใช้ก็มาหยิบไป พอเสร็จก็คืนลง Pool ทำให้ระบบประมวลผลเร็วขึ้นมาก และสามารถคุมจำนวน Maximum Connection ไม่ให้ Database รับภาระหนักเกินไป