← กลับไปหน้าบทความ
ขั้นสูง (MarTech)

ออเดอร์เดียวต้องส่งเข้า Meta, Google และ TikTok พร้อมกัน แต่ event_id เดียวใช้ซ้ำสามที่ไม่ได้ ออกแบบ Fan-out Pipeline ให้ไม่ชนกันเอง

ทีมบรรณาธิการ linli07 ก.ย. 07:43อัปเดต 07 ก.ย. 07:43อ่าน 3 นาที
ออเดอร์เดียวต้องส่งเข้า Meta, Google และ TikTok พร้อมกัน แต่ event_id เดียวใช้ซ้ำสามที่ไม่ได้ ออกแบบ Fan-out Pipeline ให้ไม่ชนกันเอง
บทความนี้จัดทำเพื่อความรู้ ชื่อธุรกิจ ตัวเลข หรือบทสนทนาที่ไม่มีแหล่งอ้างอิงกำกับเป็นกรณีตัวอย่างเพื่ออธิบายแนวคิด ดูรายละเอียดที่ นโยบายกองบรรณาธิการ

สรุปสั้น ๆ

เมื่อธุรกิจยิงแอดพร้อมกันหลายแพลตฟอร์ม ออเดอร์หนึ่งรายการต้องกลายเป็น conversion event หลายชุดที่ส่งไปคนละปลายทาง แต่ละชุดต้องมี event_id ของตัวเองที่ผูกกับ platform นั้นโดยเฉพาะ ไม่ใช่ใช้ค่าเดียวกันซ้ำ และการยิงออกทั้งหมดต้องออกแบบให้ความล่าช้าหรือ error ของปลายทางหนึ่งไม่ไปกระทบปลายทางอื่น

ทีมการตลาดของธุรกิจหนึ่งที่ผมช่วยดู ยิงแอดพร้อมกันสามช่องทางคือ Meta, Google และ TikTok เพื่อทดสอบว่าช่องทางไหนสร้าง lead คุณภาพดีที่สุด แต่ backend ที่ทีมพัฒนาเขียนไว้ยิง conversion event ออกไปทั้งสามปลายทางแบบเรียงลำดับในฟังก์ชันเดียว เขียน code เรียก Meta CAPI เสร็จ ค่อยเรียก Google ต่อ เสร็จแล้วค่อยเรียก TikTok

วันหนึ่ง Google Ads API ตอบช้าผิดปกติเป็นเวลาหลายวินาที ผลคือ TikTok event ที่ควรจะยิงต่อจากนั้นก็ล่าช้าตามไปด้วยทั้งที่ TikTok ไม่มีปัญหาอะไรเลย และในบางกรณีที่ Google ตอบ error กลับมา โค้ดที่เขียนแบบเรียงลำดับก็หยุดทำงานทั้งเส้นไปเลย ทำให้ event ที่ควรส่งเข้า TikKok หายไปด้วย ทั้งที่ปัญหาไม่ได้เกี่ยวอะไรกับ TikTok เลยสักนิด

นี่คือปัญหาคลาสสิกของการออกแบบ fan-out ที่ไม่ได้แยกแต่ละปลายทางออกจากกันอย่างแท้จริง บทความนี้จะเจาะแนวทางออกแบบ pipeline ที่ส่ง event เดียวกันไปหลายแพลตฟอร์มพร้อมกัน โดยที่ความล้มเหลวของปลายทางหนึ่งไม่ลากปลายทางอื่นให้พังตามไปด้วย

หนึ่งออเดอร์ ไม่ใช่หนึ่ง event แต่คือหลาย event ที่มี identity ของตัวเอง

ความเข้าใจผิดที่พบบ่อยคือคิดว่า event_id ควรผูกกับออเดอร์แบบหนึ่งต่อหนึ่ง แล้วใช้ค่าเดียวกันส่งไปทุกแพลตฟอร์ม ซึ่งจะสร้างปัญหาในทางปฏิบัติเพราะแต่ละแพลตฟอร์มมีกลไก deduplication ของตัวเองที่ผูก event_id เข้ากับ pixel หรือ dataset ของแพลตฟอร์มนั้นโดยเฉพาะ การใช้ event_id เดียวกันข้ามแพลตฟอร์มไม่ได้ก่อให้เกิดปัญหาโดยตรง แต่ก็ไม่ได้ช่วยอะไรเช่นกัน เพราะแต่ละแพลตฟอร์มไม่รู้จักกันอยู่แล้ว

แนวทางที่ชัดเจนกว่าคือมองว่าออเดอร์หนึ่งรายการสร้าง ‘สาม event ที่เป็นอิสระจากกัน’ แต่ละ event มี event_id เป็นของตัวเองที่คำนวณมาจาก order id บวกกับชื่อแพลตฟอร์มปลายทาง เช่น hash ของ order-id + meta หรือ order-id + tiktok วิธีนี้ทำให้แต่ละ event ยังคง deterministic (คำนวณซ้ำได้ค่าเดิมเสมอ) ตามหลักการidempotency key ที่ต้องออกแบบให้นิ่ง แต่แยกจากกันชัดเจนตามปลายทาง

ยิงแบบขนาน ไม่ใช่เรียงลำดับ เพื่อไม่ให้ความช้าของปลายทางหนึ่งลากปลายทางอื่น

หัวใจของการแก้ปัญหาคือเปลี่ยนจากการเรียก API ทีละปลายทางแบบเรียงลำดับ (sequential) ไปเป็นการยิงพร้อมกันแบบขนาน (parallel) โดยแต่ละปลายทางทำงานอิสระจากกันโดยสมบูรณ์ ถ้าปลายทางหนึ่งช้าหรือ error ปลายทางอื่นต้องไม่ได้รับผลกระทบเลย ซึ่งทำได้ด้วยการเรียกแต่ละ API แยกเป็นงานย่อยที่ทำงานพร้อมกัน แล้วรอผลลัพธ์รวมจากทุกงานในภายหลัง ไม่ใช่รอทีละตัวต่อเนื่องกัน

จุดที่ต้องระวังเพิ่มเติมคือ การจัดการ error ต้องแยกระดับกันด้วย ถ้า Meta ยิงไม่สำเร็จ ไม่ควรทำให้ Google หรือ TikTok หยุดทำงานตามไปด้วย และควรมี log ที่บอกชัดเจนว่าปลายทางไหนสำเร็จ ปลายทางไหนล้มเหลว เพื่อให้ทีมรู้ว่าต้อง retry เฉพาะปลายทางที่มีปัญหาจริง ไม่ใช่ retry ทั้งชุดทั้งที่บางปลายทางสำเร็จไปแล้ว

ใช้ queue แยกตามปลายทาง แทนการยิงตรงจากจุดเดียว

สถาปัตยกรรมข้อดีข้อจำกัด
ยิงตรงแบบขนานในฟังก์ชันเดียวเขียนง่าย เหมาะกับ traffic ไม่สูงมากถ้าเซิร์ฟเวอร์ crash กลางทาง อาจไม่รู้ว่าปลายทางไหนสำเร็จไปแล้วบ้าง
Queue รวมหนึ่งเดียว ส่งต่อให้ worker แยกตามปลายทางแยก retry ตามปลายทางได้ชัดเจน ทนต่อ traffic สูงกว่าต้องออกแบบ<a href="/blog/capi-queue-retry-architecture-line">retry และ backoff</a>แยกให้แต่ละปลายทางด้วย ไม่ใช่ใช้ค่าเดียวกันหมด
Queue แยกเป็นสามคิวตั้งแต่ต้น (queue ต่อแพลตฟอร์ม)ปลายทางหนึ่งมีปัญหาไม่กระทบ throughput ของปลายทางอื่นเลยซับซ้อนกว่าในการดูแล ต้อง monitor สามคิวแยกกัน

มูลค่าและสกุลเงินที่ส่งไปแต่ละแพลตฟอร์มต้องตรงกันเป๊ะ

อีกจุดที่มักถูกมองข้ามในการออกแบบ fan-out คือ มูลค่าของ conversion ที่ส่งไปแต่ละปลายทางต้องมาจากแหล่งข้อมูลเดียวกัน ณ เวลาเดียวกัน ไม่ใช่คำนวณแยกกันในแต่ละจุดของโค้ด เพราะถ้าฟังก์ชันที่ยิงเข้า Meta กับฟังก์ชันที่ยิงเข้า Google ต่างดึงราคาสินค้าจากคนละจุดในระบบ (เช่น จุดหนึ่งดึงราคาก่อนหักส่วนลด อีกจุดดึงหลังหักส่วนลด) ตัวเลข revenue ที่แต่ละแพลตฟอร์มเห็นจะไม่ตรงกัน ทำให้เปรียบเทียบประสิทธิภาพระหว่างช่องทางผิดเพี้ยนไปด้วย

วิธีป้องกันคือให้มีจุดคำนวณมูลค่า conversion เพียงจุดเดียวในระบบ (single source of truth) แล้วส่งค่าเดียวกันนี้แตกเป็น event ย่อยไปยังทุกปลายทาง ไม่ใช่ปล่อยให้แต่ละฟังก์ชันที่เรียก API ของแต่ละแพลตฟอร์มคำนวณมูลค่าซ้ำเอง ซึ่งเสี่ยงต่อความไม่สอดคล้องกันในระยะยาว

เมื่อสำเร็จบางปลายทาง ล้มเหลวบางปลายทาง ต้องจัดการยังไง

  1. บันทึกสถานะความสำเร็จของแต่ละปลายทางแยกกันในฐานข้อมูล ไม่ใช่บันทึกเป็นสถานะรวมเดียวว่า ‘ส่งแล้ว’ หรือ ‘ยังไม่ส่ง’
  2. ตั้งค่า retry เฉพาะปลายทางที่ล้มเหลว โดยใช้ event_id เดิมที่ผูกกับปลายทางนั้นเพื่อไม่ให้เกิดการนับซ้ำเมื่อ retry สำเร็จภายหลัง
  3. ถ้าปลายทางใดล้มเหลวซ้ำ ๆ เกินจำนวนที่กำหนด ให้มีระบบแจ้งเตือนทีมแยกตามปลายทาง เพื่อให้รู้ว่าปัญหาอยู่ที่ credential, rate limit หรือ schema ของแพลตฟอร์มไหนโดยเฉพาะ
  4. ทำ dashboard สรุปอัตราความสำเร็จแยกตามปลายทางเทียบกัน เพื่อให้เห็นว่าปลายทางไหนมีปัญหาต่อเนื่องกว่าปลายทางอื่น ซึ่งอาจสะท้อนปัญหาการตั้งค่าที่ต่างกันไป

วันหนึ่งต้องเพิ่มแพลตฟอร์มที่สี่ ระบบต้องขยายได้โดยไม่ต้องเขียนใหม่ทั้งหมด

จุดทดสอบที่ดีที่สุดว่าสถาปัตยกรรม fan-out ถูกออกแบบมาอย่างเหมาะสมหรือไม่ คือลองนึกภาพว่าธุรกิจตัดสินใจเพิ่มช่องทางโฆษณาที่สี่ในอนาคต เช่น เพิ่ม Line Ads เข้ามาอีกปลายทางหนึ่ง ถ้าโค้ดเดิมเขียนแบบ hardcode ชื่อสามแพลตฟอร์มไว้ทุกจุดของ pipeline การเพิ่มปลายทางใหม่จะต้องไปแก้ไขหลายจุดพร้อมกัน ซึ่งเสี่ยงต่อการตกหล่นและเพิ่มโอกาสเกิด bug ใหม่ในจุดที่เคยทำงานถูกต้องอยู่แล้ว

การออกแบบที่ยืดหยุ่นกว่าคือแยก configuration ของแต่ละปลายทางออกจาก logic หลักของ pipeline เช่น เก็บรายชื่อปลายทางที่ต้อง fan-out ไปเป็นรายการ configuration ที่อ่านจากที่เดียว แล้วให้ logic การสร้าง event_id การยิง API และการจัดการ retry ทำงานแบบเดียวกันกับทุกปลายทางโดยรับพารามิเตอร์ต่างกันไปตาม configuration นั้น เมื่อต้องเพิ่มปลายทางใหม่ก็แค่เพิ่ม configuration และ adapter เฉพาะของแพลตฟอร์มนั้นเข้าไป โดยไม่ต้องแตะ logic หลักที่ทำงานอยู่แล้วเลย

อีกประโยชน์ของการออกแบบแบบนี้คือ การถอดปลายทางใดปลายทางหนึ่งออกชั่วคราวก็ทำได้ง่ายเช่นกัน เช่น ถ้าธุรกิจตัดสินใจหยุดยิงแอดในแพลตฟอร์มหนึ่งไปสักระยะ ก็แค่ปิด configuration ของปลายทางนั้นโดยไม่ต้องลบหรือแก้ไขโค้ดส่วนอื่นเลย เมื่อกลับมายิงแอดอีกครั้งก็แค่เปิด configuration กลับมาใช้งานตามเดิม ความยืดหยุ่นแบบนี้ช่วยให้ทีมปรับเปลี่ยนกลยุทธ์การยิงแอดได้รวดเร็วโดยไม่ต้องพึ่งทีมพัฒนาทุกครั้งที่มีการเปลี่ยนแปลงช่องทาง

เอกสารที่ควรมีติดตัวไว้ก่อนเพิ่มปลายทางใหม่ทุกครั้ง

เมื่อถึงเวลาต้องเพิ่มปลายทางใหม่จริง สิ่งที่ช่วยให้งานนี้ราบรื่นไม่ใช่แค่โครงสร้างโค้ดที่ยืดหยุ่น แต่ยังรวมถึงเอกสารที่บันทึกไว้ว่าแต่ละปลายทางเดิมมีข้อกำหนดพิเศษอะไรบ้างที่ต่างจากปลายทางอื่น เช่น รูปแบบการแฮชข้อมูลที่ต่างกันเล็กน้อย ขีดจำกัดของ rate limit ที่ไม่เท่ากัน หรือฟิลด์บางตัวที่จำเป็นสำหรับแพลตฟอร์มหนึ่งแต่ไม่จำเป็นสำหรับอีกแพลตฟอร์ม ถ้าไม่มีเอกสารสรุปเรื่องนี้ไว้ นักพัฒนาที่มาทำงานเพิ่มปลายทางใหม่ในอนาคตอาจต้องเสียเวลาไล่อ่านโค้ดเดิมทั้งหมดเพื่อทำความเข้าใจว่าทำไมแต่ละปลายทางถึงมี adapter ที่ต่างกันเล็กน้อย

การมีเอกสารสรุปข้อกำหนดของแต่ละแพลตฟอร์มไว้ในที่เดียว พร้อมอัปเดตให้ทันสมัยเมื่อมีการเปลี่ยนแปลง ช่วยลดเวลาที่ต้องใช้ในการเพิ่มปลายทางใหม่ลงได้มาก และยังเป็นประโยชน์เมื่อต้องแก้ไขปัญหาที่เกิดขึ้นกับปลายทางใดปลายทางหนึ่งในอนาคต เพราะทีมสามารถอ้างอิงเอกสารนี้ได้ทันทีโดยไม่ต้องพึ่งความจำของคนที่เคยเขียนโค้ดส่วนนั้นไว้แต่อาจไม่ได้อยู่ในทีมแล้ว

สรุป

การส่ง conversion เดียวกันออกไปหลายแพลตฟอร์มพร้อมกันดูเหมือนงานง่ายในตอนแรก แต่ถ้าออกแบบให้ทุกปลายทางผูกติดกันเป็นเส้นเดียว ความล่าช้าหรือความล้มเหลวของปลายทางหนึ่งจะลากปลายทางอื่นให้เสียหายไปด้วยโดยไม่จำเป็น การแยกแต่ละปลายทางให้เป็นอิสระจากกันตั้งแต่ระดับ event_id ไปจนถึงการยิงและ retry คือกุญแจสำคัญ

ธุรกิจที่ยิงแอดหลายช่องทางเพื่อเปรียบเทียบประสิทธิภาพ ควรให้ความสำคัญกับความสม่ำเสมอของมูลค่าที่ส่งไปแต่ละปลายทางเป็นพิเศษ เพราะถ้าตัวเลขไม่ตรงกันตั้งแต่ต้นทาง การเปรียบเทียบระหว่างช่องทางจะไม่มีความหมายเลย

  • ออเดอร์หนึ่งรายการที่ยิงหลายแพลตฟอร์มควรกลายเป็นหลาย event ที่มี event_id แยกตามปลายทาง ไม่ใช่ใช้ค่าเดียวกันซ้ำ
  • ยิงแต่ละปลายทางแบบขนาน ไม่ใช่เรียงลำดับ เพื่อไม่ให้ความช้าของปลายทางหนึ่งลากปลายทางอื่นให้ช้าตาม
  • มูลค่า conversion ที่ส่งไปทุกปลายทางต้องมาจากจุดคำนวณเดียวกัน ไม่งั้นการเปรียบเทียบประสิทธิภาพระหว่างช่องทางจะผิดเพี้ยน

คำถามที่พบบ่อย

ถ้าใช้ event_id เดียวกันส่งทุกแพลตฟอร์ม จะเกิดปัญหาจริงไหม หรือแค่ไม่เป็นมาตรฐาน

ในทางปฏิบัติมักไม่เกิด error ทันทีเพราะแต่ละแพลตฟอร์มไม่รู้จัก event_id ของกันและกัน แต่การแยก event_id ตามปลายทางช่วยให้ debug และ retry ได้ตรงจุดกว่ามาก และป้องกันปัญหาที่อาจเกิดขึ้นในอนาคตหากมีการเปลี่ยนแปลงกลไก deduplication ของแพลตฟอร์มใดแพลตฟอร์มหนึ่ง

จำเป็นต้องมี queue แยกสามคิวตั้งแต่เริ่มต้นเลยไหม หรือเริ่มจากยิงขนานธรรมดาก่อนได้

ธุรกิจที่ traffic ยังไม่สูงมากสามารถเริ่มจากการยิงแบบขนานในฟังก์ชันเดียวได้ก่อน แล้วค่อยพัฒนาไปสู่ queue แยกตามปลายทางเมื่อปริมาณ event เพิ่มขึ้นจนกระทบ latency หรือเริ่มเห็นปัญหาปลายทางหนึ่งกระทบอีกปลายทางบ่อยขึ้น

ถ้าปลายทางหนึ่งใช้เวลานานผิดปกติ จะกระทบ latency budget ของ webhook โดยรวมไหม

ถ้าออกแบบให้ยิงขนานอย่างถูกต้อง การรอผลลัพธ์รวมจากทุกปลายทางควรใช้เวลาเท่ากับปลายทางที่ช้าที่สุดเพียงตัวเดียว ไม่ใช่ผลรวมของทุกปลายทาง ซึ่งยังดีกว่าการยิงเรียงลำดับมาก แต่ถ้าต้องการควบคุม latency ให้เข้มงวดกว่านั้น ควรแยกงานส่งเข้าคิวแล้วตอบ webhook กลับก่อน ตามแนวทาง<a href="/blog/capi-latency-budget-line">การวาง latency budget แยกแต่ละขั้นตอน</a>

ต้อง normalize ข้อมูล PII แยกกันสำหรับแต่ละแพลตฟอร์มไหม เพราะสเปกไม่เหมือนกัน

ใช่ ต้องแยก เพราะแต่ละแพลตฟอร์มมีสเปกการ normalize ก่อนแฮชที่ต่างกันเล็กน้อยตามที่ได้เจาะไว้ใน<a href="/blog/thai-pii-normalization-hash-line">การ normalize ข้อมูลไทยก่อนแฮช</a> การ fan-out ไปหลายแพลตฟอร์มจึงต้องมีชั้นแปลงรูปแบบข้อมูลแยกตามปลายทางด้วยเช่นกัน ไม่ใช่แค่แยกเรื่อง event_id อย่างเดียว

ธุรกิจที่ยิงแอดแค่แพลตฟอร์มเดียว ยังต้องสนใจเรื่อง fan-out นี้ไหม

ถ้ายิงแอดแพลตฟอร์มเดียวจริง ๆ ไม่จำเป็นต้องออกแบบซับซ้อนขนาดนี้ แต่ถ้ามีแผนจะขยายไปแพลตฟอร์มอื่นในอนาคต การวางโครงสร้างที่แยก event ตามปลายทางไว้ตั้งแต่ต้นจะทำให้ขยายระบบทีหลังง่ายกว่ามาก

จะรู้ได้ยังไงว่าปัญหาที่ event หายไปเกิดจาก fan-out พัง ไม่ใช่ปัญหาที่ต้นทาง

ต้องมี log ที่แยกชัดว่า event ถูกสร้างขึ้นจริงกี่ตัวตั้งแต่ต้นทาง แล้วแต่ละตัวถูกส่งไปปลายทางไหนสำเร็จหรือไม่สำเร็จ ถ้าจำนวน event ที่สร้างตรงกับที่ควรจะเป็นแต่ไปไม่ถึงปลายทาง แสดงว่าปัญหาอยู่ที่ชั้น fan-out ไม่ใช่ต้นทาง

ลองตรวจด้วยตัวเอง

Meta Ads → LINE Checker

ตรวจว่า fbclid, Pixel และเส้นทางเข้า LINE ของคุณพร้อมให้ Meta วัด Conversion หรือยัง

ตรวจความพร้อมฟรี

ใช้ฟรี ไม่ต้องสมัคร ไม่เก็บข้อมูลที่คุณกรอก

วัด Conversion LINE ทุกแบรนด์ รวมศูนย์ที่เดียว

องค์กรที่มีหลายแบรนด์ หลายสาขา หลาย LINE OA — linli รวม Tracking และส่ง Conversion กลับทุกแพลตฟอร์ม พร้อม Custom Limit เริ่มฟรี 14 วัน

ติดต่อทีม Sales

บทความที่เกี่ยวข้อง

ก่อนแคมเปญ 11.11 ทีมรู้อยู่แล้วว่าคนจะทักพร้อมกันเป็นพัน แต่ไม่เคยทดสอบระบบรับ Traffic ขนาดนั้นมาก่อนสักครั้ง วาง Load Test ล่วงหน้าก่อนวันจริง

ก่อนแคมเปญ 11.11 ทีมรู้อยู่แล้วว่าคนจะทักพร้อมกันเป็นพัน แต่ไม่เคยทดสอบระบบรับ Traffic ขนาดนั้นมาก่อนสักครั้ง วาง Load Test ล่วงหน้าก่อนวันจริง

ต่างจากแคมเปญที่พีคแบบไม่คาดคิด แคมเปญใหญ่อย่าง 11.11 หรือสิ้นปีรู้ล่วงหน้าอยู่แล้วว่าจะมี traffic สูง บทความนี้เจาะการวาง load test เชิงรุกก่อนวันจริง แทนการรอให้ระบบพังแล้วค่อยแก้ตามหลัง
ดีล B2B ปิดจริงหลังผ่าน 4 ขั้นตอนในสองเดือน แต่ Event เดียวตอนปิดดีลไม่พอให้ Bidding เรียนรู้ทัน ออกแบบ Progressive Value Update ระหว่างทาง

ดีล B2B ปิดจริงหลังผ่าน 4 ขั้นตอนในสองเดือน แต่ Event เดียวตอนปิดดีลไม่พอให้ Bidding เรียนรู้ทัน ออกแบบ Progressive Value Update ระหว่างทาง

ธุรกิจ B2B ที่วงจรขายยาวหลายสัปดาห์หรือหลายเดือน ส่ง conversion event เดียวตอนปิดดีลไม่พอให้ระบบ bidding เรียนรู้ทันเวลา บทความนี้เจาะการออกแบบ event ทยอยอัปเดตมูลค่าตามความคืบหน้าของดีล
ลูกค้าคลินิกความงามกลับมาซื้อซ้ำหลังหกเดือน จะให้เครดิตแอดครั้งแรกหรือครั้งที่พาเขากลับมาซื้อซ้ำ

ลูกค้าคลินิกความงามกลับมาซื้อซ้ำหลังหกเดือน จะให้เครดิตแอดครั้งแรกหรือครั้งที่พาเขากลับมาซื้อซ้ำ

แอดตัวแรกทำให้ลูกค้ารู้จักคลินิก แต่แอดรีทาร์เก็ตอีกหกเดือนต่อมาต่างหากที่พาเขากลับมาซื้อคอร์สรอบสอง คำถามคือใครควรได้เครดิตยอดขายก้อนที่สอง เคสที่ทำให้ต้องคิดเรื่อง attribution ข้ามรอบการซื้อ