ปัญหา
"service นี้ reliable พอหรือยัง" ไม่มีคำตอบ จนกว่าจะมีคนนิยามว่า พอ คือแค่ไหน ถ้าไม่มีเป้า ก็มีแต่ทุก error เป็นเรื่องด่วน หรือไม่มี error ไหนเป็นเรื่องด่วนเลย alert ดังเพราะ CPU เพราะ 500 ตัวเดียว และเพราะทุกการสะดุดห้านาที จนคน on-call เลิกเชื่อมัน แล้วการเถียงกันระหว่าง "ship ให้เร็วขึ้น" กับ "เลิกทำของพัง" ก็จบที่ใครเสียงดังกว่าคนนั้นชนะ
การตั้งเป้าไว้ 100% ไม่ได้ช่วยตัดสินเรื่องนี้ หนังสือ SRE ของ Google บอกว่ามันเป็นเป้าที่ผิด: มันไปไม่ถึง เลขเก้าแต่ละตัวที่เพิ่มขึ้นแพงกว่าตัวก่อน ในขณะที่ user สังเกตเห็นน้อยลงเรื่อย ๆ (คนที่ใช้โทรศัพท์ที่ reliable 99% แยก 99.99% ออกจาก 99.999% ไม่ได้) และ service ที่ห้ามพังเลยก็เปลี่ยนอะไรไม่ได้เลย เพราะการเปลี่ยนแปลงคือต้นเหตุใหญ่ที่สุดของ outage
ทำงานยังไง
มีสามคำ แต่ละคำต่อยอดจากคำก่อนหน้า:
- SLI (service level indicator): ค่าที่วัด service ตามที่ user ได้สัมผัสจริง เขียนเป็นอัตราส่วนของ event ที่ดีต่อ event ที่ valid ในที่นี้คือ request ของ checkout ที่ตอบกลับโดยไม่เป็น 5xx จาก request ของ checkout ทั้งหมด
- SLO (service level objective): เป้าของ SLI ในช่วงเวลาหนึ่ง เช่น 99.9% ใน 30 วันแบบ rolling มันเป็นเป้าภายในที่ใช้ขับการตัดสินใจทางวิศวกรรม
- SLA (service level agreement): สัญญากับลูกค้าที่ผูกผลที่ตามมา ปกติคือการคืนเงินหรือ credit ไว้กับการทำ service ไม่ถึงระดับที่ตกลง ถ้าไม่มีอะไรเกิดขึ้นตอนทำตัวเลขไม่ถึง มันก็คือ SLO แล้วก็ให้ตั้ง SLO ภายในให้เข้มกว่าอะไรก็ตามที่สัญญาไว้ใน SLA ทีมจะได้ลงมือตั้งนานก่อนที่สัญญาจะเริ่มเสี่ยง
error budget คือสิ่งที่เป้าหมายเหลือไว้ให้: 100% − 99.9% = 0.1% ของ event ที่ valid ถ้ามี 1,000,000 request ต่อวัน ก็คือ request ที่พลาดได้ 30,000 ตัวต่อ 30 วัน เท่ากับโควตาของการล่มทั้งระบบ 43.2 นาที ทุกอย่างที่สร้าง event ที่ไม่ดีก็เบิกจากบัญชีเดียวกันหมด: incident, release ที่พัง, การทดลอง, planned maintenance และ outage ของ dependency
Burn rate คือความเร็วที่ budget หมดไป: error rate ที่วัดได้หารด้วย budget rate (1 − SLO) ถ้าอยู่ที่ 1× ตัว budget จะอยู่ได้พอดีกับ window ส่วนที่ 15× (error 1.5% เทียบกับ 0.1%) มันจะหมดในสองวัน และที่ 1,000× (ล่มทั้งระบบ) จะหมดใน 43 นาที
error budget policy คือข้อตกลงว่าจะเกิดอะไรต่อ: ตราบที่ยังมี budget เหลือ ทีมก็ ship ไป พอใช้หมดแล้ว การ release feature ก็หยุด และแรงก็ไปอยู่ที่ reliability จนกว่า service จะกลับมาอยู่ใน SLO
เลือก SLI
เลือกไม่กี่ตัวที่ใช้แทนความพอใจของ user ได้ (SRE Workbook แนะนำไม่เกินห้าประเภทต่อ service) ตามชนิดของระบบ:
| ระบบ | SLI | event ที่ดี |
|---|---|---|
| ขับด้วย request (API, web) | Availability | request ได้ response ที่สำเร็จ |
| Latency | response เร็วกว่าเกณฑ์ เช่น 300 ms | |
| Quality | response ที่ส่งไปไม่ได้ถูกลดคุณภาพ | |
| ประมวลผลข้อมูล (pipeline) | Freshness | ข้อมูลถูก update ล่าสุดภายในเกณฑ์ |
| Correctness | record ออกมาด้วยค่าที่ถูกต้อง | |
| Coverage | record ถูกประมวลผลทันเวลา หรือ batch job ประมวลผลข้อมูลได้มากพอ | |
| Storage | Durability | record ที่เขียนแล้วอ่านกลับมาได้ |
วัดให้ใกล้ user ที่สุดเท่าที่ทำได้ log ของแอปพลิเคชันรู้แค่ request ที่มาถึงแอปพลิเคชัน ส่วน load balancer เห็น request ที่ไปไม่ถึงด้วย แล้ว instrumentation ฝั่ง client หรือ synthetic probe ก็เห็นความล้มเหลวของ DNS, CDN และ network ด้วย ตัวอย่างใน workbook เองเริ่มที่ load balancer เพราะ metric พวกนั้นมีอยู่แล้ว และใกล้ user กว่า log ของ server ตัดสินด้วยว่าอะไรนับบ้าง ในตัวอย่างนั้น response 5xx คือไม่ดี และ response อื่นทุกตัวคือดี ส่วน SLO workshop ของ Google กำหนดขอบเขตของ event ที่ valid ตาม hostname หรือ path ทำให้อัตราส่วนครอบคลุมแค่ request ที่ user journey พึ่งพา สำหรับ latency ให้ใช้มากกว่าหนึ่งเกณฑ์ (เช่น 90% ต่ำกว่า 100 ms และ 99% ต่ำกว่า 400 ms) long tail จะได้ถูกนับด้วย
Window และสิ่งที่นับ
Rolling หรือตามปฏิทิน rolling window (30 วันล่าสุด) ตามสิ่งที่ user จำได้: outage วันสุดท้ายของเดือนไม่ได้ถูกลืมไปในวันแรกของเดือนถัดไป budget จะค่อย ๆ กลับมาเมื่อ event ที่ไม่ดีหลุดออกจาก window ตัว workbook แนะนำให้ใช้จำนวนสัปดาห์เต็ม ค่า default คือสี่สัปดาห์ ทุก window จะได้มีจำนวนวันหยุดสุดสัปดาห์เท่ากัน ส่วน calendar window (หนึ่งเดือน หนึ่งไตรมาส) reset ในวันที่รู้ล่วงหน้า และตรงกับรอบวางแผนและรอบบิล ถ้าเป็น SLO แบบนับ request ตัว budget ของมันเป็นแค่ค่าประมาณจนกว่ารอบจะจบ เพราะมันขึ้นกับ traffic ที่ยังมาไม่ถึง
นับ request หรือนับช่วงเวลา SLO แบบ request-based นับ event: 99.9% ของ request ต้องดี ส่วน SLO แบบ window-based นับช่วงเวลา: 99% ของ window หนึ่งนาทีต้องดี โดยนาทีที่ดีอาจหมายถึงนาทีที่ 95% ของ request ต่ำกว่า 300 ms ตัว SLO แบบ request-based ให้น้ำหนักทุก request เท่ากัน outage ตอน peak เลยกิน budget มากกว่า outage แบบเดียวกันตอนตีสาม ส่วน SLO แบบ window-based ให้น้ำหนักทุกนาทีเท่ากัน แบบนี้ตรงกับ "จำนวนนาทีที่ล่ม" และเป็นทางเลือกเดียวถ้า metric เป็น percentile อยู่แล้ว แต่นาทีที่มี error เกินไปตัวเดียวก็นับเท่ากับนาทีที่มีแต่ error ล้วน ๆ และนาทีที่เงียบก็นับเท่ากับนาทีที่ยุ่ง
ทำไมต้อง alert ตาม burn rate
threshold บน error rate ดิบ ๆ พังไม่ทางใดก็ทางหนึ่ง workbook ไล่ดูความพยายามแต่ละแบบ และตัดสินแต่ละแบบจากสี่เรื่อง: precision (alert ที่คุ้มค่ามีกี่ครั้ง), recall (event สำคัญที่ทำให้เกิด alert มีกี่ครั้ง), detection time และ reset time (alert ยังดังอยู่นานแค่ไหนหลังปัญหาหายไปแล้ว)
- error rate เกิน SLO ใน 10 นาที เร็ว แต่ noisy เกินไปมาก: error 0.1% นานสิบนาทีกินแค่ 0.02% ของ budget รายเดือน มันเลยดังได้ 144 ครั้งต่อวันบน service ที่ยังทำได้ตาม SLO อยู่
- แบบเดียวกันแต่ใช้ window ยาว (36 ชั่วโมง) precision ดีขึ้น แต่หลังล่มทั้งระบบ alert ก็ยังดังต่อไปอีก 36 ชั่วโมง
- เงื่อนไขด้านระยะเวลา (
for: 1h) ทั้งการล่มทั้งระบบและ error rate 0.2% ถูกเห็นหลังผ่านไปหนึ่งชั่วโมงเท่ากัน ถึงตอนนั้นการล่มก็กิน budget ไปแล้ว 140% - Burn rate ให้ตัดสินว่าจะยอมให้ budget หายไปเท่าไรก่อนแจ้งคน แล้วคำนวณ threshold จากตรงนั้น: 2% ของ budget 30 วันในหนึ่งชั่วโมงคือ burn rate 14.4
- หลาย burn rate จะได้จับการรั่วช้า ๆ ได้ด้วย: 5% ใน 6 ชั่วโมง (6×) ยัง page อยู่ ส่วน 10% ใน 3 วัน (1×) เปิด ticket
- สอง window ต่อหนึ่งกฎ แต่ละกฎต้องมี window สั้นด้วย ยาวหนึ่งในสิบสองของ window ยาว และต้องเกิน burn rate เดียวกัน แบบนี้แสดงว่า budget ยังถูกเผาอยู่ alert เลยหยุดไม่กี่นาทีหลังแก้เสร็จ แทนที่จะหยุดหลังจากนั้นหนึ่งชั่วโมง (หรือสามวัน)
ผลที่ได้คือตารางใน diagram ที่ workbook แนะนำให้ใช้เป็นจุดเริ่มต้นสำหรับ SLO 99.9%:
| Burn rate | Window ยาว | Window สั้น | budget ที่ใช้ไปตอน alert ดัง | Alert |
|---|---|---|---|---|
| 14.4× | 1 ชั่วโมง | 5 นาที | 2% | page |
| 6× | 6 ชั่วโมง | 30 นาที | 5% | page |
| 1× | 3 วัน | 6 ชั่วโมง | 10% | ticket |
ตัวคูณพวกนี้คิดจาก window 30 วัน: ใช้ 2% ของ 720 ชั่วโมงไปในหนึ่งชั่วโมงคือ 14.4 ส่วน detection time จะสั้นลงเมื่อ incident รุนแรงขึ้น ที่ 15× กฎแรกใช้ราว 58 นาที ส่วนถ้าล่มทั้งระบบจะไม่ถึงนาที
Error budget policy
budget จะเปลี่ยนพฤติกรรมได้ก็ต่อเมื่อทุกคนตกลงกันไว้ก่อนว่าจะเกิดอะไรขึ้นตอนมันหมด policy คือข้อตกลงสั้น ๆ ที่เขียนไว้ ระหว่าง product manager, ทีมพัฒนา และคนที่ดูแล service ข้างในบอกว่า:
- จะเกิดอะไรขึ้น ใน policy ตัวอย่างของ workbook การ release เดินไปตามปกติตราบที่ service ยังอยู่ที่ SLO หรือดีกว่า พอมันใช้เกิน budget ของสี่สัปดาห์ที่ผ่านมาแล้ว การเปลี่ยนแปลงและการ release ทั้งหมดก็หยุด ยกเว้นการแก้ที่ด่วนที่สุดและ security patch จนกว่ามันจะกลับมาอยู่ใน SLO ขั้นที่อ่อนกว่านี้ก็ใช้ได้ เช่น release ให้ช้าลงตอน budget ใกล้หมด
- ใครตัดสิน และใครชี้ขาดเมื่อเห็นไม่ตรงกัน (ในตัวอย่างคือ CTO)
- ข้อยกเว้น ตัวอย่างให้ทำงาน feature ต่อได้ ถ้า budget หมดเพราะปัญหา network ทั้งบริษัท เพราะ service ของทีมอื่นที่ freeze release ของตัวเองไปแล้ว หรือเพราะ traffic ที่อยู่นอก SLO เช่น load test
- อะไรทำให้ต้องทำ postmortem ในตัวอย่างคือ incident ใดก็ตามเพียงครั้งเดียวที่กิน budget เกิน 20% ในสี่สัปดาห์
มันไม่ใช่การลงโทษ และตัวอย่างก็เขียนไว้แบบนั้น มันให้สิทธิ์ทีมเอา reliability มาก่อน เมื่อข้อมูลบอกว่า reliability คือปัญหา ถ้าสามฝ่ายตกลงกันไม่ได้ว่าจะบังคับใช้มัน ก็แปลว่า SLO นั้นผิด และต้องคุยกันใหม่อีกรอบ
ใช้ตอนไหนดี
- service ที่ user ใช้ตรง ๆ และมี traffic มากพอให้อัตราส่วนมีความหมาย รวมถึง platform หรือ API ที่ทีมอื่นสร้างต่อยอดอยู่
- ที่ไหนก็ตามที่ความเร็วในการ release กับ reliability ดึงกันไปคนละทาง: budget เปลี่ยนการเถียงให้เป็นตัวเลขที่ทั้งสองฝั่งยอมรับไว้ก่อนแล้ว
- เอามาแทน alert ที่ดูจากต้นเหตุ (CPU, memory, health check ที่ fail ครั้งเดียว) ด้วย page ไม่กี่ตัวที่บอกสิ่งที่ user รู้สึกจริง ๆ
- ใช้กับ data pipeline และ storage ได้ด้วย โดยใช้ freshness, correctness, coverage หรือ durability เป็น SLI
- ไม่ค่อยเหมาะถ้าไม่มีใครพร้อมจะลงมือทำอะไรตามผลที่ได้ ส่วน SLO ที่ไม่มี policy ที่ตกลงกันไว้ก็เป็นแค่ตัวเลขอีกตัวบน dashboard
ได้อะไร เสียอะไร
- traffic น้อยทำให้การคำนวณพัง ที่ 10 request ต่อชั่วโมง ความล้มเหลวครั้งเดียวคือ error rate 10% และ burn 100× มัน page ทันที และ SLO 99.9% ก็ยอมให้พลาดได้แค่เจ็ดครั้งใน 30 วัน ทางเลือกคือ synthetic traffic, ใช้ SLO เดียวกับกลุ่มของ service เล็ก ๆ ที่เกี่ยวข้องกัน, เปลี่ยนให้ความล้มเหลวครั้งเดียวกระทบ user น้อยลง (client retry พร้อม backoff, fallback) หรือลด SLO ลงหรือใช้ window ที่ยาวขึ้น
- เป้าสุดโต่งต้องใช้ alert คนละแบบ ถ้า SLO 90% การล่มทั้งระบบกิน budget แค่ 1.4% ในหนึ่งชั่วโมง กฎ 14.4× เลยไม่มีวันดัง ส่วนที่ 99.999% การล่มทั้งระบบใช้ budget ทั้งเดือนหมดใน 26 วินาที เร็วกว่า pager ไหน ๆ ระดับนั้นต้องออกแบบไว้ตั้งแต่แรก เช่น เปิดการเปลี่ยนแปลงให้ user 1% ก่อนแบบ Canary Release ไม่ใช่ใช้ alert คอยป้องกัน
- budget มอง event ที่ไม่ดีทุกตัวเหมือนกันหมด workbook เองก็เตือนไว้: outage สี่ชั่วโมงครั้งเดียวน่าจะทำให้ user ไม่พอใจน้อยกว่า error rate 0.5% ที่เกิดตลอดเวลา แต่ budget แบบนับ request คิดราคาเท่ากัน
- SLI เป็นแค่ตัวแทน ถ้า outage และ support ticket ไม่โผล่มาเป็น budget ที่ใช้ไป หรือ budget ถูกใช้ไปโดยที่ user ไม่รู้สึก ก็ต้องแก้ตัวชี้วัด: ย้ายจุดวัดให้ใกล้ user ขึ้น หรือครอบคลุมสิ่งที่ user ทำให้มากขึ้น
- dependency กำหนดเพดาน dependency ที่สำคัญต้อง reliable อย่างน้อยเท่ากับ user journey ที่ต้องใช้มัน ถ้าไม่ถึง ก็ต้องออกแบบเลี่ยงด้วย caching, store-and-forward หรือ graceful degradation การคำนวณ redundancy (สอง zone ที่ 99.9% ได้ 99.9999%) ทำให้เข้าใจผิด เพราะความล้มเหลวแทบไม่เคยเป็นอิสระต่อกัน ให้ตัดสินไว้ใน policy ว่าจะทำยังไงเมื่อ outage ของทีมอื่นใช้ budget ของเราไป workbook บอกว่าการ freeze ไปเลยอยู่ดีคือสิ่งที่ทำให้ user พอใจกว่า
- reliable เกินไปก็เป็นปัญหา user จะพึ่งพาสิ่งที่เราส่งมอบจริง ไม่ใช่สิ่งที่เราสัญญาไว้ SRE ของ Google ทำให้แน่ใจว่า global lock service อย่าง Chubby ทำได้ตาม SLO แต่ไม่เกินไปมาก: ในไตรมาสที่ไม่มี outage จริงเลย เขาจะทำให้มันล่มเองโดยตั้งใจหนึ่งครั้ง จะได้ไม่มีใครสร้างระบบบน reliability ที่ไม่เคยสัญญาไว้
- parameter เยอะ สามกฎ หก window และ threshold ของแต่ละตัว ให้เลือกมาชุดเดียวแล้วใช้กับทุก service โดยจัดกลุ่มเป็นไม่กี่ class ที่มีความต้องการคล้ายกัน แทนที่จะจูน alert แต่ละตัวเอง
ข้อควรรู้ตอนลงมือทำ
- Prometheus recording rule หนึ่งตัวต่อหนึ่ง window คำนวณ error ratio ไว้ล่วงหน้า (workbook ตั้งชื่อว่า
job:slo_errors_per_request:ratio_rate1h,ratio_rate5mไปเรื่อย ๆ) แล้ว alerting rule ก็เทียบแต่ละคู่กับ burn rate คูณ budget:ratio_rate1h > 14.4 * 0.001 and ratio_rate5m > 14.4 * 0.001ทั้งนี้ workbook แนะนำว่าไม่ควรเพิ่ม durationfor:ให้ alert พวกนี้ เพราะมันทำให้ทุก alert ช้าลงเท่ากันหมด ไม่ว่า outage จะหนักแค่ไหน - Generator Sloth และ Pyrra รับนิยาม SLO สั้น ๆ (query ของ error และของทั้งหมด, objective, window) แล้วเขียน recording rule และ alert แบบ multiwindow, multi-burn-rate ให้เอง ส่วน Pyrra ยังมี UI ที่แสดงทุก SLO พร้อม budget ที่เหลือด้วย
- OpenSLO เป็น YAML format ที่ไม่ผูกกับ vendor สำหรับข้อมูลชุดเดียวกัน: object
SLI,SLOและAlertPolicy,timeWindowแบบ rolling หรือแบบตรงกับปฏิทิน,budgetingMethod(Occurrences,TimeslicesหรือRatioTimeslices) และเงื่อนไข alert แบบburnrateตัว Sloth รับ format v1alpha รุ่นเก่ากว่าของ OpenSLO - Managed service ตัวอย่าง service monitoring ของ Google Cloud (ส่วนหนึ่งของ Cloud Monitoring) มี SLO แบบ request-based และ windows-based, compliance period แบบตามปฏิทินหรือแบบ rolling และ alerting policy แบบ burn rate โดยที่ lookback period ของมันเกิน 24 ชั่วโมงไม่ได้ Google เลยแนะนำให้เริ่มจาก alert แบบ fast-burn (10× ในหนึ่งหรือสองชั่วโมง) กับแบบ slow-burn (2× ใน 24 ชั่วโมง) ส่วน Amazon CloudWatch Application Signals มี SLO แบบ request-based และ period-based พร้อม burn-rate alarm
- ป้อน SLI จาก telemetry ที่มีอยู่แล้ว: เริ่มจาก metric ของ load balancer ก่อน แล้ว trace กับ log ก็อธิบายว่าทำไม budget ถึงถูกเผา (ดู Distributed Tracing, Centralized Logging และ Telemetry Pipeline)
- ผูก release ไว้กับ budget deployment pipeline อ่าน budget ที่เหลือได้ แล้วกัก release feature ไว้ตอนที่มันหมด นี่คือประตูใน diagram ส่วน canary analysis กับ feature flag ช่วยให้ต้นทุนของแต่ละ release เล็กลง
- เขียนไว้และกลับมาทบทวน บันทึก SLO และ policy ของมันพร้อมคนเขียน คนอนุมัติ และวันทบทวน workbook แนะนำให้ทบทวนทุกเดือนในช่วงแรก และให้ถือเป้าที่เข้มกว่าแต่ยังทำไม่ได้เป็น SLO แบบ aspirational ที่ติดตามไว้แต่ไม่ trigger policy