Prometheus မှာ PromQL ဘယ်လိုရေးမလဲ

PromQL ဆိုတာ SQL လိုပဲ system ထဲကနေ လိုအပ်တဲ့ data တွေဆွဲထုတ်တဲ့ query language တခုဖြစ်တယ်။ ရှေ့အခေါက်တွေမှာ ကျွန်တော်တို့ Prometheus အခြေခံနဲ့ Prometheus ရဲ့ data type တွေအကြောင်းကို ပြောခဲ့ကြပြီးပြီမို့ အခု အဲဒီ data တွေကို Prometheus ဆီက ဘယ်လိုဖတ်မလဲဆိုတဲ့အကြောင်း ပြောကြမယ်။

Prometheus လို့ပြောရင် metric scraping လုပ်တဲ့ feature ရယ်၊ metric တွေ process လုပ်ဖို့ feature နဲ့သိမ်းဖို့ database ရယ်၊ PromQL တွေကို evaluate လုပ်တဲ့ feature ရယ်၊ နောက် Web UI feature ရယ်၊ နောက်တခြား meta feature လေးတွေလည်း အများကြီး ပါပေမယ့် Prometheus က monolithic binary အနေနဲ့ပဲ ရှိပါတယ်။ Loki လိုမျိုး microservice စတိုင်လ် တပိုင်းချင်းစီ သီးသန့် install လို့မရပါဘူး။ ဒီတော့ database ရော၊ Query interface ရောတပါတည်း Prometheus Server ထဲပါပြီးသားဖြစ်တယ်။

Prometheus မှာသုံးထားတဲ့ database ကို time series database (tsdb) လို့ခေါ်တယ်။ tsdb က Apache Cassandra, Apache HBase နဲ့ Amazon DynamoDb တွေကို တုထားတဲ့ ခပ်ရှင်းရှင်း LSM inspired database မျိုးဖြစ်တယ်။ Cassandra, HBase အဲဒီဥပမာတွေနဲ့ မတူတာက Prometheus ရဲ့ tsdb မှာ arbitrary write တွေမရှိတဲ့အတွက် chronological order ကိုအ​​ခြေခံတဲ့ head block ဆောက်လို့ရစေတယ်။ နောက်တချက်က head block ကို disk ပေါ် flush တာက size အပေါ်မမူတည်ပဲ duration အပေါ်မူတည်တာဖြစ်ပြီး အဲဒီ duration windown ကိုလည်း ကိုယ်က သတ်မှတ်ထားပေးလို့ရပါတယ်။

ကျွန်တော်တို့ရဲ့ application တွေကနေ instrument လုပ်နေတဲ့ metrics တခုစီတိုင်းကို tsdb ထဲမှာ သီးသန့် time series တခုအနေနဲ့ သိမ်းပါတယ်။ metric နာမည်တူရင်တောင်မှ label set မတူရင် ဒါက dimensionality မတူတာဖြစ်လို့ ဒီ metric 2 ခုကို မတူညီတဲ့ time series နှစ်ခုလို့ပဲ Prometheus ကယူဆမှာပါ။ တခါ label ရဲ့တန်ဖိုးတွေ မတူပြန်ရင်လည်း cardinality ကွဲပြန်တဲ့အတွက် time series တွေထပ်ပွားလာဦးမှာဖြစ်တယ်။ ဒါကြောင့် metric တခုမှာ label တွေတပ်ပေးတဲ့အခါ cardinality သိပ်များမယ့် label မျိုးတွေကို တတ်နိုင်သလောက် ရှောင်ပါလို့ အကြံပေးချင်တယ်။ ဥပမာ ဒီအောက်က ဥပမာမှာဆိုရင် user 100 ရှိရင် time series 100 ရှိပြီး user n အရေအတွက်မှာ time series အရေအတွက်ကလည်း n ရှိလာပါလိမ့်မယ်။

http_requests_total{user="alice"}  
http_requests_total{user="bob"}  
http_requests_total{user="charlie"}

time series တခုတိုးလာခြင်းက အဲဒီ time series ကို scrape ဖို့၊ relabeling (နောက်အပိုင်းတွေမှာ ပြောမယ်) အတွက် compute လုပ်ဖို့၊ tsdb ထဲမသိမ်းခင် index လုပ်ဖို့၊ disk ပေါ်မသိမ်းခင် memory ထဲရေးထားဖို့၊ disk ပေါ်သိမ်းဖို့ စသဖြင့် အလုပ်တွေအကုန် တိုးလာမှာဖြစ်လို့ သေချာစဥ်းစားဖို့ လိုပါတယ်။

PromQL query ရေးဖို့ဆိုရင် time series တွေကို နားလည်ထားဖို့လိုတယ်။ time series တခုစီကို chronological order အရ စီထားတဲ့ data point တွေနဲ့ဆောက်ထားတယ်။ data point တခုစီမှာ timestamp နဲ့ value ဆိုပြီး တန်ဖိုးနှစ်ခုပါတယ်။ timestamp တခုနဲ့တခု ဘယ်လောက်ခြားလဲဆိုတာကတော့ scrape လုပ်တဲ့ frequency အပေါ် မူတည်မယ်။ 15 စက္ကန့်တခါလို့ ပြောထားရင် timestamp တခုနဲ့တခုက 15 စက္ကန့်နီးပါးခြားမယ်ပေါ့။ ဘာလို့အတိအကျ မခြားနိုင်ရတာလဲဆိုရင်တော့ ကိုယ်သတ်မှတ်ထားတဲ့အတိုင်း Prometheus က 15 စက္ကန့်တခါ best effort ပုံစံ scrape လုပ်ပေမယ့် transit မှာကြာမယ့်အချိန်ရယ်၊ တခြား CPU cycle အလှည့်တွေရယ်ပေါင်းပြီး nano second အထိ မတိကျနိုင်တော့ဘူးပေါ့။

ကျွန်တော်တို့ metric တခုကို query ဖတ်လိုက်ရင် အောက်က ဥပမာလို သူ့နာမည်နဲ့ time series တွေအကုန် ကျလာမယ်။ metric နာမည်တူပေမယ့် ရလာတဲ့ time series တွေက dimensionality ဒါမှမဟုတ် cardinality မတူကြဘူး။

http_requests_total

# Results
http_requests_total{method="GET", endpoint="/api/users"} 120
http_requests_total{method="POST", endpoint="/api/users"} 35
http_requests_total{method="GET", endpoint="/api/orders"} 87
http_requests_total{method="POST", endpoint="/api/orders"} 42
http_requests_total{method="GET", endpoint="/api/products", user="alice"} 15
http_requests_total{method="GET", endpoint="/api/products", user="bob"} 20

တကယ်လို့ ပိုတိကျချင်ရင် equal နဲ့ not equal, regex နဲ့ negative regex operator တွေသုံးပြီး ကိုယ်လိုချင်တဲ့ time series တွေကိုပဲ ရွေးယူလို့ရတယ်။

http_requests_total{status=~"2.."}  
        
# Results
http_requests_total{method="GET", endpoint="/api/users", status="200"} 100
http_requests_total{method="GET", endpoint="/api/orders", status="200"} 70
http_requests_total{method="POST", endpoint="/api/users", status="201"} 30

http_requests_total{method!="POST"}  

# Results
http_requests_total{method="GET", endpoint="/api/users"} 120
http_requests_total{method="GET", endpoint="/api/orders"} 87
http_requests_total{method="GET", endpoint="/api/products", user="alice"} 15
http_requests_total{method="GET", endpoint="/api/products", user="bob"} 20

တခုကြည့်စေချင်တာက ရလာတဲ့ time series တခုချင်းစီမှာ တန်ဖိုးက တခုတည်းတွေ ဖြစ်နေတာပဲ။ ဒါကို instant vector လို့ခေါ်တယ်။ ဘာလို့လဲဆိုတော့ Prometheus ရဲ့ PromQL interface ကလက်ရှိ timestamp ကို time series ထဲ anchor point အဖြစ် သတ်မှတ်ပြီး အဲရှေ့ကပ်ရပ် value ကိုယူလာပေးတဲ့အတွက်ပါ။ ပြောရရင် နောက်ဆုံး value ကိုယူလာပေးတဲ့သဘောပေါ့။ ဒါကြောင့် လက်ရှိ timestamp ကိုသုံးတယ်ဆိုတာ latest data ကိုဖတ်ချင်တယ်ဆိုတဲ့သဘော သက်ရောက်တယ်။ ကိုယ်က time-shifting metric တွေလိုချင်ရင် @ နဲ့ offset modifier တွေသုံးလို့ရတယ်။ တခုချင်းစီမှာ မသိသာပေမယ့် အသုံးတည့်တဲ့ use case တွေရှိကြတယ်။

http_requests_total @ 1609746000 
http_requests_total offset 15m

တကယ်လို့ ကိုယ်က instant vector ကိုမလိုချင်ဘူး။ နောက်ဆုံး 5 မိနစ်အတွင်း scrape ခဲ့တဲ့ data တွေအကုန်ပြစေချင်တာဆိုရင် range vector ကိုတောင်းဖို့လိုတယ်။ အဲဒီအတွက် Prometheus မှာ syntax ကိုဒီလိုရေးတယ်။

http_requests_total[5m]

# Results
2343894600 @ 20
2343894660 @ 22
2343894720 @ 25
2343894780 @ 28
2343894840 @ 30

ဒီမှာလည်း start နဲ့ end ကိုသုံးပြီး anchor point တွေသတ်မှတ်လို့ရပါတယ်။ နောက်တချက်က range vector တွေကို query ရတာ ဘာမှမခက်ပေမယ့် အဓိပ္ပါယ်ရှိတဲ့ data တွေအဖြစ် ကြည့်ဖို့ဆိုရင်တော့ data type က counter လား gauge လားဆိုတဲ့အပေါ် မူတည်ပြီး gauge အတွက်ဆိုရင် deriv, delta နောက် counter အတွက် rate, increase စတဲ့ function တွေကိုသုံးပေးဖို့လိုတယ်။

rate(cpu_usage_total[5m])  
delta(memory_usage_bytes[5m])

PromQL မှာအတော်လေး သဘောကျမိတဲ့အချက်က label aggregation လုပ်လို့ရတာပဲ။ ဥပမာ kubernetes cluster ထဲက service တွေအနေနဲ့ request volume ဘယ်လောက်ကို handle လုပ်နေရသလဲလို့ ရှာလိုက်ရင် ရှိရှိသမျှ Pod နာမည်တခုကို time series တခုနှုန်းကျလာမယ်။ ဒါပေမယ့် ကိုယ်က Pod တွေတခုချင်းအတွက် မသိချင်ဘူး။ ဥပမာ authentication service ကို request ဘယ်လောက်ပို့နေလဲဆိုတာပဲ သိချင်ရင် sum by (deployment) ကိုသုံးပြီး per deployment အတွက် တန်ဖိုးကို သိနိုင်မယ်။ အဲလိုပဲ per namespace နဲ့ကြည့်ချင်ရင် sum by (namespace) ကိုသုံးလို့ရတယ်။ per namespace မှာမှ အမြင့်ဆုံးကို ကြည့်ချင်ရင် max by (namespace) ဒါမှမဟုတ် အမြင့်ဆုံး ၃ ခု၊ ၅ ခု၊ n ခုစသဖြင့် လိုချင်ရင် sum by ကိုပဲ topk နဲ့အုပ်တာမျိုး လုပ်လို့ရတယ်။

sum by (deployment) (rate(http_requests_total[5m]))  
max by (namespace) (memory_usage_bytes)  
topk(5, rate(errors_total[5m]))

ကိုယ့် system ထဲမှာ ဘာဖြစ်နေလဲ သိရဖို့ဆိုရင် query ကောင်းကောင်းရေးပြီး ဆွဲထုတ် ဖတ်နိုင်ဖို့လိုပါတယ်။ တခါ ကိုယ်လိုချင်တဲ့ metric တန်ဖိုးတွေရလာတဲ့အခါမှာ လုပ်သင့်တာတွေ မှန်မှန်ကန်ကန် အချိန်မှီ လုပ်သွားနိုင်ဖို့ လိုပါတယ်။ ဥပမာ Pod တွေ pending ဖြစ်နေသလား။ ကျွန်တော်တို့ Scheduler ကိုစစ်ရပါမယ်။ Error Rate တအားမြင့်နေလား။ Anomaly တခုခုရှိမရှိ စစ်သင့်ပါတယ်။ ဒီအတွက် နောက်တပိုင်းမှာ ကျွန်တော်တို့ PromQL ကိုအသုံးချပြီး alert တွေဘယ်လိုဆင်မလဲဆိုတာ ဆက်ပြောကြပါမယ်။