Retry Storm: เมื่อความพยายามอันแสนดี กลายเป็นปืนกล DDoS ถล่มระบบตัวเองจนพังยับ
⏱️ ใช้เวลาอ่านประมาณ: 4 นาที 20 วินาที
ในคู่มือ Resilience 101 กฎพื้นฐานสอนไว้ว่า: "ถ้าเรียก Service ปลายทางแล้วล้มเหลว จงเขียนกลไก Retry ซ้ำสัก 3 รอบ เพื่อป้องกันปัญหาเน็ตสะดุดชั่วคราว"
ฟังดูมีเหตุผลและปลอดภัย... แต่ทำไมในคืนที่ฐานข้อมูลแค่สะอึกไป 3 วินาที ระบบ Retry ที่แสนหวังดีนี้ กลับขยายจำนวน Request พุ่งพรวดขึ้น 10 เท่าตัวในพริบตาเดียว จนเหมือนบริษัทกำลังโดนแฮกเกอร์ยิง DDoS Attack จากฝีมือโค้ดของตัวเอง?
💥 ปรากฏการณ์ทวีคูณลูกโซ่ (Cascading Retry)
สมมติว่าคุณมี Microservices ร้อยเรียงกัน 3 ชั้น: Gateway ➔ Order Service ➔ Payment Service
นักพัฒนาแต่ละทีมต่างคนต่างใส่ logic: "ถ้าปลายทางพัง กู Retry 3 ครั้ง":
- ลูกค้ากดสั่งซื้อ 1 Request วิ่งเข้า Gateway
- Order Service ส่งงานไปหา Payment Service แล้วเกิด timeout... Order Service เลย Retry 3 ครั้ง
- แต่ระหว่างนั้น Gateway รอนานเกินไปจน timeout เหมือนกัน... Gateway เลยสั่ง Retry เรียก Order Service อีก 3 ครั้ง!
- แต่ละรอบที่ Gateway เรียกซ้ำ Order Service ก็จะแตกหน่อ Retry ส่งต่อไปหา Payment อีกรอบละ 3 ครั้ง...
จากคำสั่งซื้อแค่ 1 ครั้งของลูกค้า กลายพันธุ์เป็น 3 × 3 = 9 Requests ที่ยิงอัดกระหน่ำใส่ Payment Service ตัวเดิมที่กำลังขาดอากาศหายใจอยู่แล้ว!
"เปรียบเหมือนคนกำลังจมน้ำแทนที่จะโยนห่วงยางไปช่วย เรากลับส่งฝูงชนอีก 50 คนกระโดดลงไปเกาะคอเขาพร้อมกันเพื่อถามว่า 'ไหวไหมๆๆ' ซ้ำๆ!"
🥁 จังหวะกลองนรก: การ Retry พร้อมเพรียงกัน (Thundering Retry)
ถ้าการ Retry ตั้งเวลาแบบทื่อๆ เช่น "รอ 1 วินาทีแล้วลองใหม่"
เมื่อคำขอ 2,000 อันพังพร้อมกันในวินาทีที่ 0... ทั้ง 2,000 อันก็จะ พร้อมใจกันกระหน่ำยิงซ้ำลงมาในวินาทีที่ 1 พร้อมกันเป๊ะ เกิดเป็นคลื่นสึนามิกระแทกเป็นจังหวะๆ จนเซิร์ฟเวอร์ปลายทางไม่มีวันเงยหน้าขึ้นมาฟื้นตัวได้เลยตลอดกาล
🛡️ 3 คาถาดับพายุ: Backoff, Jitter และ Circuit Breaker
1. Exponential Backoff + Jitter (ทิ้งช่วงและสุ่มเวลา)
อย่า Retry ทันที และอย่ารอเวลาเท่ากันเป๊ะ ให้เพิ่มระยะเวลารอแบบทวีคูณ (1s, 2s, 4s) พร้อมกับ บวกเวลาสุ่ม (Jitter) เข้าไปด้วยเสมอ:
val delay = baseDelay * 2.0.pow(attempt) + Random.nextDouble(0.1, 0.5)
คลื่น Request ที่เคยกรูเข้าพร้อมกัน จะแตกกระจายแถวออกไปคนละเสี้ยววินาที ทำให้แรงปะทะลดลงมหาศาล
2. ติดเบรกเกอร์ตัดไฟ (Circuit Breaker)
ใช้เครื่องมืออย่าง Resilience4j มาตรวจจับ ถ้าอัตราความล้มเหลวของปลายทางทะลุ 50% ให้สับคัตเอาต์เปิดวงจร (OPEN) ทันที เพื่อ ปฏิเสธคำขอทันทีโดยไม่ต้องเสียเวลายิงไปรบกวนปลายทาง เปิดโอกาสให้เซิร์ฟเวอร์หลังบ้านได้เคลียร์งานและกู้ระบบขึ้นมาใหม่อย่างสงบ
💡 5-Min Takeaway
"ในระบบ Distributed System ความตื๊อไม่ใช่สิ่งที่ดีเสมอไป" เมื่อระบบปลายทางเกิดปัญหา การทู่ซี้พยายามส่งซ้ำแบบไม่ลืมหูลืมตาคือการเร่งปฏิกิริยาให้ระบบล่มเร็วขึ้น การใส่ Exponential Backoff ผสม Jitter และการกล้าตัดใจสับ Circuit Breaker เพื่อปล่อยให้ระบบได้พักฟื้น คือเส้นแบ่งระหว่างระบบที่ฟื้นตัวได้เอง กับระบบที่ต้องปลุกทีมมาช่วยกันรีสตาร์ตตอนตีสาม