BotKit
บทความ › เขียนโค้ด & ดูแลระบบ

ระบบตายเงียบทั้งวันเพราะ 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 สำคัญไว้ในแรม

เกี่ยวกับผู้เขียน — Jark นักพัฒนา LINE Bot และระบบ AI อัตโนมัติ อยู่เชียงใหม่ ทำระบบตอบแชทและจองคิวให้ร้านค้าจริง บทความในเว็บนี้มาจากงานที่ลงมือทำเอง อ่านเพิ่ม