Microservices, Scaling & System Resilience
การพัฒนาระบบเก่า (legacy) ให้กลายเป็นระบบกระจาย — แลกความง่ายกับความทนทานและการขยายตัว
Monolithic Architecture
- ทุกฟีเจอร์ (สินค้า, ตะกร้า, ผู้ใช้, การชำระเงิน) อยู่ใน codebase เดียว deploy เป็นก้อนเดียว
- เรียกฟังก์ชันข้ามส่วนได้ตรง ๆ ในหน่วยความจำเดียวกัน
- ปกติใช้ฐานข้อมูลกลางร่วมกันทั้งระบบ
Microservices
- แยกเป็นบริการเล็ก ๆ ที่แต่ละตัวรับผิดชอบ ขอบเขตธุรกิจเดียว (business capability)
- แต่ละ service deploy เอง ขยายเอง และมีฐานข้อมูลของตัวเอง
- คุยกันผ่าน API (REST/HTTP) หรือ message/event queue เท่านั้น ห้ามแอบอ่านฐานข้อมูลของกันและกัน
หัวใจไม่ใช่จำนวน service แต่คือ "ความล้มเหลวถูกจำกัดวงได้แค่ไหน"
เทียบข้อดีข้อเสีย
| Monolith | Microservices | |
|---|---|---|
| เริ่มต้นพัฒนา | ง่ายและเร็วกว่า | ตั้งค่าเยอะกว่ามาก |
| 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
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 Service | Product Service / Cart Service / Order Service / Auth Service |
| แก้ 1 ฟีเจอร์ต้องแตะทุก service | แก้ฟีเจอร์เดียวจบใน service เดียว |
กฎเหล็ก: service หนึ่งห้ามเข้าไปอ่าน/เขียนฐานข้อมูลของอีก service โดยตรง
ต้องเรียกผ่าน API เท่านั้น ไม่งั้นจะกลายเป็น "distributed monolith" ที่ได้ข้อเสียของทั้งสองแบบ
API Contract
- ข้อตกลงว่า service หนึ่งเปิดอะไรให้เรียก — endpoint, method, รูปร่างของ request/response, status code
- เปลี่ยน contract แบบ breaking change จะทำให้ service ที่เรียกอยู่พังทันที
- วิธีจัดการ: ทำเวอร์ชัน เช่น
/api/v1/productsและ/api/v2/productsแล้วให้เวลาย้าย - เพิ่มฟิลด์ใหม่ = ปลอดภัย (non-breaking) · ลบหรือเปลี่ยนชนิดฟิลด์เดิม = breaking
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 Check | endpoint สำหรับถามว่า service ยังมีชีวิตอยู่ไหม เพื่อให้ load balancer ตัดเครื่องที่ตายออก |
EDA กับ microservices
เชื่อมกับหัวข้อ 3 — เมื่อลูกค้าสั่งซื้อ เกิด event Order Placed แล้ว service ชำระเงิน, สต็อก และอีเมล ต่างตอบสนองเอง โดยไม่ต้องคอยถาม order service ตลอดเวลา
ข้อดี: service ไม่ผูกกันแน่น (loose coupling) เพิ่ม service ใหม่ที่สนใจ event เดิมได้โดยไม่แก้ของเก่า
ข้อเสีย: ตามรอยยากขึ้น และข้อมูลจะสอดคล้องกันแบบ eventual ไม่ใช่ทันทีทันใด
ข้อเสีย: ตามรอยยากขึ้น และข้อมูลจะสอดคล้องกันแบบ eventual ไม่ใช่ทันทีทันใด
แนวข้อสอบอัตนัย
โจทย์: ระบบ e-commerce แบบ monolith ล่มทั้งเว็บทุกครั้งที่ระบบออกรายงานยอดขายตอนสิ้นเดือน ลูกค้าเข้าหน้าสินค้าไม่ได้เลย จงอธิบายปัญหา สาเหตุ ผลกระทบ และแนวทางแก้
- ปัญหา — งานเบื้องหลังที่ไม่เกี่ยวกับการขาย ทำให้ฟีเจอร์หลักที่สร้างรายได้ใช้งานไม่ได้
- สาเหตุทางเทคนิค — เป็น shared fate ของ monolith: โมดูลรายงานอยู่ใน process และใช้ฐานข้อมูลเดียวกับหน้าร้าน เมื่อ query หนักกิน CPU และล็อกตาราง ทุกโมดูลจึงถูกกระทบ และการ scale ก็ทำได้แค่ขยายทั้งก้อนพร้อมกัน
- ผลกระทบ — สูญเสียยอดขายในช่วงที่ระบบล่ม ผู้ใช้ไม่เชื่อถือ และทีมไม่กล้า deploy อะไรในช่วงสิ้นเดือน
- วิธีแก้เชิงสถาปัตยกรรม — แยก Reporting Service ออกไปเป็น service ของตัวเองพร้อมฐานข้อมูล/replica แยก ให้คุยกับระบบหลักผ่าน API หรือ event เท่านั้น, ทำ horizontal scaling กับ service หน้าร้านโดยออกแบบให้ stateless และวาง load balancer, ใส่ timeout + circuit breaker เพื่อกันไม่ให้ service ที่ช้าลากตัวอื่นล้ม และกำหนด API contract พร้อมเวอร์ชันให้ชัดก่อนแยกออกจากกัน