ระบบตายเงียบทั้งวันเพราะ API key หมดอายุ: ออกแบบยังไงให้ของพังแล้วส่งเสียง
ระบบที่พังแล้วฟ้องเสียงดัง แก้ได้ภายในไม่กี่นาที ระบบที่น่ากลัวกว่าคือระบบที่พังเงียบ ทุกหน้ายังเปิดได้ ทุกสถานะยังเขียว แต่งานที่มันควรทำไม่ได้ทำมาหลายชั่วโมงแล้ว
ผมเจอเคสนี้กับระบบดักงานฟรีแลนซ์ที่ใช้ AI คัดงานแล้วส่งแจ้งเตือนเข้า LINE ตอนสร้าง API key ผมตั้งวันหมดอายุไว้ 1 วันโดยไม่ได้ตั้งใจ วันถัดมา key หมดอายุ ระบบยังวิ่งตามรอบทุก 30 นาที ดึงงานมาครบทุกรอบ แต่ AI วิเคราะห์ไม่ได้ จึงไม่มีงานไหนผ่านไปถึงขั้นแจ้งเตือน ผ่านไป 1 วันเต็มกว่าจะมีคนรู้
ทำไมถึงไม่มีใครรู้
ไม่ใช่เพราะไม่มีระบบตรวจ แต่เพราะทุกชั้นรายงานว่า "สำเร็จ"
- ตัวตั้งเวลา (cron) เรียก endpoint แล้วได้ HTTP 200 กลับมา จึงถือว่ารอบนั้นสำเร็จ
- โค้ดจับ error ของ AI ไว้แล้วข้ามไป (ไม่อยากให้งานหนึ่งพังทั้งรอบ) แต่ไม่ได้บอกใคร
- ผลลัพธ์ "ไม่มีงานที่น่าสนใจ" กับ "AI ใช้ไม่ได้" ดูเหมือนกันจากข้างนอก คือไม่มีแจ้งเตือน
ข้อสุดท้ายสำคัญที่สุด ระบบแจ้งเตือนที่เงียบเพราะไม่มีอะไรจะบอก กับเงียบเพราะพัง ต้องแยกออกจากกันให้ได้
หลักการ 1: คืน error code ให้ถูก
ถ้า endpoint ทำงานไม่สำเร็จ ให้ตอบ 5xx ไม่ใช่ 200 พร้อมข้อความว่า "ผิดพลาด" ข้างใน บริการ cron ส่วนใหญ่ (เช่น cron-job.org) ส่งอีเมลเตือนได้เมื่อ job ล้มเหลวติดกันหลายครั้ง แต่มันดูจาก status code เท่านั้น
มีอีกเคสที่เจอกับระบบเดียวกัน: ตอนสั่งปิดฟีเจอร์ชั่วคราวผ่านตัวแปรตั้งค่า endpoint ตอบ 200 ว่า "ปิดอยู่"
cron เลยไม่เตือนอะไร พอลืมเปิดคืน ระบบก็หยุดไปอีกเกือบวันโดยไม่มีใครรู้
หลังจากนั้นเปลี่ยนให้ตอบ 503 ตอนปิดอยู่ ถ้าลืมเปิดคืน cron จะเตือนเอง
if not FEATURE_ENABLED:
return jsonify({"ok": False, "disabled": True}), 503 # ห้ามเป็น 200
หลักการ 2: แยก "ไม่มีผลลัพธ์" ออกจาก "ทำงานไม่ได้"
ทุกรอบให้นับไว้ว่า ดึงข้อมูลได้กี่รายการ วิเคราะห์สำเร็จกี่รายการ ล้มเหลวกี่รายการ ถ้าดึงมาได้ 50 งาน แต่วิเคราะห์สำเร็จ 0 งาน นั่นไม่ใช่ "ไม่มีงานดี" แต่คือระบบวิเคราะห์ใช้ไม่ได้ และต้องแจ้งเตือนทันที
stats = {"fetched": len(jobs), "analyzed": 0, "failed": 0}
for job in jobs:
try:
analyze(job); stats["analyzed"] += 1
except Exception as e:
stats["failed"] += 1; last_error = str(e)[:200]
if stats["fetched"] and stats["analyzed"] == 0:
alert(f"AI วิเคราะห์ไม่ได้ทั้งรอบ ({stats['failed']} งาน) — {last_error}")
หลักการ 3: แจ้งเตือนแบบมี cooldown และบอกสาเหตุ
ถ้า AI ล่มแล้วระบบเตือนทุก 30 นาที จะกลายเป็นสแปม แล้วคนจะเลิกอ่าน ให้เตือนครั้งแรกทันที แล้วพักไว้ (เช่น 6 ชั่วโมง) ก่อนเตือนเรื่องเดิมซ้ำ และใส่สาเหตุที่น่าจะเป็นจาก error ที่ได้ เช่น
- 401 / authentication error → key ผิดหรือหมดอายุ
- 404 / model not found → ชื่อโมเดลพิมพ์ผิด หรือรุ่นนั้นเลิกให้บริการแล้ว
- 429 → เกิน rate limit หรือเกินเพดานใช้จ่าย
- timeout / 5xx → ฝั่งผู้ให้บริการมีปัญหาชั่วคราว
เคสจริงที่เจอ: หลังแก้ key เสร็จ ก็ยังใช้ไม่ได้ เพราะชื่อโมเดลในโค้ดพิมพ์ผิดมาตั้งแต่แรก แต่ถูกปัญหา key บังไว้ ถ้าข้อความเตือนแยกประเภท error ให้ จะเห็นทันทีว่าเป็นคนละเรื่อง
หลักการ 4: มีหน้า health check ที่ทดสอบของจริง
ทำ endpoint อย่าง /api/ai-health ที่เรียก AI จริงด้วยคำถามสั้นที่สุด แล้วรายงานผล
อย่าเช็คแค่ว่า "มี key ตั้งไว้ไหม" เพราะ key ที่หมดอายุแล้วก็ยังตั้งอยู่
เช็คลิสต์สร้าง API key
- key ที่ใช้กับเซิร์ฟเวอร์ ไม่ควรตั้งวันหมดอายุสั้นโดยไม่มีระบบเตือนก่อนหมด
- ถ้านโยบายบังคับให้หมดอายุ ให้จดวันหมดอายุไว้ในปฏิทิน และให้ระบบเตือนล่วงหน้า
- เก็บ key ไว้ในตัวแปรสภาพแวดล้อม (environment variable) ไม่ฝังในโค้ด
- หลังเปลี่ยน key ให้เรียก health check ทันที อย่ารอดูว่ารอบถัดไปจะเป็นยังไง
อีกเหตุผลที่ระบบทำงานผิดหลังรีสตาร์ต อ่านได้ที่ ทำไมห้ามเก็บ state สำคัญไว้ในแรม