ปัญหาที่พบบ่อยที่สุดของทีม oncall คือ notification ท่วมท้นจน engineer เริ่มเพิกเฉย ส่วนใหญ่เกิดจากการตั้งเงื่อนไขแจ้งเตือนที่ไม่ actionable — ปลุกคนกลางดึกโดยไม่มีอะไรต้องทำ, แจ้งซ้ำ ๆ จนกลายเป็น noise, หรือไม่มีบริบทพอที่จะเริ่ม troubleshoot การออกแบบกฎแจ้งเตือนที่ดีจึงเป็นศิลปะที่สำคัญไม่แพ้การเขียนโค้ด
บทความนี้สรุปหลักการออกแบบ notification ที่มีคุณภาพ ตั้งแต่ SLI/SLO-based approach, golden signals, การเขียน runbook, การหลีกเลี่ยง flappy alert ไปจนถึง anti-pattern ที่ควรหลีกเลี่ยง เพื่อให้ทีมได้รับข้อความที่ทำให้เกิดการตัดสินใจ ไม่ใช่แค่รับรู้
Actionable คืออะไร
การแจ้งเตือนที่ actionable ต้องผ่านเกณฑ์ 3 ข้อ — (1) Urgent ปัญหาที่ต้องแก้ทันที ไม่สามารถรอได้ (2) Important มีผลกระทบต่อผู้ใช้หรือธุรกิจจริง ๆ (3) Actionable คนที่ได้รับสามารถลงมือแก้ได้ทันที ถ้าขาดข้อใดข้อหนึ่ง ไม่ควรปลุกคน — ควรเป็นแค่ ticket หรือ dashboard เท่านั้น
SLI, SLO, SLA: พื้นฐานการวัด
แนวคิด SRE จาก Google เสนอให้วัดคุณภาพระบบผ่าน 3 แนวคิด Service Level Indicator (SLI) เช่น request latency, success rate, Service Level Objective (SLO) เป้าหมายที่ยอมรับได้ เช่น 99.9% ของ request ต้องเสร็จใน 500ms และ Service Level Agreement (SLA) สัญญากับลูกค้าที่ต่ำกว่า SLO
แจ้งเตือนเมื่อ SLO กำลังจะถูกละเมิด ดีกว่าแจ้งเมื่อ metric เกิน threshold เพียงครั้งเดียว เพราะสะท้อน user impact จริง ๆ
# Error Budget Burn Rate Alert
- alert: HighErrorBudgetBurn
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[1h]))
/
sum(rate(http_requests_total[1h]))
) > (14.4 * 0.001)
for: 2m
labels:
severity: critical
annotations:
summary: "Error budget burning 14.4x faster than normal"
runbook: "https://wiki.example.com/runbooks/error-budget"
14.4 คือ burn rate ที่จะใช้ error budget หมดภายใน 2 วัน ถ้า SLO = 99.9% (error budget = 0.1%) multiplier นี้แปลว่าเสียเร็วกว่าปกติ 14.4 เท่า
Golden Signals: 4 ตัวชี้วัดที่ต้อง monitor
Google SRE book แนะนำให้ทุก service track 4 ตัวชี้วัดหลัก ที่ครอบคลุม 90% ของปัญหาที่เกิดขึ้น
- Latency — เวลาในการประมวลผล request แยก success และ failure
- Traffic — ปริมาณ request/second หรือ concurrent connection
- Errors — อัตรา error จริง ๆ (5xx) หรือ failed transaction
- Saturation — ความอิ่มตัวของทรัพยากร เช่น CPU, memory, queue depth
สำหรับระบบที่ใช้ infrastructure เช่น database เพิ่ม USE Method (Utilization, Saturation, Errors) ของ Brendan Gregg ที่เน้น hardware resource
Severity Level: จัดลำดับความสำคัญ
แบ่งระดับ severity ตาม impact และความเร่งด่วน เพื่อกำหนด routing และ escalation ที่เหมาะสม
| Severity | ตัวอย่าง | การตอบสนอง |
|---|---|---|
| P1 / Critical | service ล่มทั้งหมด, ข้อมูลเสียหาย | ปลุก oncall ทันที 24/7 |
| P2 / High | feature หลักไม่ทำงาน, degraded performance | ปลุก oncall ในเวลาทำงาน |
| P3 / Warning | trend ไม่ดี, capacity ใกล้เต็ม | สร้าง ticket, ดูใน business hours |
| P4 / Info | deploy, config change, routine event | log อย่างเดียว ไม่ notify |
หลีกเลี่ยง Flappy Alert
Flappy alert คือการแจ้งเตือนที่เปลี่ยนสถานะ firing/resolved ซ้ำ ๆ ในเวลาสั้น ๆ ทำให้ทีมสับสน วิธีแก้คือใช้ for: clause ให้เงื่อนไขขอ trigger ต่อเนื่องอย่างน้อย N นาทีก่อน fire
# ไม่ดี — alert fire ทุกครั้งที่ CPU spike
- alert: HighCPU
expr: cpu_usage > 80
# ดี — ต้อง sustained อย่างน้อย 5 นาที
- alert: HighCPU
expr: cpu_usage > 80
for: 5m
labels:
severity: warning
annotations:
summary: "CPU usage sustained above 80% for 5 minutes"
description: "Instance {{ $labels.instance }} CPU = {{ $value }}%"
สำหรับ metric ที่ noisy มาก ใช้ avg_over_time หรือ rate แทน snapshot value เพื่อให้ทนต่อ spike ชั่วคราว
เขียน Runbook ที่มีประโยชน์
Runbook คือเอกสารที่บอกวิธีตอบสนองต่อการแจ้งเตือนแต่ละประเภท ควรลิงก์ไว้ใน annotation ของ rule ทุกตัว เนื้อหาที่ดีควรประกอบด้วย
- Summary — ปัญหานี้หมายความว่าอะไร มีผลกระทบระดับใด
- Diagnostic steps — command ที่ควรรัน, dashboard ที่ควรดู, log ที่ควรเช็ค
- Remediation — ขั้นตอนการแก้ไข เรียงลำดับจากง่ายไปยาก
- Escalation — ถ้าแก้ไม่ได้ใน X นาที ติดต่อใคร
- Related alerts — การแจ้งเตือนที่อาจเกิดร่วมกัน
- alert: DatabaseConnectionPoolExhausted
expr: db_pool_active / db_pool_max > 0.9
for: 2m
labels:
severity: critical
team: database
annotations:
summary: "DB connection pool > 90% utilized"
description: |
Pool {{ $labels.pool }} on {{ $labels.instance }}
has {{ $value | humanizePercentage }} connections in use.
runbook: "https://wiki.example.com/runbooks/db-pool-exhausted"
dashboard: "https://grafana.example.com/d/db-pool"
Symptom-Based vs Cause-Based
แนวคิดสำคัญจาก Google SRE — แจ้งเตือนที่อาการ (symptom) ไม่ใช่สาเหตุ (cause) เพราะสาเหตุเปลี่ยนได้ แต่อาการที่กระทบผู้ใช้สำคัญเสมอ
- Symptom-based (ดี) — “API error rate > 1%” / “checkout latency p99 > 2s” / “user login success rate < 99%”
- Cause-based (ระวัง) — “CPU > 80%” / “memory > 90%” / “disk queue depth > 10” — อาจไม่กระทบผู้ใช้จริง
Cause-based ใช้เป็น warning หรือ trend alert ได้ แต่ critical alert ควรเป็น symptom-based เสมอ เพื่อให้มั่นใจว่าสิ่งที่ปลุกคนจริง ๆ กระทบ user
วัดคุณภาพของ Alert เอง
ระบบ oncall ที่ดีควร audit ตัวเองเป็นประจำ metric สำคัญที่ควร track
- Actionability rate — กี่ % ของ alert ที่คนรับแล้วต้องทำอะไรจริง ๆ ต่ำกว่า 50% = มี noise เยอะ
- Time to acknowledge (TTA) — เวลาเฉลี่ยที่คนรับการแจ้งเตือน ถ้าสูงมาก = alert fatigue
- Alert volume per shift — จำนวน page ต่อกะ ไม่ควรเกิน 2 ครั้งเฉลี่ย
- Top 10 noisiest alerts — review รายเดือนเพื่อปรับปรุงหรือลบ
Anti-Patterns ที่ควรหลีกเลี่ยง
- “Just in case” alerts — แจ้งเตือนที่เพิ่มไว้เผื่อ ไม่มีคนรู้ว่าต้องทำอะไรเมื่อ fire
- Cascading noise — service A ล่มทำให้ B, C, D, E แจ้งเตือนพร้อมกัน ใช้ inhibit rules แก้
- Too aggressive thresholds — ตั้ง threshold ต่ำเกินจนมี false positive ทุกวัน
- Missing context — annotation แค่ “Something wrong” โดยไม่บอกว่า service/instance/metric ไหน
- No runbook — คนใหม่ในทีมไม่รู้ต้องทำอะไร
- Duplicate alerts — rule ต่างกันแต่ detect ปัญหาเดียวกัน ควร merge
- Stale alerts — rule ที่ไม่ fire มาเป็นปี อาจเป็นเพราะโค้ดเปลี่ยนหรือ metric name เปลี่ยน
ตัวอย่าง Alert ที่ดีเทียบกับที่ไม่ดี
# ❌ ไม่ดี — ไม่มีบริบท, cause-based, ไม่มี runbook
- alert: HighMemory
expr: memory_used > 8000000000
annotations:
summary: "Memory high"
# ✅ ดี — symptom-based, มีบริบท, มี runbook
- alert: APILatencyHigh
expr: |
histogram_quantile(0.99,
sum by (le, service) (rate(http_duration_seconds_bucket[5m]))
) > 2
for: 10m
labels:
severity: critical
team: platform
annotations:
summary: "{{ $labels.service }} p99 latency > 2s"
description: |
Service {{ $labels.service }} p99 latency is
{{ $value | printf "%.2f" }}s for the past 10 minutes.
Users are likely experiencing slow responses.
runbook: "https://wiki.example.com/runbooks/api-latency"
dashboard: "https://grafana.example.com/d/api-overview?var-service={{ $labels.service }}"
วัฒนธรรมทีม: Blameless Postmortem
ทุก incident ที่ผ่าน critical alert ควรมี postmortem แบบ blameless — เน้นหาสาเหตุเชิงระบบไม่ใช่หาคนผิด ในเอกสารควรมี timeline, root cause, impact, action items ที่ชัดเจน เพื่อป้องกันไม่ให้เกิดซ้ำ การ review notification บ่อย ๆ ใน retrospective ช่วยให้ทีมร่วมกันปรับปรุงคุณภาพของระบบ
สรุป
การออกแบบระบบ notification ที่ actionable ไม่ใช่เรื่องเทคนิคล้วน ๆ แต่เป็นเรื่องของวัฒนธรรมและการตัดสินใจ — ยึดหลัก symptom-based แทน cause-based, ใช้ SLO-driven approach, เขียน runbook ทุกครั้ง, หลีกเลี่ยง flappy alert ด้วย for: clause และ audit คุณภาพเป็นประจำ
เป้าหมายสุดท้ายคือให้ทีม oncall ได้นอนหลับ ไม่ต้องลุกขึ้นมารับ notification ที่ไม่ต้องทำอะไร ระบบที่สมบูรณ์จะปลุกคนเฉพาะเมื่อ user กำลังเดือดร้อนจริง ๆ และคนที่รับสามารถแก้ได้ทันทีด้วยข้อมูลที่ครบถ้วน

