เมื่อเข้าใจ PromQL ระดับพื้นฐานแล้ว ขั้นต่อไปคือการใช้ความสามารถที่ลึกขึ้น เช่น subquery, vector matching (join ข้าม metric), group operator และเทคนิคจัดการ high-cardinality ที่จะเปลี่ยนการเขียน query ธรรมดาให้ตอบโจทย์งานจริงที่ซับซ้อนได้ บทความนี้จะลงรายละเอียดเรื่องเหล่านี้พร้อมตัวอย่างที่ใช้งานได้จริง
PromQL ขั้นสูงไม่ได้แปลว่า syntax ยากขึ้น แต่คือการใช้ความสามารถที่มีอยู่ให้ได้ประสิทธิภาพและความแม่นยำสูงสุด ตั้งแต่การรวมข้อมูลข้าม metric ต่างตัว การคำนวณ rate จากผลรวมของ query ซ้อน ไปจนถึงการสร้าง view ระดับ service ด้วยการ join
Subquery
Subquery คือการเอา query ที่คืน instant vector มาทำเป็น range vector ในตัวมัน — syntax คือ [range:resolution] หลัง expression ใช้เมื่อต้องการคำนวณ rate ของ query ที่มี aggregation อยู่แล้ว:
# max ของ rate ใน 1 ชั่วโมง โดย sample ทุก 1 นาที
max_over_time(
sum(rate(http_requests_total[5m]))[1h:1m]
)
# avg ของ 95th percentile latency ใน 24 ชั่วโมง
avg_over_time(
histogram_quantile(0.95,
sum by (le)(rate(http_request_duration_seconds_bucket[5m]))
)[1d:5m]
)
# quantile over time
quantile_over_time(0.99, up[1h:])
Subquery แพงกว่า query ธรรมดา — Prometheus ต้องประมวลผล inner query หลายครั้งตาม resolution ใช้เท่าที่จำเป็นและหลีกเลี่ยงใน alert rule ที่รันบ่อย
Vector Matching พื้นฐาน
เมื่อ operator ทำงานกับ 2 instant vector เช่น A / B Prometheus จะจับคู่ series โดย matching labels ตาม default คือ “ทุก label ต้องตรงกัน”:
# one-to-one matching (default) — ต้องมี label ทุกตัวตรงกัน
http_errors_total / http_requests_total
# ignoring — ไม่สนใจ label ที่ระบุ
http_errors_total / ignoring(code) http_requests_total
# on — ใช้เฉพาะ label ที่ระบุในการจับคู่
http_errors_total / on(service, instance) http_requests_total
Many-to-One / One-to-Many
บางกรณี series ทางซ้ายมีหลายตัวที่ match กับ series ทางขวาตัวเดียว ต้องใช้ group_left หรือ group_right:
# labels เพิ่มเข้ามาจาก instance_info (metadata metric)
# หา pod ที่รัน version เก่า
kube_pod_info
* on(pod) group_left(node, host_ip)
kube_node_info
# คำนวณ cost ต่อ pod โดยเอา cost_per_hour (per node) มารวม
container_cpu_usage_seconds_total
* on(node) group_left
node_cost_per_hour
Group operator: group_left(<labels>) บอกว่าด้านซ้ายเป็น “many” และจะดึง label จากด้านขวามาเพิ่มให้ series ซ้าย สลับด้านได้ด้วย group_right
Error Budget และ SLO
ตัวอย่างการใช้ vector matching เพื่อคำนวณ error budget จาก SLO 99.9%:
# error rate 5m
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
# error budget remaining (SLO 99.9%, window 30 วัน)
1 - (
(sum(increase(http_requests_total{status=~"5.."}[30d]))
/ sum(increase(http_requests_total[30d])))
/ (1 - 0.999)
)
# burn rate alert — spend budget 2% ใน 1 ชั่วโมง = crash trajectory
(
sum(rate(http_requests_total{status=~"5.."}[1h]))
/ sum(rate(http_requests_total[1h]))
) > (14.4 * (1 - 0.999))
จัดการ High Cardinality
High cardinality คือ label combination จำนวนมหาศาลที่ทำให้ Prometheus ช้าและกิน memory เทคนิคการรับมือ:
- ใช้
label_replace()จัดกลุ่ม label ให้น้อยลงก่อน aggregate - Drop label ที่ไม่จำเป็นด้วย
metric_relabel_configsในระดับ scrape config - ใช้
topk()/bottomk()แทนการดึงทุก series - ตั้ง
sample_limitและtarget_limitกันระเบิดโดยไม่ตั้งใจ - ตรวจสอบ cardinality ด้วย
count({__name__=~".+"})หรือtopk(10, count by (__name__)({__name__=~".+"}))
label_replace และ label_join
# เปลี่ยน label pod ที่มี suffix hash ให้เหลือชื่อ deployment
label_replace(
kube_pod_info,
"deployment", # new label
"$1", # replacement
"pod", # source label
"(.+)-[a-f0-9]+-.*" # regex capture group
)
# join 2 labels ให้เป็น label ใหม่
label_join(
up,
"target", # new label
":", # separator
"instance", "job" # source labels
)
Aggregation ขั้นสูง
# count_values — นับจำนวน series ตามค่า value
# เช่น มี pod กี่ตัวที่ใช้ memory ในช่วงเดียวกัน
count_values("memory_gb", round(container_memory_usage_bytes / 1e9))
# quantile over groups
quantile by (job) (0.99, rate(http_requests_total[5m]))
# group — ลดขนาดโดยรวม series
group by (service) (up)
Histogram และ Heatmap
Histogram metric ต้องใช้ histogram_quantile() กับ rate ของ bucket — รวมกลุ่มผิดจะได้ผลลัพธ์ผิดทันที:
# ถูก — aggregate ที่ le
histogram_quantile(0.95,
sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
# ผิด — aggregate ก่อน histogram_quantile โดยไม่รักษา le
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket[5m])))
# หลาย percentile พร้อมกันด้วย label manipulation
histogram_quantile(0.50, sum by (le,job) (rate(http_request_duration_seconds_bucket[5m])))
or
histogram_quantile(0.95, sum by (le,job) (rate(http_request_duration_seconds_bucket[5m])))
or
histogram_quantile(0.99, sum by (le,job) (rate(http_request_duration_seconds_bucket[5m])))
Predict และ Trend Analysis
# คาดการณ์ว่า disk จะเต็มเมื่อไหร่ — linear regression 1 ชั่วโมง
predict_linear(node_filesystem_free_bytes[1h], 4 * 3600) < 0
# derivative — อัตราเปลี่ยนต่อวินาที (ใช้กับ gauge)
deriv(node_memory_MemAvailable_bytes[5m])
# smoothing ด้วย avg_over_time
avg_over_time(rate(http_requests_total[5m])[30m:])
Performance Tips
- ทำ query selectivity สูงสุดก่อน ลด time series ที่ต้องประมวลผลตั้งแต่ต้น
- ใช้
rate()ก่อนsum()— ไม่ reverse - หลีกเลี่ยง subquery ใน alert rule ที่รันทุก 30s
- Pre-aggregate ด้วย recording rule สำหรับ query ที่ใช้ซ้ำ
- Instrument
prometheus_engine_query_duration_secondsเพื่อติดตาม query ที่ช้า - ระวัง
__name__=~".+"ที่กระทบทุก metric — ใช้เฉพาะตอน explore
สรุป
PromQL ขั้นสูงเปิดประตูสู่การวิเคราะห์ที่ลึกและแม่นยำขึ้น ตั้งแต่ subquery ที่คำนวณ query ซ้อน vector matching ที่ join ข้าม metric ไปจนถึง label manipulation และการคาดการณ์ด้วย linear regression — เทคนิคเหล่านี้คือตัวแยกระหว่างผู้ใช้ Prometheus มือใหม่กับผู้ที่ใช้ได้อย่างเชี่ยวชาญ
Query ที่ซับซ้อนมักส่งผลกับ performance ของ server ดังนั้นควรตรวจสอบ query_duration และ optimize ด้วย recording rule เมื่อจำเป็น เพื่อให้ dashboard และ alert ทำงานได้เสถียรในระยะยาว

