ช่วงพีคคนทัก LINE พร้อมกัน 200 คน แล้ว Conversion หาย 30%: วางสถาปัตยกรรม Queue กับ Retry ให้รับมือได้

สรุปสั้น ๆ
การยิง CAPI แบบ synchronous ทันทีที่มี event เกิดขึ้น ดูเรียบง่ายตอน traffic น้อย แต่พอวันไหนคนทักพร้อมกันเยอะ ระบบจะรอ response จากแพลตฟอร์มโฆษณาจนคอขวด การใส่ queue คั่นกลางพร้อม retry ที่มี backoff ที่เหมาะสม คือทางออกที่ทำให้ระบบรับมือช่วงพีคได้โดยไม่เสียข้อมูล
ร้านหนึ่งที่ผมช่วยดูระบบ เจอปัญหาแปลก ๆ คือทุกครั้งที่มีแคมเปญลดราคาช่วงเที่ยงคืนถึงตีหนึ่ง (เวลาที่คนไทยช้อปกันเยอะที่สุด) ยอด conversion ที่ส่งกลับเข้า Meta จะหายไปราว 30% เทียบกับยอดขายจริงที่ปิดในแชท ทั้งที่ช่วงเวลาปกติระบบทำงานถูกต้องแทบทุกครั้ง
พอไล่ log ดูจริง ๆ พบว่าโค้ดที่ยิง CAPI ถูกเรียกแบบ synchronous ทันทีที่ webhook รับ event เข้ามา คือรอ response จาก Meta ก่อนถึงจะตอบ 200 กลับให้ LINE พอช่วงพีค Meta เองก็ตอบช้าลงเป็นธรรมชาติ (เพราะทุกร้านทั่วประเทศยิงพร้อมกัน) ทำให้ endpoint ของร้านนี้ตอบ LINE ไม่ทัน เกิด timeout เป็นลูกโซ่ บาง event เลยหายไปเลยเพราะไม่มีกลไก retry เก็บไว้
ปัญหานี้ไม่ใช่เรื่องแปลก มันคือผลลัพธ์ธรรมชาติของสถาปัตยกรรมที่ทำงานแบบเรียงลำดับ (synchronous) ทั้งเส้น ตั้งแต่รับ webhook จนถึงยิง CAPI เสร็จ ทางแก้ที่วิศวกรระบบส่วนใหญ่ใช้กันคือแยกงานสองส่วนออกจากกันด้วย queue ซึ่งเป็นเรื่องที่บทความนี้จะเจาะลึก
ทำไม synchronous ถึงพังง่ายกว่าที่คิดตอน traffic พีค
เวลาระบบทำงานแบบ synchronous ทั้งเส้น (รับ webhook → ประมวลผล → ยิง CAPI → รอ response → ตอบ LINE) เวลาทั้งหมดของแต่ละคำขอคือผลรวมของทุกขั้นตอน ถ้าขั้นตอนไหนช้าลงแม้แค่ขั้นตอนเดียว เวลาทั้งเส้นก็ยืดตามไปด้วย และถ้ายืดจนเกิน timeout ที่ LINE กำหนด LINE จะยิง webhook ซ้ำ ซึ่งยิ่งเพิ่ม load ให้ระบบที่กำลังช้าอยู่แล้วให้ช้าลงไปอีก กลายเป็นวงจรที่แย่ลงเรื่อย ๆ
จุดที่ทำให้ปัญหานี้ร้ายแรงเป็นพิเศษสำหรับธุรกิจที่ขายผ่าน LINE คือ ช่วงเวลาที่ traffic พีคที่สุด (แคมเปญร้อน โปรโมชันจำกัดเวลา) มักตรงกับช่วงที่ conversion สำคัญที่สุดพอดี ระบบที่พังเฉพาะตอนพีคจึงสร้างความเสียหายมากกว่าที่ตัวเลขเปอร์เซ็นต์ downtime บอกไว้มาก
แยกงานด้วย queue: รับ event ให้เร็ว ประมวลผลทีหลังได้
หลักการคือ เปลี่ยนจาก ‘รับ webhook แล้วยิง CAPI ทันที’ เป็น ‘รับ webhook แล้วบันทึกลง queue ทันที (เร็วมาก) แล้วตอบ 200 กลับ LINE ทันที’ ส่วนการยิง CAPI จริงให้ worker แยกต่างหากดึงงานจาก queue มาประมวลผลทีหลัง วิธีนี้ทำให้เวลาตอบ LINE ไม่ขึ้นอยู่กับความเร็วของ Meta หรือ Google เลยแม้แต่น้อย
ข้อดีอีกอย่างของการมี queue คือ ทำให้ปรับความเร็วในการประมวลผลได้อิสระจากอัตราการรับ event เข้ามา ถ้า traffic พีคจน worker ตามไม่ทันชั่วคราว งานก็แค่รอคิวอยู่ ไม่หายไปไหน ต่างจากระบบ synchronous ที่ถ้าตอบไม่ทันคืองานหายเลยถ้าไม่มีกลไกเก็บไว้
retry อย่างเดียวไม่พอ ต้องมี backoff ที่ฉลาด
| กลยุทธ์ retry | พฤติกรรม | ความเสี่ยงถ้าใช้ผิด |
|---|---|---|
| Retry ทันทีไม่มีหน่วงเวลา | ยิงซ้ำติด ๆ กันทันทีที่ล้มเหลว | ถ้าแพลตฟอร์มปลายทางกำลังโดน rate limit อยู่แล้ว จะยิ่งซ้ำเติมปัญหา |
| Fixed delay (เช่น รอ 5 วิทุกครั้ง) | เว้นระยะเท่ากันทุกรอบ | ถ้าปัญหาต้นทางใช้เวลานานกว่านั้น จะ retry รัวจนเสียทรัพยากรเปล่า |
| Exponential backoff | เว้นระยะนานขึ้นทวีคูณทุกครั้งที่ล้มเหลว (1s, 2s, 4s, 8s...) | ต้องตั้งเพดานสูงสุดไว้ ไม่งั้นงานเก่าอาจค้างนานเกินไปจนข้อมูลไม่ทันสมัย |
เมื่อ retry ครบจำนวนแล้วยังไม่สำเร็จ ต้องมีที่ไปสุดท้าย
ระบบที่ดีต้องกำหนดเพดานจำนวนครั้งที่ retry ได้ (เช่น 5 ครั้ง) ไม่ใช่ retry ไปเรื่อย ๆ ไม่มีที่สิ้นสุด เพราะงานที่ล้มเหลวซ้ำ ๆ มักมีสาเหตุที่ retry เฉย ๆ แก้ไม่ได้ เช่น ข้อมูลผิด format หรือ field ที่จำเป็นหายไปตั้งแต่ต้น ถ้าปล่อยให้ retry ไม่มีเพดาน จะเปลืองทรัพยากร queue ไปเรื่อย ๆ โดยไม่มีประโยชน์
เมื่อครบเพดานแล้วยังไม่สำเร็จ งานนั้นควรถูกย้ายไปยัง dead letter queue ซึ่งเป็นที่พักสำหรับงานที่ต้องให้คนมาดูด้วยตาจริง ไม่ใช่ปล่อยให้หายไปเงียบ ๆ ทีมที่ดูแลระบบควรมีการแจ้งเตือนเมื่อ dead letter queue เริ่มมีงานสะสม เพราะนั่นคือสัญญาณว่ามีบางอย่างผิดปกติที่ต้องแก้ที่ต้นเหตุ ไม่ใช่แค่ปล่อยให้ retry ต่อไป
วัดสุขภาพของ queue ให้เห็นปัญหาก่อนลูกค้าจะรู้ตัว
- ความยาวของ queue (queue depth) — ถ้าตัวเลขนี้เพิ่มขึ้นต่อเนื่องแปลว่า worker ประมวลผลตามไม่ทัน ต้องเพิ่ม worker หรือหาสาเหตุที่ประมวลผลช้าลง
- อัตราความสำเร็จของการ retry — ถ้าอัตรานี้ตกลงเรื่อย ๆ มักบอกว่าปัญหาไม่ใช่แค่ความช้าชั่วคราว แต่อาจเป็นปัญหาที่ต้นเหตุจริงจัง เช่น token หมดอายุ
- อายุของงานที่เก่าที่สุดใน queue (oldest message age) — สำคัญกว่าความยาว queue เฉย ๆ เพราะบอกว่ามีงานที่ค้างนานแค่ไหน ซึ่งกระทบโดยตรงกับหน้าต่างเวลาที่แพลตฟอร์มยอมรับ conversion ย้อนหลัง
- จำนวนงานใน dead letter queue — ควรใกล้ศูนย์เสมอ ถ้าเริ่มสะสมคือสัญญาณเตือนที่ต้องรีบตรวจ ไม่ใช่ปล่อยไว้
สรุป
ระบบที่ทำงานได้ดีตอน traffic น้อยแต่พังตอนพีค ไม่ใช่ระบบที่ ‘แย่’ เสมอไป มันแค่ถูกออกแบบมาไม่รองรับ load ที่ไม่สม่ำเสมอ ซึ่งเป็นเรื่องปกติของธุรกิจที่ขายผ่านแคมเปญโปรโมชัน การแยกงานด้วย queue คือการยอมรับความจริงข้อนี้แล้วออกแบบให้รับมือได้ตั้งแต่แรก แทนที่จะไปแก้ตอนที่ลูกค้าบ่นว่ายอดหาย
สิ่งที่แยกระบบที่นิ่งกับระบบที่ล้มบ่อย ไม่ใช่แค่โค้ดที่เขียนถูกหรือผิด แต่คือการมี queue depth และ dead letter queue ให้ดูตลอดเวลา เพื่อรู้ปัญหาก่อนที่มันจะกลายเป็นยอดขายที่หายไปจริง
- ระบบ synchronous ที่ยิง CAPI ทันทีจะพังง่ายตอน traffic พีค เพราะเวลารอทั้งเส้นยืดตามความช้าของแพลตฟอร์มปลายทาง
- แยกงานด้วย queue ให้รับ event เร็วก่อน แล้วประมวลผลทีหลังด้วย worker แยกต่างหาก
- ใช้ exponential backoff สำหรับ retry และมี dead letter queue รองรับงานที่ล้มเหลวซ้ำ ๆ
คำถามที่พบบ่อย
ทีมเล็กไม่มีระบบ queue พร้อมใช้ ต้องเริ่มยังไง
ไม่จำเป็นต้องเริ่มจากระบบ queue ใหญ่โต ฐานข้อมูลที่มีอยู่แล้วก็ทำหน้าที่เป็นคิวง่าย ๆ ได้ โดยสร้างตารางเก็บงานที่รอประมวลผล แล้วมี cron job ดึงมาทำทีละชุด เมื่อ traffic โตขึ้นค่อยย้ายไปใช้ระบบ queue เฉพาะทาง
ควรตั้งเพดาน retry กี่ครั้งถึงจะเหมาะสม
ไม่มีตัวเลขตายตัว แต่ส่วนใหญ่ใช้ราว 3-5 ครั้งพร้อม exponential backoff ก็เพียงพอสำหรับปัญหาชั่วคราว ถ้าครบแล้วยังไม่สำเร็จ มักหมายความว่าเป็นปัญหาที่ retry ต่อไปก็ไม่ช่วย ควรส่งเข้า dead letter queue แทน
queue ทำให้ conversion event ล่าช้ากว่าเดิมไหม
ล่าช้าขึ้นเล็กน้อยในระดับวินาทีถึงหลักนาที ซึ่งแทบไม่กระทบการอ่านผลของแพลตฟอร์มโฆษณา แต่แลกกับความเสถียรที่สูงขึ้นมาก ถือว่าคุ้มค่ากว่าการเสี่ยงเสีย event ไปเลยตอน traffic พีค
จำเป็นต้องมีทีม dev เฉพาะทางถึงจะวางระบบนี้ได้ไหม
ไม่จำเป็นต้องเป็นผู้เชี่ยวชาญระดับสูง หลักการพื้นฐานของ queue และ retry เข้าใจได้ไม่ยาก แต่ต้องมีคนในทีมที่เข้าใจภาพรวมของระบบทั้งเส้น เพื่อออกแบบให้ทุกจุดสอดคล้องกัน ไม่ใช่แค่เพิ่ม queue เข้าไปโดยไม่คิดเรื่อง monitoring
ถ้าใช้ระบบสำเร็จรูปอย่าง linli จำเป็นต้องทำเรื่อง queue เองไหม
ถ้าระบบสำเร็จรูปมี queue และ retry จัดการให้อยู่แล้ว ก็ไม่จำเป็นต้องสร้างเอง แต่ทีมพัฒนาควรเข้าใจหลักการนี้ไว้ เพื่อประเมินได้ว่าระบบที่เลือกใช้รองรับ traffic พีคของธุรกิจตัวเองได้จริงหรือเปล่า
บทความที่เกี่ยวข้อง


