เมื่อ Prometheus ต้องประมวลผลคำสั่งที่ซับซ้อนหรือรวมข้อมูลจากเมตริกจำนวนมากซ้ำ ๆ ใน Dashboard และ Alert การเรียก PromQL แบบสดทุกครั้งจะเปลือง CPU และทำให้ Grafana ค่อย ๆ ช้าลงอย่างเห็นได้ชัด บทความนี้จะอธิบาย Recording Rules — กลไกการ pre-compute เมตริกเพื่อให้ query ที่มีต้นทุนสูงถูกคำนวณล่วงหน้าและเก็บเป็นเมตริกใหม่ พร้อมตัวอย่าง rule file, naming convention, การวางแผน evaluation interval และ anti-pattern ที่ควรหลีกเลี่ยง
หากคุณมี Dashboard ที่ใช้เวลาโหลดหลายวินาที หรือ Alert ที่ประมวลผลช้าจนทริกเกอร์สาย Recording Rules คือเครื่องมือที่ควรรู้จักเป็นอันดับต้น ๆ ก่อนจะไปแก้ด้วยการเพิ่ม hardware
Recording Rules คืออะไร
Recording Rules คือกฎที่บอกให้ Prometheus ประมวลผล PromQL expression ตามรอบเวลาที่กำหนด (โดยปกติ 15-60 วินาที) แล้วบันทึกผลลัพธ์เป็นเมตริกใหม่ใน TSDB ทำให้ query ที่ใช้เมตริกใหม่นี้ทำงานเหมือนกับการอ่านเมตริกธรรมดา ไม่ต้องคำนวณซ้ำทุกครั้งที่ Dashboard หรือ Alert เรียกใช้
ประโยชน์หลักมี 3 ประการ ประการแรกคือลดเวลา query ของ Dashboard จากวินาทีเหลือมิลลิวินาที ประการที่สองคือลดภาระของ Prometheus เอง เพราะคำสั่งที่ต้องอ่าน series หลายหมื่นตัวจะถูกคำนวณครั้งเดียวแล้วแชร์ทุกคน ประการที่สามคือทำให้ Alert Rule มีความเสถียรเพราะคำนวณจากข้อมูลที่ pre-compute แล้ว ไม่ขึ้นกับโหลดของเครื่องตอนที่ query
เมื่อไหร่ควรใช้ Recording Rules
- Query ที่ใช้ใน Dashboard หลายแผง แต่ละแผงคำนวณเหมือนกันซ้ำ ๆ
- Aggregation ที่ครอบคลุม series จำนวนมาก (หลักพันขึ้นไป) เช่น
sum(rate(http_requests_total[5m])) - Query ที่ใช้ใน Alert Rule หลายตัว และต้องการให้คำนวณจากค่าชุดเดียวกัน
- Query ที่ประกอบด้วย
histogram_quantileบน histogram ที่มี bucket เยอะ - Expression ซับซ้อนที่ใช้ subquery หรือ
label_replaceหลายชั้น
ในทางกลับกัน query ที่เรียกใช้เพียงครั้งเดียวหรือนาน ๆ ครั้ง ไม่ควร pre-compute เพราะจะเพิ่มภาระการเขียน TSDB โดยไม่จำเป็นและสร้างเมตริกที่ไม่มีใครใช้
โครงสร้างไฟล์ Recording Rules
Recording Rules ถูกเก็บในไฟล์ YAML แยกจาก prometheus.yml และอ้างถึงผ่านคีย์ rule_files โครงสร้างพื้นฐานประกอบด้วย groups ที่ห่อ rules หลายกฎ แต่ละ group มี interval กำหนดรอบประมวลผลของตัวเอง
groups:
- name: http_recording_rules
interval: 30s
rules:
- record: job:http_requests:rate5m
expr: sum by (job) (rate(http_requests_total[5m]))
- record: job:http_request_duration:p95
expr: histogram_quantile(0.95,
sum by (job, le) (
rate(http_request_duration_seconds_bucket[5m])))
- record: job:http_errors:ratio_rate5m
expr: |
sum by (job) (rate(http_requests_total{status=~"5.."}[5m]))
/
sum by (job) (rate(http_requests_total[5m]))
ในไฟล์ prometheus.yml เพิ่มบรรทัด rule_files ให้ชี้ไปที่ไฟล์ดังกล่าว Prometheus จะโหลดและประมวลผลตาม interval ที่กำหนดทันทีหลัง reload config
rule_files:
- /etc/prometheus/rules/*.yml
# reload config โดยไม่ต้อง restart
curl -X POST http://localhost:9090/-/reload
Naming Convention ที่แนะนำ
Prometheus แนะนำรูปแบบ level:metric:operations เพื่อให้ผู้อ่านรู้ได้ทันทีว่าเมตริกใหม่ผ่านการรวมด้วย label ระดับไหน และดำเนินการอะไรบ้าง รูปแบบนี้ช่วยให้ทีมที่ร่วมกันดูแลหลายสิบ rule ไม่เขียนทับกันและค้นหาเมตริกในระบบได้ง่ายขึ้น
| องค์ประกอบ | ความหมาย | ตัวอย่าง |
|---|---|---|
| level | label ที่เก็บไว้หลัง aggregation | job, instance, namespace |
| metric | ชื่อเมตริกต้นทาง (ตัด suffix) | http_requests, node_cpu |
| operations | การดำเนินการที่ทำ | rate5m, sum_irate, p95 |
ตัวอย่างชื่อที่ดีเช่น job:http_requests:rate5m หมายถึง rate 5 นาทีของ http_requests รวมให้เหลือเฉพาะ label job และ instance:node_cpu_usage:avg1m หมายถึง CPU usage เฉลี่ย 1 นาที แยกตาม instance
Evaluation Interval กับต้นทุนการประมวลผล
ทุก group ประมวลผลตาม interval ของตัวเอง (หากไม่ระบุจะใช้ global.evaluation_interval ใน prometheus.yml) ตั้งค่าถี่เกินไปจะเปลือง CPU ส่วนตั้งห่างเกินไปจะทำให้ alert ตอบสนองช้า หลักคิดที่ใช้จริงคือปรับให้สอดคล้องกับ scrape interval และความไวที่ต้องการ
- Scrape interval 15 วินาที → ตั้ง evaluation interval 15-30 วินาที
- Rule ที่ใช้สำหรับ Alert ต้อง <= for duration ของ alert
- Rule ที่ใช้ทำ Dashboard 5m+ สามารถตั้ง 60 วินาทีได้เพื่อประหยัด
- Rule หนัก ๆ ที่รวมหลาย series ให้แยกเป็น group ที่มี interval ของตัวเอง
การเรียงลำดับและพึ่งพาระหว่าง Rule
Rule ใน group เดียวกันจะถูกประมวลผลตามลำดับบนลงล่างแบบ sequential ทำให้ rule ล่าง ๆ สามารถอ้างผลลัพธ์ของ rule ก่อนหน้าได้ แต่ rule ข้าม group ไม่รับประกันลำดับ — Prometheus 2.x ขึ้นไปประมวลผล group แบบขนาน หากจำเป็นต้องอ้าง rule ข้าม group ต้องยอมรับว่าอาจได้ข้อมูลจากรอบก่อน
groups:
- name: slo_calculations
interval: 30s
rules:
# rule แรก: error rate
- record: job:http_errors:rate5m
expr: sum by (job) (rate(http_requests_total{status=~"5.."}[5m]))
# rule ที่สอง: total rate
- record: job:http_requests:rate5m
expr: sum by (job) (rate(http_requests_total[5m]))
# rule ที่สาม: error ratio (อ้างสองตัวบน)
- record: job:http_errors:ratio5m
expr: job:http_errors:rate5m / job:http_requests:rate5m
ตรวจสอบประสิทธิภาพของ Rule
Prometheus เปิดเผยเมตริกภายในเกี่ยวกับ rule ที่ endpoint /metrics ของตัวเอง ใช้ตรวจสอบว่า rule ตัวไหนช้าหรือใช้ทรัพยากรเกินควร
| เมตริก | ความหมาย |
|---|---|
| prometheus_rule_evaluation_duration_seconds | เวลาที่ใช้ประมวลผล rule แต่ละรอบ |
| prometheus_rule_group_duration_seconds | เวลารวมของทั้ง group ต่อรอบ |
| prometheus_rule_group_iterations_missed_total | จำนวนรอบที่ข้ามไป — ถ้าเพิ่มเรื่อย ๆ แปลว่า group ช้าเกิน interval |
| prometheus_rule_evaluation_failures_total | จำนวนครั้งที่ rule ประมวลผลไม่สำเร็จ |
หาก iterations_missed_total โตต่อเนื่อง ให้พิจารณาเพิ่ม interval ของ group แยก rule หนักออก หรือปรับ expression ให้มี label น้อยลง
ใช้คู่กับ Alerting Rules
วิธีที่ทีม SRE นิยมใช้คือ pre-compute ค่าที่ alert ต้องใช้ไว้ก่อน แล้วให้ Alert Rule อ้าง recording rule แทนการคำนวณใหม่ ทำให้ alert คำนวณเร็ว ประหยัดทรัพยากร และถ้าเปลี่ยน threshold ในอนาคตก็แก้ที่ alert เพียงที่เดียว
# Recording Rule (pre-compute)
- record: job:http_errors:ratio5m
expr: sum by (job) (rate(http_requests_total{status=~"5.."}[5m]))
/ sum by (job) (rate(http_requests_total[5m]))
# Alert Rule (อ้างค่าที่ pre-compute)
- alert: HighErrorRate
expr: job:http_errors:ratio5m > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "Error rate ของ {{ $labels.job }} สูงเกิน 5%"
Anti-patterns ที่ควรหลีกเลี่ยง
- สร้าง recording rule สำหรับ query ที่เรียกใช้วันละไม่กี่ครั้ง — เสียพื้นที่เก็บข้อมูลโดยไม่คุ้ม
- ใส่ rule นับร้อยใน group เดียว — ประมวลผลแบบ sequential ทำให้ rule ตัวท้าย ๆ ได้ข้อมูลช้า
- Pre-compute rate บน counter ที่มี cardinality สูงโดยไม่ลด label ก่อน — ยังคงมี series เยอะเท่าเดิม
- ใช้ชื่อเหมือนเมตริกต้นทาง เช่น
http_requests_total— สับสนกับของจริง ควรใช้รูปแบบ level:metric:operations - ไม่เก็บ rule file ไว้ใน Git — เมื่อแก้ rule แล้วผิด จะย้อนกลับไม่ได้
ทดสอบ Rule ก่อนใช้งานจริง
Prometheus มีคำสั่ง promtool สำหรับตรวจสอบไวยากรณ์ของ rule file และรัน unit test กับข้อมูลจำลอง ใช้ควบคู่กับ CI pipeline เพื่อไม่ให้ rule ที่ผิดเข้า production
# ตรวจไวยากรณ์
promtool check rules /etc/prometheus/rules/*.yml
# รัน unit test (ต้องเขียน test file แยก)
promtool test rules tests/recording_rules_test.yml
ไฟล์ test แบบพื้นฐานมีโครงสร้างดังนี้ กำหนด input series พร้อมค่าที่ทำการ simulate ตามเวลา จากนั้นระบุค่า expected ที่ rule ควรคำนวณออกมา
rule_files:
- recording_rules.yml
tests:
- interval: 15s
input_series:
- series: 'http_requests_total{job="api",status="200"}'
values: '0 10 20 30 40 50'
- series: 'http_requests_total{job="api",status="500"}'
values: '0 1 2 3 4 5'
promql_expr_test:
- expr: job:http_errors:ratio5m
eval_time: 5m
exp_samples:
- labels: 'job:http_errors:ratio5m{job="api"}'
value: 0.1
สรุป
Recording Rules เป็นเครื่องมือคลาสสิกของ Prometheus ที่ให้ผลตอบแทนสูงต่อความพยายาม เพียงย้าย query ที่ใช้บ่อยและหนักมาเป็น rule แยก Dashboard จะโหลดเร็วขึ้น Alert คำนวณเร็วขึ้น และทรัพยากรของ Prometheus ก็ถูกใช้อย่างมีประสิทธิภาพมากขึ้น กุญแจสำคัญคือเลือก query ที่คุ้มค่าต่อการ pre-compute วางแผน naming convention ให้เข้าใจตรงกัน ตั้ง interval ให้เหมาะกับการใช้งาน และตรวจ iterations_missed_total เป็นระยะ
สำหรับทีมที่เพิ่งเริ่มใช้ Prometheus ในขนาด production ขอแนะนำให้ตั้ง rule ชุดแรกกับ 3 สิ่งที่ได้ผลแน่นอน: error rate ratio, request rate แยกตาม service, และ p95 latency จาก histogram — สามตัวนี้ครอบคลุม Dashboard และ SLO ส่วนใหญ่ที่องค์กรต้องการดูทุกวัน

