Journal เฝ้าความเงียบ

เฝ้าความเงียบ

ระบบเฝ้าระวังส่วนใหญ่ทำงานแบบรอสัญญาณ พอมีอะไรเกิดถึงค่อยตอบสนอง แต่ความล้มเหลวอีกแบบไม่ส่งสัญญาณเลย คือสิ่งที่ควรเกิดแล้วไม่เกิด · จะจับมันได้ต้องเลิกรอ event แล้วหันมาไล่ตรวจสถานะตามนาฬิกา เทียบกับสิ่งที่ควรเป็น (invariant) · desired ลบ observed เท่ากับส่วนต่างที่ต้องจัดการ · กลไกเดียวกับ control loop และ audit · บทที่ว่าด้วยการยุบความกลัวสารพัดให้เหลือลูปเดียว

งานตัวหนึ่งของผมเคยรันทุกเช้าตีห้า คอยดึงข้อมูลเข้าระบบเงียบ ๆ ทุกวัน จนเช้าวันหนึ่งมันก็หยุดทำงาน

ไม่มี error ไม่มี alert ไม่มี exception เพราะมันไม่ได้ทำอะไรผิดพลาดเลย มันแค่ ไม่ถูกเรียกให้ทำงาน

ผมมารู้เอาตอนวันที่สี่ ตอนที่ลูกค้าทักมา

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

error คือสิ่งที่ระบบ ทำ (แล้วผิด) ส่วนความเงียบคือสิ่งที่ระบบ ไม่ได้ทำ — และการไม่ทำ ย่อมไม่ปล่อยสัญญาณให้ใครดักได้

ความล้มเหลวสองชนิด

ถ้าเราแยกความล้มเหลวตามสัญญาณที่มันปล่อยออกมา จะเห็นรอยแยกชัดเจน

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

แบบที่สองคือ absence หรือความว่างเปล่า คือเหตุการณ์ที่ ควรเกิดแต่ไม่เกิด งานไม่ถูกเรียก บิลไม่ถูกจ่าย ข้อมูลไม่เข้า มันไม่มี log ไม่มี exception เพราะความว่างเปล่าไม่โยนอะไรออกมา มันคือสัญญาณที่ควรจะดัง แต่ไม่เคยดัง

รอยแยกอยู่ตรงนี้ — error ส่งเสียง ส่วน absence เงียบสนิท

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

รอสัญญาณ กับ ไปดูเอง

วิธีที่ระบบจะรับรู้ว่ามีอะไรเกิดขึ้น มีอยู่สองแบบ และความต่างของสองแบบนี้คือหัวใจของทั้งเรื่อง

แบบแรกคือ รอสัญญาณ (interrupt-driven) ระบบนั่งรอให้มีอะไรมาสะกิดว่าเกิดเหตุแล้ว วิธีนี้ประหยัดและตอบสนองไว แต่มันได้ยินเฉพาะสิ่งที่ ส่งเสียง เท่านั้น

แบบที่สองคือ ไปดูเอง (polling) ระบบเดินไปอ่านสถานะเองเป็นรอบ ๆ ตามนาฬิกา ไม่ว่าจะมีสัญญาณหรือไม่ก็ตาม

เครื่องมือเฝ้าระวังเกือบทั้งหมด ไม่ว่าจะเป็น alert, exception handler หรือแม้แต่ AI agent ที่รอ trigger ล้วนเป็นแบบ รอสัญญาณ ทั้งสิ้น และเพราะมันรอสัญญาณ มันจึงมองไม่เห็นเหตุการณ์ที่ไม่เกิดขึ้นโดยธรรมชาติ เพราะสิ่งที่ไม่เกิด ย่อมไม่มีสัญญาณมาปลุก ต่อให้ผู้ช่วยเก่งแค่ไหน ถ้ามันเอาแต่รอสัญญาณ มันก็ได้แต่นั่งรอเฉย ๆ นี่คือเหตุผลที่ตลอดสี่วันนั้น ไม่มีใครหรือเครื่องมือไหนขยับเลย

ทางเดียวที่จะเห็นความเงียบได้ คือเลิกรอ แล้วเปลี่ยนมา ไปดูเอง — เดินไปอ่านสถานะตามนาฬิกา

เฝ้าสิ่งที่ต้องจริงเสมอ ไม่ใช่เฝ้าเหตุการณ์

แต่การไปดูเองเฉย ๆ ยังไม่พอ พออ่านสถานะมาแล้ว เราต้องรู้ด้วยว่า “สถานะที่ถูกต้องควรเป็นยังไง” สิ่งนั้นเรียกว่า invariant — ประพจน์ที่ต้องเป็นจริงอยู่เสมอ

ยกตัวอย่างให้เห็นภาพ:

  • “ภายในตีห้าของทุกวัน ข้อมูลชุด D ต้องสด” คือ invariant หนึ่งข้อ
  • “ใบแจ้งหนี้ต้องถูกจ่ายก่อนครบกำหนด” ก็เป็น invariant
  • “ใบรับรอง (certificate) ต้องเหลืออายุมากกว่า 14 วัน” ก็เป็น invariant

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

กลไกของมัน — desired ลบ observed

พอเรามี invariant แล้ว กลไกที่เหลือก็ชัดเจน เป็นลูปสามจังหวะ:

  1. desired — สถานะที่ควรเป็น (มาจาก invariant) พร้อมกับ เวลา ว่าควรเป็นจริงเมื่อไหร่ (จากนาฬิกา)
  2. observed — เดินไปอ่านสถานะจริงมา
  3. delta — เอาสองอันมาเทียบกัน ว่าต่างกันตรงไหน

delta ที่ไม่ว่าง คือสิ่งที่เราต้องจัดการ และความเงียบจะโผล่มาเป็นรายการใน delta ทันที ไม่ใช่เพราะมันส่งเสียง แต่เพราะมันคือ “สิ่งที่ desired มี แต่ observed ไม่มี”

กล่อง desired สถานะที่ควรเป็นบวกนาฬิกา และกล่อง observed สถานะจริง ป้อนเข้าโหนด diff ตรงกลาง ได้ผลลัพธ์เป็นกล่อง delta สิ่งที่ต้องจัดการ รวมถึงความเงียบ · ด้านล่างกำกับว่า poll บวก reconcile บวกเก็บหลักฐาน เท่ากับ audit
เอา desired (พร้อมนาฬิกา) มาเทียบกับ observed จะได้ delta · ความเงียบโผล่มาเพราะมันคือสิ่งที่ desired มีแต่ observed ไม่มี

คนที่เคยทำระบบกระจาย (distributed system) จะคุ้นกับลูปนี้ทันที เพราะมันคือ reconciliation loop ตัวเดียวกับใน control loop คือเทียบสถานะที่ควรเป็นกับสถานะจริง แล้วลงมือจัดการเฉพาะส่วนที่ต่าง

และนาฬิกาคือชิ้นส่วนที่ขาดไม่ได้ เพราะมันคือสิ่งที่แยก “ยังไม่เกิด” (ยังไม่ถึงกำหนด ถือว่ายังปกติ) ออกจาก “ไม่เกิด” (เลยกำหนดแล้ว ถือว่าละเมิด) สองอย่างนี้ดูเหมือนกันบนเส้นเวลา ถ้าไม่มีนาฬิกา เราแยกมันไม่ออก

กลไกนี้มีชื่อ และมันเก่าแก่

ลูป “ไปดูเอง แล้วเทียบกับ invariant” นี้ไม่ใช่ของใหม่ ในวงวิศวกรรมมีชื่อเรียกรองรับครบ:

  • การเทียบ “ชุดที่ควรมี” กับ “ชุดที่มีจริง” เรียกว่า reconciliation
  • ตัวที่คอยจับ “สัญญาณที่ควรมาเป็นจังหวะ แต่จู่ ๆ หยุดมา” เรียกว่า heartbeat หรือ dead man’s switch
  • ทั้งหมดนี้อยู่ใต้ร่มของคำว่า audit คือการตรวจเชิงรุกว่า สิ่งที่ควรจริง เป็นจริงหรือเปล่า

ที่จริง to-do list ที่เราติ๊กในใจทุกวันก็คือ audit แบบมือเปล่านี่เอง และเส้นแบ่งระหว่าง checklist เล่น ๆ กับ audit จริง มีอยู่เส้นเดียว คือ เราติ๊กเพราะ เห็นกับตาว่ามันเกิดขึ้นจริง ไม่ใช่ติ๊กเพราะ จำได้ว่าน่าจะทำไปแล้ว

ผลลัพธ์ที่เชื่อได้ ต้องมีหลักฐาน

ผลลัพธ์ของลูปนี้ในแต่ละรอบ คือ คำยืนยัน (assertion) ว่า “ตอนนี้ invariant ทุกข้อเป็นจริง” หรือ “ข้อที่ k ถูกละเมิด”

แต่คำยืนยันจะมีค่าก็ต่อเมื่อมีหลักฐานหนุนหลังเสมอ เหมือนเวลา test ผ่าน เราเชื่อแถบสีเขียวเพราะมันตรวจจริงแล้วมีร่องรอยให้ดู ไม่ใช่เพราะมีไฟล์ไหนเขียนคำว่า pass เอาไว้เฉย ๆ

นั่นแปลว่า ข้อความ “✅ ทุกอย่างปกติ” ที่ไม่มีหลักฐานประกอบ ก็คือคำยืนยันที่ไม่เคยตรวจจริง เป็นการเดาที่แต่งตัวมาสวย ๆ ส่วน “✅” ที่เชื่อถือได้ ต้องตอบได้ว่า ตรวจ invariant ข้อไหนไปบ้าง และอ่านสถานะจริงมาจากไหน

คนคือด่านสุดท้าย ไม่ใช่ความล้มเหลว

ลูปนี้ตัดสิน delta ส่วนใหญ่ได้เอง (มีหรือไม่มี, เกินหรือไม่เกินกำหนด) เหลือแต่ delta ที่ต้องใช้วิจารณญาณตีความ ค่อยส่งข้ามไปให้คน

ดังนั้นจังหวะ “🔴 อันนี้ต้องให้คนเปิดดู” จึงไม่ใช่ความล้มเหลวของระบบ แต่เป็น escalation policy ที่เราตั้งใจออกแบบ คือวางคนไว้ตรงจุดที่ต้องใช้วิจารณญาณจริง ๆ เหมือนการปิดลูปที่ดี ไม่ใช่เอาคนไปคั่นอนุมัติทุกขั้นจนงานช้า ทั้งที่ไม่มีใครได้ใช้วิจารณญาณเลย

เริ่มจาก invariant เดียว

ข่าวดีคือ เราไม่ต้องมี framework อะไรเลย แค่เขียน invariant หนึ่งข้อ (“X ต้องเป็นจริงภายในเวลา T”) บวกกับวิธีไปอ่านสถานะของมันหนึ่งวิธี บวกกับนาฬิกาหนึ่งตัว เท่านี้ก็ได้ reconciler ตัวแรกแล้ว

งานตีห้าของผมไม่เคยต้องใช้ AI หรือ dashboard อะไรเลย มันต้องการแค่ประพจน์เดียว — “ข้อมูลต้องสดภายในตีห้า” — กับอะไรสักอย่างที่คอยไปดูมันทุกเช้า แล้วบอกเราเมื่อสถานะจริงไม่ตรงกับที่ควรเป็น

ความงามคือการยุบรูป

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

ตรวจ invariant บนนาฬิกา · เทียบสิ่งที่ควรเป็นกับสิ่งที่เป็นจริง · ส่งส่วนต่างให้คนดู

ความงามเชิงวิศวกรรมไม่ได้อยู่ที่ความซับซ้อน แต่อยู่ตรงที่ความกลัวสารพัดอย่างยุบลงเหลือลูปเล็ก ๆ ลูปเดียว ที่เราถือไว้ในหัวทั้งก้อนได้พร้อมกัน

คำถามที่ดีจึงไม่ใช่ “มี error อะไรไหม” แต่เป็น — invariant ของวันนี้ ถูกตรวจแล้วหรือยัง และตอนนี้ใครถือมันไว้แทนเรา?


แนวคิดในบทความนี้กำลังตกผลึกร่วมกับ studio axiom system Loop design และต่อยอดจาก Open Loop, Closed Loop — ถ้าหลักนี้ถูกใช้จริงในระยะยาว มันจะถูก lock เป็น axiom พร้อมเวอร์ชัน บทความนี้จะยังอยู่เป็นบันทึกว่าหลักนี้มาจากไหน