ข้อความเดิมเด้งเข้ามาสองครั้ง ห่างกันไม่กี่วินาที หลายคนคิดว่าเป็นความบังเอิญของเครือข่าย แล้วปล่อยผ่าน แต่จริง ๆ มันมีสาเหตุที่อธิบายได้ และเกือบทั้งหมดมาจากสี่แบบนี้
การส่งซ้ำไม่ใช่แค่เรื่องน่ารำคาญ ถ้าข้อความนั้นคือแจ้งเตือนออเดอร์ คนรับของอาจจัดของสองรอบ ถ้าเป็นแจ้งเตือนโอนเงิน บัญชีอาจถูกบันทึกซ้ำ และถ้าเป็นช่องทางที่มีโควตา มันกินโควตาเป็นเท่าตัวโดยไม่ได้อะไรเพิ่ม
สี่ข้อต่อไปนี้เรียงตามความถี่ที่เจอจริง พร้อมวิธีแก้ที่ใช้ได้
อาการ: ส่งซ้ำเฉพาะตอนที่มีงานเยอะ ช่วงปกติไม่เป็น และสองข้อความห่างกันประมาณเท่ากับรอบเวลาที่ตั้งไว้พอดี
ตั้งให้ทำงานทุกนาที แต่ข้างในมีงานหนัก เช่นสร้างภาพหรือเรียก API หลายชั้น พอรอบหนึ่งใช้เวลาเกินหนึ่งนาที รอบถัดไปจะเริ่มทำงานทั้งที่รอบเก่ายังไม่จบ ทั้งสองรอบมองเห็นงานชิ้นเดียวกันว่า "ยังไม่ได้ส่ง" จึงส่งกันคนละที
เคสที่ผมเจอคือการ์ดแจ้งผลที่ต้องสร้างภาพก่อนส่ง รอบหนึ่งใช้เวลาเกินนาที ผลคือเหตุการณ์เดียวได้ภาพสองใบคนละแบบ แล้วส่งออกไปทั้งคู่
จำกัดงานหนักให้ทำทีละชิ้นต่อรอบ ที่เหลือตกไปรอบถัดไป แล้วต้องมีตัวจำว่าชิ้นไหนทำไปแล้ว ไม่ใช่พึ่งเวลาอย่างเดียว ถ้ายังไม่พอ ให้ตั้งธงล็อกไว้ตอนเริ่มทำงานและปลดตอนจบ รอบที่มาทีหลังเห็นธงแล้วข้ามไปเลย
อาการ: มีโค้ดเช็คแล้วว่า "ถ้าเคยส่งแล้วให้ข้าม" แต่ยังส่งซ้ำอยู่ดี และมักเกิดตอนสองรอบทำงานพร้อมกัน
โครงสร้างที่เขียนกันทั่วไปคือ อ่านค่าว่าเคยส่งหรือยัง ถ้ายัง ให้ส่ง แล้วค่อยบันทึกว่าส่งแล้ว ปัญหาคือช่วงระหว่าง "อ่าน" กับ "บันทึก" มีช่องว่างเวลาอยู่ ถ้าอีกรอบหนึ่งอ่านในจังหวะเดียวกัน มันจะเห็นว่ายังไม่เคยส่งเหมือนกัน แล้วส่งทั้งคู่
ที่เข้าใจผิดกันมากคือคิดว่าฐานข้อมูลแบบ key-value ธรรมดาจะกันได้ แต่มันไม่รับประกันว่าการอ่านกับเขียนจะเกิดแบบต่อเนื่องกันโดยไม่มีใครแทรก ยิ่งถ้าข้อมูลกระจายหลายเครื่อง ค่าที่อ่านได้อาจเป็นค่าเก่าด้วยซ้ำ
ต้องให้การ "จอง" สิทธิ์ส่งเกิดขึ้นในที่เดียวที่ทำงานทีละคำสั่งจริง ๆ เช่นใช้หน่วยประมวลผลที่ผูกกับเหตุการณ์นั้นตัวเดียว หรือใช้ฐานข้อมูลที่รองรับการเขียนแบบมีเงื่อนไข (เขียนได้เฉพาะเมื่อยังไม่มีคีย์นี้) แล้วให้ผู้ที่จองได้เท่านั้นเป็นคนส่ง
หลักคิดคือเปลี่ยนจาก "ถามว่าเคยส่งไหม" เป็น "ขอสิทธิ์ส่ง" ซึ่งมีผู้ชนะได้คนเดียวเสมอ
อาการ: ส่งซ้ำแบบห่างกันนาน ๆ ไม่ใช่วินาที และเนื้อหาสองข้อความต่างกันนิดหน่อย
อันนี้เนียนที่สุดและหายากที่สุด ตัวกันซ้ำทำงานถูกต้องทุกอย่าง แต่รหัสที่ใช้ระบุว่า "นี่คือเหตุการณ์เดียวกัน" ดันประกอบจากข้อมูลที่เปลี่ยนได้
ตัวอย่างที่ผมเจอ: ระบบแจ้งเตือนเหตุการณ์กีฬาสร้างรหัสจาก ลำดับที่ + ชื่อทีม + ชื่อผู้ทำ พอภายหลังมีการแก้ข้อมูลว่าเหตุการณ์นั้น ถูกนับให้อีกฝ่าย ชื่อทีมในข้อมูลจึงเปลี่ยน รหัสที่คำนวณได้จึงกลายเป็นรหัสใหม่ ระบบมองว่าเป็นเหตุการณ์ที่ไม่เคยเห็น แล้วส่งอีกครั้ง
ประกอบรหัสจากสิ่งที่ไม่มีวันเปลี่ยนเท่านั้น เช่นรหัสเหตุการณ์จากต้นทาง บวกลำดับที่ ไม่เอาชื่อ สถานะ หรือข้อความมาปน ถ้าต้นทางมีรหัสของตัวเองอยู่แล้วให้ใช้ของเขาเลย และตรวจว่ารหัสเดียวกันเมื่อคำนวณซ้ำในวันถัดไปยังได้ค่าเดิม
อาการ: ส่งซ้ำทุกครั้งอย่างสม่ำเสมอ ไม่ใช่บางครั้ง และมักเริ่มเกิดหลังจากเพิ่งเพิ่มเครื่องมือใหม่เข้าระบบ
เกิดตอนที่มีสองชั้นทำงานต่อกัน เช่นมีตัวสร้างเนื้อหาอยู่แล้ว แล้วเพิ่มตัวจัดการเวิร์กโฟลว์มาเรียกใช้ ตัวจัดการเรียกให้สร้างเนื้อหา แล้วเอาผลลัพธ์ไปส่งเอง แต่ตัวสร้างเนื้อหาก็ส่งของมันเองด้วยตามค่าตั้งต้น ผลคือผู้รับได้สองครั้งทุกครั้ง
กรณีนี้เจอบ่อยมากเวลาเอาเครื่องมือใหม่มาครอบระบบเดิม เพราะเครื่องมือเดิมมักตั้งค่าเริ่มต้นให้ส่งเองเสมอ
กำหนดให้ชัดว่า ใครคือเจ้าของหน้าที่ส่ง แล้วปิดของอีกฝั่ง วิธีที่สะอาดคือให้ตัวสร้างเนื้อหามีโหมดที่ทำงานแล้วคืนผลลัพธ์กลับ โดยไม่ส่งเอง และให้ตัวจัดการเวิร์กโฟลว์เรียกโหมดนั้นเสมอ ไม่ใช่หวังว่าคนตั้งค่าจะจำได้
ดูจากลักษณะของการซ้ำก่อน จะแคบลงได้เร็วมาก
และสิ่งที่ควรมีตั้งแต่แรกคือ ตัวตรวจจับการซ้ำ — เก็บบันทึกว่าส่งอะไรไปเมื่อไหร่ แล้วมีงานที่วิ่งตรวจเป็นระยะว่า มีเหตุการณ์ไหนถูกส่งมากกว่าหนึ่งครั้งหรือเปล่า เพราะการซ้ำที่อันตรายที่สุดคือแบบที่เกิดนาน ๆ ครั้งจนไม่มีใครสังเกต
ชุดเวิร์กโฟลว์ n8n ของเรามีตัวรับ error กลางและตัวเฝ้าระบบที่ออกแบบ มาพร้อมกลไกกันซ้ำตั้งแต่ต้น พร้อมคู่มือไทยบอกทีละจุดว่าต้องแก้อะไรก่อนใช้
ดูชุดเวิร์กโฟลว์ →อ่านต่อ: 9 เวิร์กโฟลว์ n8n ที่ธุรกิจไทยใช้จริง · LINE OA โควตาหมดกลางเดือน แก้ยังไง