สิ่งที่อยากทำนั้นง่ายมาก
เขียนบทความใหม่ แล้วเผยแพร่ให้เร็วที่สุด
แค่นั้นเอง
แต่ไม่รู้ตัว ก่อนจะเผยแพร่ก็มีการประชุมเกิดขึ้นเสียแล้ว
รอบนี้ใครรับผิดชอบ? Workers หรือ Actions? งบยังเหลือไหม? ตัวล็อกครั้งก่อนยังใช้อยู่หรือเปล่า? ป้ายกำกับนี้เป็นของใคร? ลองใหม่ไปกี่ครั้งแล้ว?
บทความกำลังรออยู่
ระบบก็กำลังรออยู่
ทุกส่วนคิดว่า "งานนี้ไม่ใช่ของฉัน"
ถ้าเป็นบริษัท เรียกทุกคนเข้าห้องประชุมได้ แต่ระบบแบบกระจายจะหยุดไปเงียบ ๆ
นี่คือนรก Live Schedule
1. อยากเผยแพร่ทันที แต่กลับกลายเป็นเลือกตั้งผู้รับผิดชอบ
เมื่อเราอยากให้ขั้นตอนเผยแพร่แข็งแรงขึ้น ก็มักจะเพิ่มตัวสำรองไปเรื่อย ๆ
ถ้า Workers หยุด ก็ส่งต่อให้ Actions ถ้างบของ Actions ตึง ก็ส่งกลับให้ Workers ใส่ตัวล็อก เพื่อไม่ให้ทำงานเดียวกันสองรอบ ใส่ป้ายกำกับ เพื่อไม่ให้สองฝั่งแตะงานเดียวกัน นับจำนวนครั้งที่ลองใหม่ ใส่ผู้เฝ้าดูเข้าไปอีก
มองทีละอย่าง ทุกอย่างถูกต้องหมด
ปัญหาเกิดตอนที่เอาทุกอย่างมาต่อกันเป็นแถว
ก่อนจะถามว่า "บทความนี้เผยแพร่ได้ไหม" ต้องตอบคำถามที่ใหญ่กว่ามากก่อนว่า "ใครมีสิทธิ์เผยแพร่"
สิ่งที่อันตรายที่สุดคือ เหลือแต่ป้ายผู้รับผิดชอบ
งานฝั่ง Workers หยุดไปแล้ว แต่บันทึกยังเขียนว่า "Workers รับผิดชอบ" พอ Actions ตื่นขึ้นมา อ่านเจอว่า "Workers รับผิดชอบ" ก็ข้ามงานไป
ไม่มีส่วนไหนพัง แต่ก็ไม่มีใครทำงาน
เป็นแบบระบบกระจายของ "ผู้รับผิดชอบไม่อยู่ที่โต๊ะ"
2. ปัญหาจริงคือการประสานงาน ไม่ใช่ความสามารถในการรัน
ในการรันครั้งหนึ่ง ฝั่ง Actions ยังมีกำลังและงบเหลือ
แต่งานหลักก็ไม่ได้รัน
เหตุผลง่ายมาก คือการตรวจสรุปว่า "งานนี้เป็นของ Workers"
ตรงนี้สำคัญ
ไม่ใช่ว่า CPU ไม่พอ ไม่ใช่ว่า API ตายหมด ไม่ใช่ว่า Actions เริ่มไม่ได้
ปัญหาคือการประสานงาน ไม่ใช่การรัน
สิ่งที่ขวางอยู่คือการตัดสินว่าใครทำ ไม่ใช่แรงที่จะทำ
เมื่อเป็นแบบนี้ แก้บั๊กทีละจุดก็ยังจบยาก
แก้ปริมาณข้อมูลที่ดึง แก้ความล้มเหลวตอนบันทึกลง main แก้การลองใหม่ แก้การสลับผู้รับผิดชอบ แล้วป้ายกำกับเก่าก็ค้างอยู่ ต่อมาตัวล็อกเก่าก็ค้างอยู่ ผู้เฝ้าดูเห็นปัญหา แต่ไม่มีสิทธิ์เผยแพร่
ดูเหมือนเกมตีตัวตุ่น แต่ตัวตุ่นทุกตัวโผล่จากรูเดียวกัน
ชั้นควบคุมฉลาดเกินไป
3. schedule คือนาฬิกา ไม่ใช่รายการงาน
schedule ของ GitHub Actions สะดวกดี แต่เอกสารทางการก็เขียนว่าอาจล่าช้าเมื่อโหลดสูง และถ้าโหลดสูงมาก งานที่รอคิวบางส่วนอาจหายไป
ดังนั้น schedule เหมาะกับ "ปลุกฉันราว ๆ เวลานี้"
แต่ไม่เหมาะกับ "ต้องมีใครสักคนมารับต้นฉบับนี้และถือไว้จนกว่าจะสำเร็จ"
นาฬิกาดังได้
แต่นาฬิกาไม่ได้ถือต้นฉบับให้เรา
ส่วนคิวข้อความจะเก็บตัวงานเอาไว้ และรับมือกับความล้มเหลวได้ ทั้งลองใหม่ ทั้งหน่วงเวลา และ "คิวงานที่ล้มเหลว" (dead-letter queue) ซึ่งเป็นที่แยกไว้สำหรับงานที่ล้มซ้ำ ๆ
ตรงนี้ไม่ได้บอกว่า "ต้องเพิ่มบริการใหม่"
แต่บอกว่าให้แบ่งหน้าที่ให้ชัด
- schedule = ตัวปลุกตามเวลา
- รายการงานค้าง = บันทึกหลักของงานที่ยังไม่เสร็จ
- ผู้ลงมือทำ = ส่วนที่ทำงานจริง
- ตัวล็อกช่วงสั้น = กุญแจกันสองฝั่งทำพร้อมกัน
- ลองใหม่และคิวงานที่ล้มเหลว = ที่ไปของงานที่ล้มเหลว
ถ้าให้นาฬิกาทำหน้าที่ทั้งฝ่ายบุคคล โกดัง และแผนกของหาย นาฬิกาก็คงแย่
4. ใช้ "อะไรยังไม่เสร็จ" เป็นหลัก ไม่ใช่ "ใครเป็นเจ้าของ"
ในการแบ่งงานที่ซับซ้อน ศูนย์กลางของบันทึกมักกลายเป็น "ผู้รับผิดชอบ"
แต่สิ่งที่อยากรู้จริง ๆ ในขั้นตอนเผยแพร่คือ
บทความนี้เผยแพร่แล้วหรือยัง
เกือบมีแค่นี้
ดังนั้นใช้รายการงานค้างเป็นบันทึกหลักก็พอ
ต้นฉบับใหม่เข้ามา ก็ใส่ในรายการงานค้าง ตอนเริ่มเผยแพร่ ใส่ตัวล็อกช่วงสั้น ถ้าสำเร็จ ก็ทำเครื่องหมายว่าเผยแพร่แล้ว ถ้าล้มเหลว ก็ส่งกลับไปที่รายการงานค้าง ถ้าล้มเหลวครบจำนวนที่กำหนด ก็ย้ายไปที่คิวงานที่ล้มเหลวและส่งคำเตือน
"งานของ Workers" หรือ "งานของ Actions" เป็นแค่ข้อมูลเสริม อย่าให้กลายเป็นสิทธิ์ยับยั้งถาวร
ต่อให้เขียนว่า "งานของ Workers" ถ้าไม่มีความคืบหน้าเกินเวลาที่กำหนด ผู้ลงมือทำอีกฝั่งก็รับช่วงต่อได้
แบบนี้ ป้ายผู้รับผิดชอบที่ตายไปแล้วก็จะจับงานเป็นตัวประกันไม่ได้
5. เส้นหลักเริ่มเมื่อมีเหตุการณ์ ส่วน schedule ไว้เก็บตกที่หลุดไป
ถ้าอยากเผยแพร่ทันที การรอรอบเวลาถัดไปก็ผิดจุดประสงค์
ปลุกผู้ลงมือทำทันทีที่ต้นฉบับใหม่มาถึง
ให้นั่นเป็นเส้นหลัก
แล้วให้ schedule เก็บงานค้างเก่าที่ยังเหลืออยู่หลังผ่านไปหลายนาทีถึงหลายสิบนาที
พูดอีกอย่างคือ
เหตุการณ์คือเส้นหลัก schedule คือประกัน
แบบนี้ในวันปกติจะเร็ว
ถ้าพลาดการแจ้งเตือน schedule ก็เก็บตามทีหลังได้
ถ้าให้ schedule เป็นเส้นหลัก ทันทีที่ล่าช้า การเผยแพร่ก็ล่าช้าตาม และถ้ามีการตรวจว่าใครรับผิดชอบเข้ามาอีก ก็จะเกิด "ตื่นแล้วแต่ไม่ทำอะไร"
ถึงเวลาแล้ว
ผ่านการตรวจแล้ว
แล้วบอกว่า "วันนี้ไม่ใช่หน้าที่ฉัน" แล้วกลับบ้าน
แล้วมาทำไม
6. ตัวสำรองคือ "เก็บงานเก่าที่ยังไม่เสร็จ" ไม่ใช่ "เปลี่ยนผู้รับผิดชอบ"
กับดักที่พบบ่อยในการออกแบบตัวสำรองคือการโอนงานจาก A ไป B อย่างเป็นทางการ
การโอนต้องอัปเดตบันทึก
ถ้าอัปเดตนั้นล้มเหลว การโอนเองก็กลายเป็นปัญหา
ทำให้ง่ายกว่านี้ได้
ปกติ Workers ทำงาน ถ้าสำเร็จ ก็จบ ถ้าล้มเหลว ก็ยังเหลืออยู่ในรายการงานค้าง
ผู้ลงมือทำสำรอง เช่น Actions ตรวจเป็นระยะ ถ้า
- มีงานค้างอยู่
- เก่ากว่าเวลาที่กำหนด
- ไม่มีตัวล็อกที่ยังใช้ได้
- ตัวเองรันได้
ก็รับงานนั้นมาทำ
ไม่ต้องมี "ใบรับรองการโอน" จาก Workers
สิ่งที่ต้องดูคือความจริงที่ว่า งานยังไม่เสร็จ
ในวิธีนี้ การมีผลงานอยู่จริงสำคัญกว่าความเรียบร้อยของตารางผู้รับผิดชอบ
สำหรับระบบเผยแพร่บทความ นี่คือวิธีที่เป็นธรรมชาติ
7. ไม่ต้องทิ้ง Live Schedule แต่อย่าให้เป็นเครื่องช่วยหายใจ
Live Schedule เองไม่ได้เป็นปัญหา
มันเก่งมากเวลามีคนคอยดูอยู่
ปรับลำดับความสำคัญ ตรวจหาความผิดปกติ คิดแนวทางปรับปรุง ซ่อมให้เร็วขึ้น ใส่แผนใหม่เข้าไป
ปัญหาคือพอมันหยุด โรงงานบทความก็หยุดตามไปด้วย
Live ควรเป็น ตัวเร่งความเร็ว ไม่ใช่ จุดเดียวที่พังแล้วทุกอย่างหยุด
ต่อให้ไม่มี Live
ต้นฉบับใหม่ → รายการงานค้าง → ผู้ลงมือทำ → ตรวจ → เผยแพร่
ก็ต้องหมุนไปได้เอง
ถ้ามี Live ก็เร็วขึ้น
ถ้าไม่มี ก็ไม่ตาย
เหมือนที่เราไม่ควรต่อหัวใจของคนเข้ากับว่า "มีคนตอบ Slack หรือยัง"
8. เปลี่ยนเป้าหมายจาก "ระบบฉลาดสร้างเสร็จ" กลับเป็น "ใส่หนึ่งเรื่อง ได้ออกมาหนึ่งเรื่อง"
ระบบแบบนี้ปรับปรุงได้ไม่มีที่สิ้นสุด
แบ่งผู้รับผิดชอบอย่างเป็นธรรม ใช้แพ็กเกจฟรีให้คุ้มที่สุด กันของซ้ำ คาดการณ์งบ ลำดับความสำคัญ การเฝ้าดู การกู้คืนอัตโนมัติ การวิเคราะห์
สนุกทั้งนั้น
และถ้าทำทั้งหมด บทความก็ไม่ออก
เป้าหมายแรกจึงหยาบกว่านี้ได้
ใส่ต้นฉบับใหม่หนึ่งชิ้น แล้วเผยแพร่ได้หนึ่งบทความ โดยไม่ต้องทำอะไรพิเศษ
ถ้าล้มเหลว ก็ยังอยู่ในรายการงานค้าง ผู้ลงมือทำอีกฝั่งมารับไป เฉพาะงานที่ล้มซ้ำ ๆ เท่านั้นที่ส่งให้คน
เมื่อเส้นตรงเรียบ ๆ นี้นิ่งแล้ว ค่อยเอาการปรับให้ดีขึ้นกลับมา
เราแค่อยากเผยแพร่บทความใหม่ให้เร็ว แต่ไม่รู้ตัวก็กำลังสร้างอะไรที่คล้ายระบบหาฉันทามติแบบกระจายขึ้นมาเอง
บางวันก็เป็นแบบนั้น
แต่มีทางออก
ตัดความซับซ้อนทิ้ง
คุณค่าของระบบเผยแพร่ไม่ได้อยู่ที่ความสวยงามของการเลือกตั้งผู้รับผิดชอบ
แต่อยู่ที่บทความออกไปสู่ผู้อ่าน
สรุป
แก่นของนรก Live Schedule ไม่ใช่ตัว schedule เอง
แต่เป็นเพราะเราทำให้สถานะ "ใครเป็นผู้รับผิดชอบ" แข็งแรงเกินกว่าสถานะ "งานอะไรยังไม่เสร็จ"
ถ้าจะย้อนกลับไปออกแบบใหม่ เอาแค่นี้ก็พอ
- ใช้รายการงานค้างเป็นบันทึกหลักของงานที่ยังไม่เสร็จ
- เริ่มงานทันทีเมื่อมีเหตุการณ์
- ทำตัวล็อกให้สั้น
- ให้ผู้ลงมือทำสำรองเก็บงานค้างเก่า
- ใช้ schedule เป็นประกัน
- แยกงานที่ล้มเหลวครบจำนวนที่กำหนดไปไว้ที่คิวงานที่ล้มเหลว
- ถือว่า Live เป็นตัวเร่งที่จะมีหรือไม่มีก็ได้
โรงงานที่ไม่หยุด ดีกว่าโรงงานที่ฉลาด
เริ่มจากตรงนั้นก่อน
เอกสารอ้างอิง (4 รายการ)
- GitHub Docs: Events that trigger workflows — schedule https://docs.github.com/actions/reference/workflows-and-actions/events-that-trigger-workflows
- Cloudflare Workers Docs: Cron Triggers https://developers.cloudflare.com/workers/configuration/cron-triggers/
- Cloudflare Queues Docs: Overview https://developers.cloudflare.com/queues/
- Cloudflare Queues Docs: Dead Letter Queues https://developers.cloudflare.com/queues/configuration/dead-letter-queues/
