SLO, Error Budget และ Burn Rate Alert
ไอเดียหลักในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียหลักในหนึ่งประโยค”Datadog SLO แปลง target แบบ “99.9% ดีตลอด 30 วัน” ให้กลายเป็น error budget ที่ track ได้ และ Datadog มี SLO alert สองแบบที่ต่างกันเพราะ “budget เหลือเท่าไหร่” กับ “กำลังหมดเร็วแค่ไหน” คือคนละปัญหากัน
SLO สามแบบ
หัวข้อที่มีชื่อว่า “SLO สามแบบ”Metric-based — อัตราส่วนระหว่าง metric query สองตัว good event หารด้วย total event
{ "name": "Checkout API availability", "type": "metric", "query": { "numerator": "sum:trace.http.request.hits{service:checkout-api,!http.status_code:5*}.as_count()", "denominator": "sum:trace.http.request.hits{service:checkout-api}.as_count()" }, "thresholds": [ { "timeframe": "30d", "target": 99.9 } ]}Monitor-based — สร้างจาก uptime history ของ monitor ที่มีอยู่แล้วหนึ่งตัวหรือมากกว่า เวลา “ดี” ของ SLO คือช่วงที่ monitor พวกนั้น ไม่ได้ trigger
{ "name": "Checkout API monitor-based SLO", "type": "monitor", "monitor_ids": [111111, 222222], "thresholds": [ { "timeframe": "30d", "target": 99.9 } ]}Time Slice — ประเมิน query ทีละ time slice ขนาดคงที่ทั่วทั้ง window เช่น “p99 latency ต่ำกว่า 300ms ในทุก slice ขนาด 5 นาทีหรือไม่”
{ "name": "Checkout API p99 latency SLO", "type": "time_slice", "sli_specification": { "time_slice": { "query": { "formula": "query1", "queries": ["p99:trace.http.request.duration{service:checkout-api}"] }, "comparator": "<", "threshold": 300, "query_interval_seconds": 300 } }, "thresholds": [ { "timeframe": "30d", "target": 99.9 } ]}Error budget คืออะไร
หัวข้อที่มีชื่อว่า “Error budget คืออะไร”target แบบ 99.9% ตลอด window 30 วัน มีนัยว่ายอมให้เวลาหรือ event ที่ “แย่” ได้จำนวนหนึ่ง — ส่วนที่ยอมได้นั้นคือ error budget
Target: 99.9% over 30d30d = 43200 minutesAllowed bad time (error budget) = 0.1% x 43200 = 43.2 minutesทุก event ที่แย่หรือทุกนาทีที่แย่กิน budget 43.2 นาทีนั้นไปทีละส่วน พอ budget เหลือ 0% window นั้นก็ตกต่ำกว่า target 99.9% ไปแล้ว
Error budget alert เทียบกับ burn rate alert
หัวข้อที่มีชื่อว่า “Error budget alert เทียบกับ burn rate alert”นี่คือความต่างที่สำคัญที่สุดในบทเรียนนี้ และ Datadog มี SLO alert สองแบบแยกกันสำหรับเรื่องนี้
Error budget alert — ใช้ threshold บนเปอร์เซ็นต์สะสมของ budget ที่ถูกใช้ไปแล้วใน window เช่น alert ตอน budget ถูกใช้ไป 75% และตั้ง warn เร็วกว่านั้นที่ 50% ก็ได้
{ "name": "checkout-api error budget consumption", "type": "slo alert", "query": "error_budget(\"<slo_id>\").over(\"30d\") > 75", "message": "@slack-checkout-oncall 75% of the 30d error budget for checkout-api is already consumed."}Burn rate alert — notify เมื่อ อัตรา การใช้ error budget เกิน threshold และค้างอยู่ต่อเนื่องตลอดช่วงเวลาหนึ่ง query shape จริงต้องการสามส่วน ไม่ใช่สอง: .over(), .long_window(), และ .short_window():
{ "name": "checkout-api SLO burn rate (fast)", "type": "slo alert", "query": "burn_rate(\"<slo_id>\").over(\"30d\").long_window(\"6h\").short_window(\"30m\") > 10", "message": "@pagerduty-checkout-critical Burn rate is 10x normal - at this rate the entire 30d budget is gone in about 3 days."}เลือก burn rate threshold กับขนาด window
หัวข้อที่มีชื่อว่า “เลือก burn rate threshold กับขนาด window”เอกสารของ Datadog เองให้ตัวอย่างจริงที่ทำงานสมบูรณ์หนึ่งอันสำหรับ SLO target 30 วัน: burn rate 14.4, long_window("1h"), short_window("5m") แล้วระบุว่า “theoretical error budget consumed: 2%” ตลอด long window นั้น หน้าเดียวกันให้สูตรที่อยู่เบื้องหลัง (burn rate = (ความยาว SLO target เป็นชั่วโมง × % ของ budget ที่ใช้ไป) / (long window เป็นชั่วโมง × 100)) และวิธีคิดอีกแบบที่เทียบเท่ากัน (ความยาว SLO target / burn rate = เวลาที่ error budget จะหมดถ้า burn ที่อัตรานี้ต่อเนื่อง) ทั้งสองสูตรตรงกับตัวอย่างของบทนี้เอง burn_rate(...).over("30d").long_window("6h") > 10:
Approach 1 (time to exhaustion): 30d / burn_rate(10) = 3 daysApproach 2 (budget consumed over one long_window): (long_window_hours x burn_rate x 100) / (SLO_target_hours) = (6 x 10 x 100) / (30 x 24) = 6000 / 720 = 8.33%ตัวเลข 8.33%-ต่อ-6-ชั่วโมงนั้นแหละคือเหตุผลว่าทำไม message ใน fence ข้างบนถึงเขียนว่า “gone in about 3 days” — สูตรทั้งสองตรงกัน ตัวอย่างจริงของ docs.datadoghq.com เองก็ใช้สูตรเดียวกันแค่ตัวเลขต่างไป (long_window=1h, burn_rate=14.4, over=30d → (1 x 14.4 x 100)/720 = 2% ตรงกับที่หน้าเอกสารระบุ “2%” เป๊ะ)
ขยายสูตรเดียวกันเป็นตาราง multi-window เล็ก ๆ (short window คงอัตราส่วนที่ Google แนะนำไว้ที่ ~1/12 ของ long window ซึ่งหน้าเอกสารระบุตรง ๆ):
| Sensitivity | burn_rate | long_window | short_window (~1/12) | over | budget consumed / long_window | time to exhaust budget ||-------------|-----------|-------------|-----------------------|------|--------------------------------|--------------------------|| Fast page | 14.4 | 1h | 5m | 30d | 2.00% (docs' own example) | 30d / 14.4 = ~2.08 days || This lesson | 10 | 6h | 30m | 30d | 8.33% | 30d / 10 = 3 days || Slow ticket | 1 | 3d (72h) | 6h | 30d | 10.00% | 30d / 1 = 30 days |burn rate ที่สูงคู่กับ long_window สั้น จับ incident รุนแรงที่เคลื่อนไหวเร็วได้ภายในไม่กี่นาที ส่วน burn rate ใกล้ 1 คู่กับ long_window หลายวัน คือสัญญาณ “budget กำลังจะหมดพอดีตามกำหนด” — ควรเป็น ticket ไม่ใช่ page การรันสองหรือสามตัวนี้คู่กันกับ SLO เดียวกัน (ตัวเร็ว/page หนึ่งตัว ตัวช้า/ticket หนึ่งตัว) คือ pattern มาตรฐาน — burn-rate monitor ตัวเดียวบอกได้แค่ความไวที่มันถูก tune ไว้เท่านั้น
alert สองแบบนี้แก้คนละปัญหากัน error budget alert บอกว่า “budget กำลังจะหมด” — ดูที่ยอดสะสมที่ใช้ไปแล้ว เหมาะกับการตัดสินใจว่าสัปดาห์นี้ ship ของที่มีความเสี่ยงได้ปลอดภัยไหม ส่วน burn rate alert บอกว่า “budget กำลังจะหมด เร็วมาก ตอนนี้เลย” — ดูที่ความเร็ว ไม่ใช่ยอดรวม incident ที่ burn เร็วต่อเนื่องสามารถเผา budget 30 วันทั้งก้อนหมดได้ในไม่กี่ชั่วโมง และ burn rate alert จับสัญญาณนั้นได้ตั้งแต่ชั่วโมงแรก ก่อนที่ threshold แบบสะสมของ error budget alert จะข้ามไปด้วยซ้ำ
flowchart LR
SLO[SLO target 99.9% over 30d] --> Budget[Error budget]
Budget --> EBA{Error budget alert: percent consumed above threshold}
Budget --> BRA{Burn rate alert: consumption rate above threshold, sustained}
EBA -->|e.g. 75 percent consumed| Notify1[Notify: budget running out]
BRA -->|e.g. 10x burn rate for 1h| Notify2[Notify: fast incident right now] Reading AI-generated code
หัวข้อที่มีชื่อว่า “Reading AI-generated code”🐛 หา bug ในโค้ดนี้
// AI comment: "Page the on-call when the error budget is burning 10x too fast"{"name": "checkout-api SLO burn rate (fast)","type": "slo alert","query": "burn_rate(\"<slo_id>\").over(\"1h\").long_window(\"6h\") > 10","message": "@pagerduty-checkout-critical Burn rate is 10x normal."}เฉลย
นี่คือ JSON ที่ valid และผ่าน Monitor(**data, _check_type=True) model validation ได้สะอาด — พิสูจน์แล้วข้างบนว่า client เช็คแค่ว่า query เป็น str เท่านั้น ไม่เช็ค Datadog query grammar ข้างในเลย มีบั๊กจริงสองตัวซ่อนอยู่ในสตริงนั้น อย่างแรก .over("1h") — .over() ต้องเป็น window ที่ config ไว้ของ SLO เอง (7d/30d/90d) ไม่ใช่ alerting window เลย "1h" ไม่ใช่ความยาว SLO target ที่ valid ด้วยซ้ำ อย่างที่สอง ไม่มี .short_window() อยู่เลย ซึ่ง query grammar ของ burn-rate ต้องการ เอกสารของ Datadog (docs.datadoghq.com/service_management/service_level_objectives/burn_rate/) ปฏิเสธ query ที่ขาดส่วนใดส่วนหนึ่งไป การแก้ ให้ตรงกับ SLO ที่ define ไว้ก่อนหน้าในบทนี้: burn_rate("<slo_id>").over("30d").long_window("6h").short_window("30m") > 10 ทั้ง comment ของ AI และตัว JSON-shape validator ไม่มีทางจับสิ่งนี้ได้เลย — ต้องอ่าน query grammar จริงหรือ real API call เท่านั้นถึงจะจับได้
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”SLO คือมาตรวัดระยะทางที่มีเกจน้ำมันติดมาด้วย มาตรวัดระยะทาง (query metric/monitor/time-slice ของ SLO เอง) แค่วัด “ดี” กับ “แย่” ต่อเนื่อง เกจน้ำมัน (error budget) แปลงมันเป็น “เรายอมให้มีเวลาแย่ได้เท่าไหร่ แล้วเหลือเท่าไหร่” error budget alert อ่านค่าที่เกจโดยตรง — threshold บนระดับน้ำมัน burn rate alert ดูว่าเข็มขยับเร็วแค่ไหน — threshold บนอัตราการเปลี่ยนแปลงที่ค้างอยู่ตลอด window รถอาจมีถังเต็ม (budget แทบไม่ถูกใช้เลย) แต่เผาน้ำมันในอัตราที่ถ้าคงไว้แบบนี้จะหมดถังภายในชั่วโมงเดียว และ burn rate alert คือตัวเดียวในสองแบบนี้ที่สังเกตเห็นทันเวลา
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”- Metric-based เทียบกับ monitor-based เทียบกับ time-slice SLO — metric-based ยืดหยุ่นที่สุด (อัตราส่วนของ query สองตัวแบบไหนก็ได้) แต่ต้องมีการแบ่ง “ดี”/“ทั้งหมด” เป็น metric query แยกกันอยู่แล้ว; monitor-based ใช้ monitor ที่มีอยู่แล้วซ้ำ แต่สืบทอด threshold ที่ monitor พวกนั้นถูก tune ไว้; time-slice เป็นแบบเดียวในสามแบบที่สื่อ “ต้องคงอยู่เหนือ/ใต้ threshold ต่อเนื่อง” แทนที่จะเป็น “เฉลี่ย” ได้ แลกกับ query shape ที่ซับซ้อนขึ้นนิดหน่อย (พิสูจน์แล้วข้างบน:
sli_specification.time_sliceซ้อน formula/query กับcomparator/thresholdเข้าด้วยกัน) - Error budget alert เทียบกับ burn rate alert — error budget alert คิดง่ายกว่า (เปอร์เซ็นต์สะสมตัวเดียว) แต่ fire หลังจากความเสียหายจริงเกิดขึ้นแล้วเท่านั้น; burn rate alert จับปัญหาได้เร็วกว่า แต่ต้อง tune ตัวเลขสองตัวให้ถูก (
long_window,short_window) และพิสูจน์แล้วข้างบนว่า.over()ต้องตรงกับ window ของ SLO เองเป๊ะ ไม่งั้น query จะไม่มีความหมาย - burn-rate monitor ตัวเดียว เทียบกับคู่เร็ว+ช้า — burn-rate monitor ตัวเดียวที่ tune สำหรับ incident เร็ว (พิสูจน์แล้วข้างบน:
14.4/1h/5m, ~2% budget ต่อ window) พลาด leak ที่ช้าแต่ต่อเนื่องซึ่งจะเผา budget เดียวกันหมดในไม่กี่วัน รัน monitor ตัวที่สองที่ช้ากว่า (1/3d/6hในตารางของบทนี้) จับ case นั้นได้ด้วย แลกกับต้องดูแลและ tune สอง monitor ต่อหนึ่ง SLO แทนที่จะเป็นตัวเดียว
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “
.over()ใน burn rate query คือ alerting window (burn ต้องค้างนานแค่ไหน)” ไม่จริง — พิสูจน์แล้วข้างบน อ้างจากเอกสารตรง ๆ:.over()คือ window target ที่ config ไว้ของ SLO เอง (7d/30d/90d) ส่วนความนานที่ต้องค้างคือ.long_window() - “burn rate query ที่ไม่มี
.short_window()ยัง valid อยู่ แค่แม่นยำน้อยลง” ไม่จริง — ตัวอย่างจริงในเอกสารและ burn-rate query ทุกตัวต้องมีทั้ง.long_window()และ.short_window(); query ที่ไม่มี.short_window()(พิสูจน์แล้วข้างบน ดราฟต์เก่าของบทนี้) ผิดรูป ไม่ใช่แค่แม่นยำน้อยลง - “JSON model validation (
datadog_api_client) จะจับ burn_rate() query string ที่ผิดได้” ไม่จริง — พิสูจน์แล้วข้างบนด้วยการรันทั้ง query ที่แก้แล้วและที่ผิดผ่านMonitormodel จริงที่ pin ไว้: ทั้งคู่ได้{"ok": true}client เช็คแค่ type ของ field (queryต้องเป็นstr) ไม่เช็ค Datadog query grammar ข้างในสตริงนั้น - “SLO ที่ไม่มี
thresholdsยังเป็น SLO document ที่ใช้ได้” ไม่จริง — พิสูจน์แล้วข้างบน:ServiceLevelObjective(**data, _check_type=True)ต้องการthresholdsสำหรับ SLO ทุกแบบใน client version ที่ pin นี้ document ที่ไม่มี field นี้ construct ไม่ได้เลย