Microservices, Scaling & System Resilience

การพัฒนาระบบเก่า (legacy) ให้กลายเป็นระบบกระจาย — แลกความง่ายกับความทนทานและการขยายตัว

Monolithic Architecture

Microservices

Monolith — ก้อนเดียว Product Cart Auth Payment 1 process · 1 deploy Shared Database Payment พัง → ทั้งระบบล่ม = Shared Fate Microservices — แยกอิสระ Product Svc Cart Svc Auth Svc Payment Svc DB DB คุยกันผ่าน API เท่านั้น Payment พัง → ยังเลือกของ / ใส่ตะกร้าได้ = isolation ของความล้มเหลว

หัวใจไม่ใช่จำนวน service แต่คือ "ความล้มเหลวถูกจำกัดวงได้แค่ไหน"

เทียบข้อดีข้อเสีย

MonolithMicroservices
เริ่มต้นพัฒนาง่ายและเร็วกว่าตั้งค่าเยอะกว่ามาก
Deployทีเดียวทั้งก้อน แก้จุดเดียวต้อง deploy ใหม่หมดแยก deploy เฉพาะ service ที่แก้
การขยายต้องขยายทั้งระบบพร้อมกันขยายเฉพาะ service ที่โหลดหนัก
ความล้มเหลวShared fate — พังจุดเดียวล่มทั้งระบบจำกัดวงได้ ระบบอื่นยังทำงานต่อ
ฐานข้อมูลกลางร่วมกันของใครของมัน
การสื่อสารเรียกฟังก์ชันในหน่วยความจำ เร็วมากผ่านเครือข่าย มี latency และอาจล้มเหลว
ความยากในการ debugตามรอยง่าย อยู่ที่เดียวยากขึ้น ต้องมี distributed tracing
ความสอดคล้องข้อมูลใช้ transaction ของฐานข้อมูลได้ตรง ๆต้องใช้ eventual consistency และ compensating action

Monolithic "Shared Fate" Risk

ทุกส่วนอยู่ใน process เดียวกัน จึง ใช้ชะตากรรมร่วมกัน:

  • โมดูลรายงานยอดขายเขียน query หนักจนกิน CPU หมด → หน้าร้านที่ไม่เกี่ยวข้องก็ช้าตาม
  • โค้ดส่วน payment memory leak → process ตาย → ล็อกอินและดูสินค้าล่มไปด้วย
  • แก้ปุ่มเล็ก ๆ 1 จุด ต้อง deploy ทั้งระบบ ความเสี่ยงตกอยู่กับทุกฟีเจอร์

Horizontal vs Vertical Scaling

Vertical — ขยายขึ้น (scale up) 2 CPU / 4GB 16 CPU / 64GB เครื่องเดิม แต่แรงขึ้น มีเพดาน · ต้องดับเครื่องตอนอัปเกรด เป็น single point of failure Horizontal — ขยายออก (scale out) server server server Load Balancer เพิ่มเครื่องได้เรื่อย ๆ · เครื่องหนึ่งล่มยังมีตัวอื่น ต้องออกแบบให้ stateless ก่อน

REST ที่ stateless คือเงื่อนไขที่ทำให้ scale แนวนอนได้ — request ไปตกเครื่องไหนก็ทำงานได้เหมือนกัน

Vertical Scaling (scale up)Horizontal Scaling (scale out)
ทำอย่างไรเพิ่ม CPU / RAM ให้เครื่องเดิมเพิ่มจำนวนเครื่อง แล้วกระจายงานด้วย load balancer
ขีดจำกัดมีเพดานฮาร์ดแวร์แทบไม่มีเพดาน
ความทนทานเครื่องเดียวล่ม = ระบบล่มเครื่องหนึ่งล่ม ตัวอื่นรับงานต่อ
เงื่อนไขทำได้ทันที ไม่ต้องแก้โค้ดแอปต้อง stateless และ session ต้องไม่ผูกกับเครื่อง
ค่าใช้จ่ายราคาพุ่งเร็วเมื่อสเปกสูงมากใช้เครื่องเล็กหลายตัว คุ้มกว่าเมื่อโตขึ้น

Service Boundary

แบ่ง service ตาม ขอบเขตธุรกิจ ไม่ใช่ตามชั้นเทคนิค

แบ่งแบบผิดแบ่งแบบถูก
UI Service / Logic Service / Database ServiceProduct Service / Cart Service / Order Service / Auth Service
แก้ 1 ฟีเจอร์ต้องแตะทุก serviceแก้ฟีเจอร์เดียวจบใน service เดียว
กฎเหล็ก: service หนึ่งห้ามเข้าไปอ่าน/เขียนฐานข้อมูลของอีก service โดยตรง ต้องเรียกผ่าน API เท่านั้น ไม่งั้นจะกลายเป็น "distributed monolith" ที่ได้ข้อเสียของทั้งสองแบบ

API Contract

Contract ของ Product Service
--------------------------------------------
GET  /api/v1/products          → 200 [{id, name, price, stock}]
GET  /api/v1/products/:id      → 200 {…} | 404
POST /api/v1/products          → 201 {id} | 400 | 401

Resilience — ความทนทาน

เทคนิคทำอะไร
Timeoutไม่รอ service ที่ไม่ตอบจนค้างไปเรื่อย ๆ
Retryลองใหม่เมื่อพลาดชั่วคราว — ต้องระวังการทำซ้ำ จึงคู่กับ idempotency
Circuit Breakerถ้า service ปลายทางพังซ้ำ ๆ ให้ "ตัดวงจร" หยุดยิงชั่วคราว กันลามทั้งระบบ
Graceful Degradationฟีเจอร์รองล่มแต่ฟีเจอร์หลักยังใช้ได้ เช่นระบบแนะนำสินค้าล่ม แต่ยังซื้อของได้
Health Checkendpoint สำหรับถามว่า service ยังมีชีวิตอยู่ไหม เพื่อให้ load balancer ตัดเครื่องที่ตายออก

EDA กับ microservices

เชื่อมกับหัวข้อ 3 — เมื่อลูกค้าสั่งซื้อ เกิด event Order Placed แล้ว service ชำระเงิน, สต็อก และอีเมล ต่างตอบสนองเอง โดยไม่ต้องคอยถาม order service ตลอดเวลา

ข้อดี: service ไม่ผูกกันแน่น (loose coupling) เพิ่ม service ใหม่ที่สนใจ event เดิมได้โดยไม่แก้ของเก่า
ข้อเสีย: ตามรอยยากขึ้น และข้อมูลจะสอดคล้องกันแบบ eventual ไม่ใช่ทันทีทันใด

แนวข้อสอบอัตนัย

โจทย์: ระบบ e-commerce แบบ monolith ล่มทั้งเว็บทุกครั้งที่ระบบออกรายงานยอดขายตอนสิ้นเดือน ลูกค้าเข้าหน้าสินค้าไม่ได้เลย จงอธิบายปัญหา สาเหตุ ผลกระทบ และแนวทางแก้

  1. ปัญหา — งานเบื้องหลังที่ไม่เกี่ยวกับการขาย ทำให้ฟีเจอร์หลักที่สร้างรายได้ใช้งานไม่ได้
  2. สาเหตุทางเทคนิค — เป็น shared fate ของ monolith: โมดูลรายงานอยู่ใน process และใช้ฐานข้อมูลเดียวกับหน้าร้าน เมื่อ query หนักกิน CPU และล็อกตาราง ทุกโมดูลจึงถูกกระทบ และการ scale ก็ทำได้แค่ขยายทั้งก้อนพร้อมกัน
  3. ผลกระทบ — สูญเสียยอดขายในช่วงที่ระบบล่ม ผู้ใช้ไม่เชื่อถือ และทีมไม่กล้า deploy อะไรในช่วงสิ้นเดือน
  4. วิธีแก้เชิงสถาปัตยกรรม — แยก Reporting Service ออกไปเป็น service ของตัวเองพร้อมฐานข้อมูล/replica แยก ให้คุยกับระบบหลักผ่าน API หรือ event เท่านั้น, ทำ horizontal scaling กับ service หน้าร้านโดยออกแบบให้ stateless และวาง load balancer, ใส่ timeout + circuit breaker เพื่อกันไม่ให้ service ที่ช้าลากตัวอื่นล้ม และกำหนด API contract พร้อมเวอร์ชันให้ชัดก่อนแยกออกจากกัน