Redis Cache ของ Webhook Config และ Agent Mode
ภาพรวม
บนเส้นทางประมวลผล LINE event นั้น webhook service ไม่แตะ PostgreSQL เลย การตัดสินใจ routing ทั้งหมดมาจาก Redis เพียง 2 key เท่านั้น การออกแบบเช่นนี้เป็นเพราะ endpoint ต้องตอบกลับ LINE ให้เร็วที่สุด และเป็นจุดที่รับ traffic สูงสุดในระบบ
| Key (ก่อนเติม prefix) | ชนิด | ผู้เขียน | ผู้อ่าน |
|---|---|---|---|
webhook_config:{webhookId} | hash | cms-api (ตอนแก้ config ของ OA) | webhook-go (อ่านอย่างเดียว) |
agent_mode:{lineOaId}:{userId} | hash | worker-go (ตอนเริ่ม/จบ session) และ webhook-go (เฉพาะ lastActivity) | webhook-go |
ทุก key จะถูกเติม prefix REDIS_NAMESPACE (ค่าเริ่มต้นคือ LINE_MANAGEMENT:) โดย
internal/cache.Cache ให้อัตโนมัติ กล่าวคือโค้ดเรียกด้วยชื่อสั้น แต่ key จริงบน Redis คือ
LINE_MANAGEMENT:webhook_config:xxxx
Business Flow
ฟิลด์ใน webhook_config ที่โค้ดอ่านจริง
| ฟิลด์ | การใช้งาน |
|---|---|
lineOaId | แปลงเป็น int64 แล้วใส่ลง payload ของทุกคิว หาก parse ไม่ได้จะได้ค่า 0 โดยไม่ crash |
forwardWebhookUrl | ถ้าไม่ว่างจะ forward body ดิบไปยัง URL นี้ |
mboxEnabled | ค่า "1" หมายถึงเปิดระบบคุยกับเจ้าหน้าที่ |
mboxBaseUrl, mboxApiToken, mboxAccountId, mboxInboxId | credential ของ Chatwoot ที่แนบไปกับ payload ส่งให้ worker |
mboxLineWebhookUrl | URL ปลายทางที่ forward ข้อความไปขณะอยู่ใน agent mode |
mboxTimeoutMinutes, mboxWarningMinutes | ระยะเวลา timeout และการเตือน (worker เป็นผู้ใช้งาน) |
mboxGreetingMessage, mboxWarningMessage, mboxEndMessage, mboxTimeoutMessage | ข้อความระบบที่ส่งให้ผู้ใช้ |
mboxExitKeywords, mboxAgentKeywords | keyword ที่ใช้ trigger การเข้าและออก agent mode |
mboxDepartmentPickerEnabled | ค่า "1" หมายถึงให้ผู้ใช้เลือกแผนกก่อน handoff |
mboxDepartmentPickerHeaderText, mboxDepartmentPickerGeneralLabel, mboxDepartments | ข้อมูลปุ่มเลือกแผนก โดย mboxDepartments เก็บเป็น JSON string |
ค่าทุกตัวเป็น string เนื่องจากเป็น Redis hash โค้ดจึงเปรียบเทียบตรง ๆ กับ "1"
ไม่ใช่ค่า boolean
พฤติกรรมเมื่อ cache miss
เมื่อ HGETALL คืน map ว่าง ระบบจะถือว่าไม่มี config แล้ว publish payload ทั้งก้อน
ลงคิว line_webhook แล้วจบการทำงาน ไม่ถือเป็น error และไม่ fallback ไปอ่านฐานข้อมูล
เนื่องจาก worker-go จะเป็นผู้ค้น config จากฐานข้อมูลเอง
พฤติกรรมเมื่อ Redis error
ระบบจะ log ข้อความ processLine: HGETALL webhook_config failed แล้ว return ทันที
ข้อความนั้นจะหายไปโดยไม่มี retry และไม่มี dead letter queue
เพราะ response 200 ถูกส่งกลับ LINE ไปก่อนหน้าแล้ว
จุดนี้เป็นข้อมูลสำคัญเมื่อต้องสืบสวนกรณีข้อความหาย
agent_mode และการนับ activity
- การมี key อยู่ (hash ไม่ว่าง) หมายความว่าผู้ใช้คนนี้กำลังคุยกับเจ้าหน้าที่ บอทจึงต้องเงียบ
- webhook-go เขียนเฉพาะฟิลด์
lastActivityเป็น epoch หน่วยมิลลิวินาทีในรูป string ทุกครั้งที่มีข้อความเข้ามา และ ไม่ตั้ง TTL เพราะการหมดอายุ session เป็นหน้าที่ของ worker-go - การสร้างและลบ key นี้ทั้งหมดอยู่ที่ worker-go โดย webhook-go เพียงแตะ
lastActivityเท่านั้น
ไฟล์และฟังก์ชันหลัก
| ไฟล์ | ฟังก์ชัน |
|---|---|
internal/cache/cache.go | Cache.HGetAll, Cache.HSet, Cache.Get/Set/Del/DelWildcard/Keys/HGet/IncrBy/Expire, setKeyName (เติม namespace) |
internal/line/service.go | เรียก HGetAll สำหรับ webhook_config และ agent_mode และมี buildMboxConfig สำหรับ map hash เป็น struct |
internal/platform/redisx/redisx.go | Manager แบบหลาย instance โดย instance ที่ใช้ชื่อว่า "redis" |
internal/config/api_load.go | ค่า REDIS_URL, REDIS_NAMESPACE (ค่าเริ่มต้น LINE_MANAGEMENT) และ REDIS_TTL |
cmd/api/main.go | สร้าง cache.New(rdb, namespace, ttl) แล้วใส่ลงใน server.Deps.Cache |
จุดเชื่อมต่อกับ Service อื่น
- cms-api — โมดูล
line-oa-managementและauto-responseเป็นผู้ warm cache นี้ หาก cache หายหรือไม่ถูก sync webhook จะทำงานในโหมด default คือส่งเข้าคิวอย่างเดียว - worker-go — เป็นเจ้าของ lifecycle ของ
agent_mode:*ทั้งการสร้างเมื่อ handoff สำเร็จ และการลบเมื่อ exit หรือ timeout อีกทั้งเป็น consumer ของคิวline_webhook - Redis instance เดียวกัน ถูกใช้เป็น backend ของ rate limiter ด้วย แต่แยก key space
ต่างหากที่
throttle:app:* - หากไม่ได้ตั้งค่า Redis (
REDIS_URLว่าง) ตัวdeps.Cacheจะเป็น nil และ handler จะไม่ spawn pipeline เลย โดย endpoint ยังคงตอบ200แต่จะไม่มีการประมวลผลใด ๆ เกิดขึ้น