pg_notify และ Event Outbox
ภาพรวม
ระบบใช้ PostgreSQL เป็น event bus ชั้นแรก โดย trigger บนตารางสำคัญจะเรียก pg_notify(...)
และฝั่ง worker-go มี pg-listener คอยฟังช่องสัญญาณเหล่านั้นแบบเรียลไทม์
ข้อจำกัดสำคัญคือ pg_notify ไม่มีการรับประกันการส่ง หาก listener หลุดการเชื่อมต่อในจังหวะที่
event ถูกยิงออกมา event นั้นจะหายไปทันทีโดยไม่มีร่องรอย ระบบจึงเพิ่มตาราง event_outbox
ให้ทุก trigger เขียนสำเนาของ event ลงฐานข้อมูลควบคู่กันไปด้วย (รูปแบบ transactional outbox)
listener ที่เพิ่งกลับมาออนไลน์จึงสามารถไล่อ่านย้อนหลัง (catch up) จากตารางนี้ได้
โครงสร้างข้อมูลหลัก
event_outbox
ตารางนี้ถูกสร้างผ่าน manual SQL เท่านั้น ไม่มีนิยามอยู่ใน Prisma
idBIGSERIAL,channelVARCHAR(100),payloadJSONBcreated_at,processed(ค่าเริ่มต้น false),processed_at- ใช้ partial index สองตัวที่แยกหน้าที่กันชัดเจน คือ
(created_at) WHERE processed = FALSEสำหรับการ catch up และ(processed_at) WHERE processed = TRUEสำหรับงานลบข้อมูลเก่า
ช่องสัญญาณและ trigger ที่ยิง
| ช่องสัญญาณ | ตารางต้นทาง | Trigger | ฟังก์ชัน |
|---|---|---|---|
attribute_changed | line_user | trg_line_user_attribute_change (AFTER UPDATE) | notify_attribute_change() |
form_submitted | form_submission | form_submission_notify (AFTER INSERT/UPDATE) | notify_form_submitted() |
campaign_click | tracking_log | tracking_log_campaign_click_notify (AFTER INSERT) | notify_campaign_click() |
friend_track_event_inserted | friend_track_event | friend_track_event_insert_notify (AFTER INSERT) | notify_friend_track_event_insert() |
booking_event | appointment.booking | trg_booking_event_insert / trg_booking_event_update | appointment.notify_booking_event() |
พฤติกรรมที่ควรทราบ
notify_attribute_change()ตรวจจับการเปลี่ยนแปลงทั้งในคอลัมน์คงที่ (display_name,firstname,lastname,email,mobile_no,language,follow) และคีย์ภายในcustom_attributeโดยส่งชื่อคีย์ในรูปแบบcustom.<ชื่อคีย์>- มีสวิตช์ปิด trigger ชั่วคราวในระดับ transaction ผ่าน
current_setting('app.skip_triggers', true) = 'true'ใช้ระหว่างการนำเข้าข้อมูลจำนวนมาก เพื่อไม่ให้เกิด event นับหมื่นครั้ง วิธีนี้ปลอดภัยกับระบบ multi-tenant เพราะเป็นค่าระดับ session หรือ transaction ไม่ใช่ค่าระดับฐานข้อมูล appointment.notify_booking_event()เขียน outbox แบบ best-effort คือดักจับundefined_tableแล้วข้ามไปเงียบ ๆ หากยังไม่มีตาราง outbox ในฐานข้อมูลนั้น
ไฟล์ที่เกี่ยวข้อง
manual-sql/5.sql— สร้างevent_outboxเขียนทับทุกฟังก์ชัน notify ให้บันทึกลง outbox และเพิ่ม triggerfriend_track_event_insert_notifyที่หายไป (แก้ไขเมื่อ 24 ก.ค. 2026)manual-sql/1.notify_attribute_change.sql,manual-sql/3.bank.sql— พัฒนาการของ trigger ฝั่ง attributemanual-sql/2.notify_attribute_change.sql—notify_form_submittedmanual-sql/4.sql—notify_campaign_clickapps/appointment/migrations/004_add_booking_event_trigger.sql— ช่องbooking_eventmanual-sql/0.select.sql— คำสั่งตรวจสอบ trigger ทั้งหมดจากinformation_schema.triggersschema-dumps/2026-07-24/triggers-summary.txt,schema-dumps/2026-07-24/notify-functions.txt— สรุปสถานะจริงบน preprodschema-dumps/2026-07-24/schema.sql:1681—event_outbox
จุดเชื่อมต่อกับ Service อื่น
- worker-go ใช้ pg-listener เชื่อมต่อกับทั้ง 5 ช่องสัญญาณ แล้วส่งต่อให้ กฎทริกเกอร์ และ ผังงาน (Workflow) ประมวลผล
- ตารางต้นทางกระจายอยู่ใน เพื่อน LINE, ตัวสร้างฟอร์ม, ระบบติดตามลิงก์, Friend Track และ ระบบจองนัดหมาย
- ข้อควรระวัง: trigger ทั้งหมดนี้อยู่นอกขอบเขตของ Prisma คำสั่ง
prisma migrateมองไม่เห็น จึงต้อง apply ด้วย psql เองและตรวจสอบผลด้วยmanual-sql/0.select.sqlทุกครั้ง