AI agent ของคุณที่ Elevare Digital หยุดทำงาน (idle) เนื่องจากนโยบาย Row-Level Security (RLS) ของ PostgreSQL ที่เพิ่มเข้ามาใหม่ได้กรองแถวของงาน (job row) ออกทั้งหมด ทำให้คิวดูเหมือนว่างเปล่า ความผิดพลาดนี้ไม่ถูกสังเกตเห็นจนกระทั่งงานเริ่มสะสมตัว ส่งผลให้ทีมต้องออกแบบวิธีการที่ orchestrator ใช้ตรวจจับคิวว่างใหม่

จุดบอดที่ถูกซ่อนไว้

ARIA ระบบ AI อัตโนมัติของ Elevare ทำการดึงข้อมูล (poll) จากตาราง PostgreSQL เพื่อดูงานที่ค้างอยู่ การคิวรี (query) สำเร็จและคืนค่าเป็นศูนย์แถว ทำให้ agent เข้าสู่โหมดพัก (idle) ทั้งที่ในความเป็นจริงตารางนั้นเต็มไปด้วยงาน นโยบาย RLS ได้จำกัดสิทธิ์ SELECT ไว้เฉพาะผู้ใช้บางกลุ่มเท่านั้น เนื่องจาก orchestrator เชื่อมต่อด้วย service role ที่ไม่มีสิทธิ์ bypass ทำให้ฐานข้อมูลลบทุกแถวออกจากชุดผลลัพธ์อย่างเงียบๆ PostgreSQL ปฏิบัติต่อการอ่านที่ถูกกรองออกเหมือนกับตารางที่ว่างเปล่า ดังนั้นจึงไม่มีข้อผิดพลาด (error) คำเตือน (warning) หรือรหัสความล้มเหลว (failure code) ปรากฏขึ้น สัญญาณ heartbeat ที่ปกติจาก agent ที่อยู่ในโหมด idle ไม่ได้บ่งบอกเลยว่ามีบางอย่างผิดปกติ

RLS เปลี่ยนคิวที่เต็มให้กลายเป็นความเงียบได้อย่างไร

RLS จะเพิ่ม predicate เข้าไปในแต่ละแถวระหว่างการทำ SELECT หาก predicate เป็นเท็จ แถวนั้นจะหายไปจากผลลัพธ์ ฝั่ง client จะเห็นเฉพาะแถวที่ตรงตามนโยบายเท่านั้น และจะไม่รู้เลยว่ามีแถวถูกซ่อนอยู่ สำหรับ queue worker ชุดผลลัพธ์ที่ว่างเปล่าดูเหมือนกับคิวที่ว่างจริงๆ ทุกประการ orchestrator จึงสันนิษฐานว่า “ไม่มีแถว = ไม่มีงาน” และเข้าสู่ลูป idle ในขณะที่งานสะสมอยู่เบื้องหลัง

ทีมค้นพบว่านโยบายที่ตั้งใจจะจำกัดขอบเขตการอ่านข้อมูลเฉพาะผู้ใช้แต่ละราย กลับไปครอบคลุมถึงตัว service role เองโดยไม่ตั้งใจ เนื่องจาก role ดังกล่าวขาดคุณสมบัติพิเศษ “bypass RLS” นโยบายจึงถูกนำไปใช้กับทุก query ที่ orchestrator ส่งออกไป สิ่งนี้แสดงให้เห็นถึงการแลกเปลี่ยน (trade-off) ระหว่างความปลอดภัยและการสังเกตการณ์ (security-vs-observability) ที่คลาสสิก: RLS ช่วยปกป้องข้อมูลจากผู้ใช้ที่ไม่ได้รับอนุญาต แต่ในขณะเดียวกันก็ลบสัญญาณความล้มเหลวที่มีประโยชน์สำหรับส่วนประกอบของระบบที่ต้องพึ่งพาความสามารถในการมองเห็นข้อมูล (visibility)

รูปแบบการตรวจสอบแบบ canary

เพื่อลดการพึ่งพาผลลัพธ์ว่างเปล่าที่เงียบเชียบ Elevare จึงเพิ่มการตรวจสอบแบบ “canary” ขึ้นมา ขั้นตอนใหม่คือ:

  1. คิวรีตาราง pending-jobs
  2. หากมีแถวถูกส่งกลับมา ให้ประมวลผลตามปกติ
  3. หากผลลัพธ์ว่างเปล่า ให้ส่ง query ที่สองไปยังแถว canary โดยเฉพาะซึ่งต้องมีอยู่เสมอ
  4. หาก query canary คืนค่าแถวที่คาดหวัง แสดงว่าคิวว่างจริงๆ ให้บันทึก idle heartbeat
  5. หาก query canary ไม่คืนค่าอะไรเลย แสดงว่า agent กำลังตาบอด (blind) ให้แจ้งเตือนทันที

ตอนนี้ orchestrator สามารถแยกแยะสถานะได้สามแบบ:

  • พบงาน (Jobs found) – ประมวลผลตามปกติ
  • ไม่มีงาน, canary ปกติ (No jobs, canary OK) – ช่วงเวลา idle ที่แท้จริง
  • ไม่มีงาน, canary ล้มเหลว (No jobs, canary failed) – ถูก RLS บล็อกไว้ ให้ส่งสัญญาณแจ้งเตือน

ตาราง canary คือแถวเดียวที่ไม่เคยเปลี่ยนแปลง การตั้งค่าใช้เวลาเพียงประมาณหนึ่งชั่วโมง แต่สามารถกำจัดความล้มเหลวแบบเงียบเชียบ (silent failures) ได้ทั้งประเภท

สิ่งที่ทีมควรทำ

หากคุณรัน queue worker บน PostgreSQL หรือบริการที่โฮสต์บน PostgreSQL (เช่น Supabase) ให้ปฏิบัติตามขั้นตอนเหล่านี้:

  • ใช้ service-role credential ที่มี flag “bypass RLS” วิธีนี้จะช่วยให้ส่วนประกอบของระบบมองเห็นแถวทั้งหมดโดยไม่ขึ้นกับนโยบายระดับผู้ใช้
  • ตรวจสอบ (Audit) นโยบาย RLS เพื่อหาการขาดสิทธิ์ bypass สำหรับ service roles นโยบายที่ดูเหมือนถูกต้องสำหรับผู้ใช้ทั่วไปอาจไปดักจับบริการภายในโดยไม่ตั้งใจ
  • เพิ่มตาราง canary (หรือแถวที่คงอยู่เสมอในรูปแบบอื่น) และรวมการตรวจสอบ canary เข้ากับตรรกะการ idle ของ worker การคิวรีเพิ่มเติมนั้นมีต้นทุนต่ำและเป็นตาข่ายรองรับความปลอดภัยที่ชัดเจน

การแลกเปลี่ยน

RLS ยังคงเป็นเครื่องมือที่ทรงพลังในการบังคับใช้การเข้าถึงข้อมูลที่ละเอียด (fine-grained) ช่วยป้องกันข้อมูลรั่วไหลโดยไม่ตั้งใจและรองรับสถาปัตยกรรมแบบ multi-tenant โดยไม่ต้องใส่ฟิลเตอร์ระดับแอปพลิเคชันไว้ทั่วทั้ง codebase ข้อเสียคือมันสามารถซ่อนความล้มเหลวจากส่วนประกอบที่คาดหวังว่าสัญญาณ “ไม่มีแถว” หมายถึง “ไม่มีอะไรต้องทำ” รูปแบบ canary ไม่ได้ทำให้ RLS อ่อนแอลง แต่มันเป็นการเพิ่มขั้นตอนการตรวจสอบที่เบาบาง (lightweight) เพื่อคืนความสามารถในการสังเกตการณ์ (observability) กลับมา

บทสรุป

นโยบาย RLS ที่ถูกซ่อนไว้สามารถเปลี่ยนคิวที่ยุ่งเหยิงให้กลายเป็นทางตันที่เงียบเชียบ ทิ้งให้ AI agent อยู่ในสถานะ idle ในขณะที่งานค้างสะสม การมอบสิทธิ์ bypass ที่เหมาะสมให้กับ service roles และจับคู่การอ่านคิวที่ว่างเปล่าทุกครั้งด้วยการตรวจสอบ canary จะช่วยให้ทีมสามารถรักษาการทำงานของ worker อัตโนมัติให้ถูกต้องและหลีกเลี่ยงจุดบอดที่มีราคาแพงได้