ดึงปลั๊กออก กดปุ่มหยุดฉุกเฉิน (killswitch) สัญชาตญาณเหล่านี้ใช้ได้ผลเมื่อคุณยืนอยู่ข้างเครื่องจักรเพียงเครื่องเดียว แต่มันจะล้มเหลวเมื่อระบบ AI ของคุณกระจายอยู่บน 50 โหนด ใน 3 availability zones ทีมวิศวกรส่วนใหญ่เรียนรู้เรื่องนี้ด้วยวิธีที่ยากลำบาก พวกเขาอัปเดตฐานข้อมูลกลาง เปลี่ยนค่า boolean จาก true เป็น false และทึกทักเอาเองว่าระบบจะหยุดทำงาน แต่มันไม่หยุด ฐานข้อมูลดูเรียบร้อยดี แต่บริการ (service) ยังคงทำงานอยู่

ภาพลวงตาของสวิตช์เพียงตัวเดียว

ลองนึกภาพคอนโทรลเลอร์ที่บันทึกการเพิกถอน (revocation) ที่ epoch 12 มันเขียนการเปลี่ยนแปลงลงใน persistent store และถอนหายใจด้วยความโล่งอก ในขณะเดียวกัน Worker B กำลังทำงานโดยใช้สิทธิ์ (grant) ที่ถูกแคชไว้จาก epoch 11 โดยที่ worker ไม่ได้รับแจ้งเรื่องนี้เลย 30 วินาทีต่อมา มันเริ่มงาน model inference, สั่งรัน GPU cluster หรือเรียก external API ทั้งที่ audit log ระบุว่าสิทธิ์ถูกเพิกถอนไปแล้ว แต่การกระทำนั้นก็ยังเกิดขึ้นอยู่ดี

นี่คือช่องว่างระหว่าง persistence และ propagation การเขียนข้อมูลลงฐานข้อมูลไม่ใช่สถานะของระบบ (system state) มันเป็นเพียงแถวหนึ่งในตารางหนึ่ง และมี actor จำนวนมากในระบบของคุณที่ไม่เคยไปตรวจสอบ (poll) ตารางนั้นในจังหวะที่จำเป็นต้องใช้พอดี หากคุณปฏิบัติกับการหยุดฉุกเฉินเหมือนสวิตช์ไฟ คุณจะพบว่าความมืดไม่เคยมาเยือนในบางมุมของห้อง

ความจริงอันโหดร้ายของระบบแบบกระจาย (Distributed Systems)

คุณต้องออกแบบเพื่อรองรับความล้มเหลว (design for failure) ไม่ใช่แค่ความล้มเหลวเป็นครั้งคราว แต่เป็นความล้มเหลวที่เกิดขึ้นตลอดเวลา ยุ่งเหยิง และเป็นอิสระต่อกัน Worker รีบูตกลางคัน Queue consumer ล้าหลังไปหลายนาที Authorization service ส่งข้อมูลที่ล้าสมัย (stale data) เพราะ replica ตัวหนึ่งค้าง ข้อความซ้ำ ข้อความหาย หรือข้อความมาไม่เรียงลำดับ NTP daemon ของคุณเกิดการคลาดเคลื่อน (drift) และจู่ๆ โหนดหนึ่งก็คิดว่าตัวเองช้ากว่าโหนดอื่น 10 วินาที นาฬิกามีความผิดพลาด และคุณไม่สามารถเชื่อถือ wall time ในการจัดลำดับเหตุการณ์ข้ามขอบเขตได้

หากโปรโตคอลฉุกเฉินของคุณตั้งอยู่บนสมมติฐานว่าเครือข่ายเชื่อถือได้, การส่งข้อความเรียงลำดับได้ หรือนาฬิกาซิงค์กัน นั่นไม่ใช่โปรโตคอล แต่มันคือความเพ้อฝัน Worker, queue consumer และ authorization service ล้มเหลวแยกจากกัน กฎความปลอดภัยของคุณต้องยังคงใช้ได้ แม้ในขณะที่โครงสร้างพื้นฐาน (infrastructure) ดูเหมือนจะจงใจขัดขวางคุณก็ตาม

กฎ 5 ข้อที่ใช้งานได้จริง

ความปลอดภัยมาจาก invariant ที่อยู่รอดท่ามกลางความวุ่นวาย นี่คือกฎที่จะช่วยไม่ให้การเพิกถอนสิทธิ์กลายเป็นเพียงเรื่องเพ้อฝัน

ห้ามเริ่มการทำงานใดๆ ด้วย grant epoch ที่ต่ำกว่า revocation epoch
นี่คือเกราะป้องกันหลักของคุณ การอนุญาตสิทธิ์ (permission grant) ทุกครั้งจะมีเลข epoch และการเพิกถอน (revocation) ทุกครั้งจะมีเลขที่ใหม่กว่า ก่อนที่ worker จะเริ่มทำงาน มันต้องเปรียบเทียบตัวเลขเหล่านี้ หาก grant ของ worker เก่ากว่า revocation ล่าสุดที่มันเห็น worker ต้องหยุดทำงาน Epoch ให้ logical clock ที่ไม่ขึ้นกับ system clock Worker ที่ถือ epoch 11 ต้องปฏิเสธการเริ่มงานทันทีเมื่อรู้ว่า epoch 12 ได้เพิกถอนอำนาจนั้นไปแล้ว

สิทธิ์ที่ถูกแคชไว้ (cached grants) ต้องหมดอายุภายในระยะเวลาที่กำหนด
สิทธิ์ต้องไม่คงอยู่ในหน่วยความจำตลอดไป Worker จำเป็นต้องตรวจสอบความถูกต้องใหม่ (revalidate) หรือสละสิทธิ์หลังจากผ่านช่วงเวลาที่กำหนด หากไม่มีสิ่งนี้ โหนดที่ออฟไลน์ไปอาจกลับมาทำงานในอีกหลายวันหรือหลายสัปดาห์ต่อมา และทำงานโดยใช้สิทธิ์ที่ล้าสมัย (fossilized grant) จงกำหนด lease และบังคับใช้อย่างเคร่งครัด เวลาจะกลายเป็นทีมทำความสะอาดอัตโนมัติของคุณ

การรีสตาร์ทระบบต้องไม่ทำให้ epoch ที่บันทึกไว้ลดต่ำลง
ความคงทน (persistence) เป็นเรื่องสำคัญ หากคอนโทรลเลอร์พังและรีสตาร์ท มันต้องกู้คืน epoch ที่สูงที่สุดที่เคยออกไป การย้อนกลับไปใช้ epoch ที่เก่ากว่าจะทำให้สิทธิ์ที่ถูกเพิกถอนไปแล้วฟื้นคืนชีพขึ้นมา ราวกับว่าการหยุดฉุกเฉินไม่เคยเกิดขึ้น จงบันทึก epoch ให้คงทนก่อนที่จะประกาศใช้ (broadcast) โดยใช้ write-ahead log, confirmed fsync หรือ replicated consensus group ประวัติศาสตร์ต้องเดินไปข้างหน้าเท่านั้น

การเพิกถอนที่ซ้ำซ้อน