Quantile Metrikleri Nasıl Doğru Kurulur
Merhabalar, bu yazımda metriklerin sıkça gözden kaçan ama production'da en çok canımızı yakan kısmına, quantile metriklerine bakacağız. Servisin ortalama latency'sine 200ms diye bakıp rahatlamış, sonra desteğe gelen 'site yavaş' ticket'larını anlamlandıramamış olanlardansanız, hoş geldiniz. Lafı uzatmadan başlayalım.
Bir endpoint'in ortalaması her zaman yalan söyler. 1000 isteğin 990'ı 50ms'de dönüyorsa, kalan 10'u 5 saniyede dönse bile aritmetik ortalama yaklaşık 100ms gibi gözükür ve dashboard'da hayat güzeldir. Gel gör ki o 10 kullanıcı için site çökmüş demektir. İşin aslı şu: kullanıcı deneyimi ortalamada değil, kuyrukta yaşanır. Bu yüzden p95 ve p99'a bakmadan üretim sistemini izlemek bana sorarsanız neredeyse körlemesine uçmak gibi.
Histogram mi Summary mi?
Prometheus dünyasında quantile hesaplamak için iki yol var: histogram ve summary. İkisi de aynı problemi farklı yerden çözüyor.
Summary, quantile'i uygulama içinde, yani client tarafında hesaplar. t-digest veya CKMS gibi bir streaming algoritmasıyla yaklaşık dağılım tutulur, scrape anında sana p50, p99 gibi hazır değerler verilir. Avantajı: hata payı çok küçük ({0.99: 0.001} dersen p99'u %0.1 hatayla bilirsin). Dezavantajı: bu değerler toplanamaz. İki pod'un p99'unun ortalamasını alıp 'genel p99 budur' diyemezsin, matematiksel olarak yanlıştır.
Histogram ise gözlemleri önceden tanımladığın bucket'lara dağıtır. le=0.1 bucket'ında 'değeri 100ms ve altında olan kaç istek geldi' bilgisi durur. Quantile hesabı sorgu zamanında, histogram_quantile() ile bucket'lar arasında interpolasyon yapılarak çıkar. Bucket sayaçları sayı olduğu için pod'lar arasında toplanabilir, kümeleyebilir, region bazında kırabilirsin.
Şahsi tercihim çoğu zaman histogram. Birden fazla instance'ı olan herhangi bir servis için summary'nin pratik bir faydası yok; SLO konuşurken tek tek pod'un p99'unu değil, sistemin geneline bakarsın.
Bucket sınırlarını seçmek
Histogram'ın can damarı bucket sınırları. Yanlış sınırlar p99'u tamamen yalan yapar. Örneğin SLO'nuz '99% istek 200ms altında' ise ve bucket'larınız [1, 5, 10] saniyeyse, histogram_quantile(0.99, ...) size hep +Inf'a yakın saçma değerler döndürür çünkü granularite yok.
Pratik kural: bucket'lar SLO eşiklerini içermeli ve veri dağılımının yoğun olduğu bölgede sık olmalı. Bir HTTP API için makul bir başlangıç:
buckets:
- 0.005 # 5ms - cache hit
- 0.01
- 0.025
- 0.05
- 0.1 # SLO target
- 0.25
- 0.5 # SLO degraded
- 1.0
- 2.5
- 5.0
- 10.0 # timeout esigi
Burada 0.1 ve 0.5 SLO eşiklerine denk geliyor; bu sınırlar bucket boundary olarak girince histogram_quantile interpolasyonu da çok daha doğru sonuç veriyor. Eğer servisinin latency dağılımını henüz bilmiyorsan, Prometheus 2.40+ ile gelen native histogram'a bakmanı öneririm; sınırları kendisi büyütüp küçültüyor, manuel ayar yükünü kaldırıyor.
histogram_quantile ile sorgu
Asıl sihir PromQL tarafında. p99'u tüm instance'lar arasında toplayarak hesaplamak için:
histogram_quantile(
0.99,
sum by (le) (rate(http_request_duration_seconds_bucket[5m]))
)
Buradaki kritik nokta sum by (le). Eğer by (le) demezsen Prometheus farklı pod'ların aynı bucket'ını ayrı ayrı tutar ve histogram_quantile her seri için ayrı çalışır. Endpoint bazında kırmak istersen by (le, path) yaparsın, region bazında istersen by (le, region). Bu kadar basit; ve bu basitlik tam olarak histogram'ın summary'ye karşı kazandığı yer.
SLO uyumunu da hesaplamak çok kolay. '100ms altında dönen istek yüzdesi' şu sorguyla çıkar:
sum(rate(http_request_duration_seconds_bucket{le="0.1"}[5m]))
/
sum(rate(http_request_duration_seconds_count[5m]))
Exemplar'lari atlamayin
Histogram çok şey söyler ama 'şu yavaş istek hangisiydi?' diye sorduğunda susar. İşte burada exemplar devreye giriyor: bir gözlemin yanına trace ID iliştirip Tempo veya Jaeger'a sıçramana izin veriyor. Go client'da kabaca şöyle:
# Pseudo-kod, gercek dilinde Observe ile birlikte exemplar verirsiniz
hist.observe_with_exemplar(duration_seconds, {"trace_id": current_trace_id})
Grafana'da p99 grafiğine tıklayıp doğrudan o yavaş trace'e gitmek, bence gözlemlenebilirlik tarafında son yıllarda gelen en pratik özelliklerden biri.
Hot path performansı
Bir uyarı: histogram observe çağrısı bedava değil. Her observe, ilgili tüm bucket sayaçlarını artırır ve label kombinasyonu kadar seri yaratır. user_id gibi yüksek kardinaliteli bir label histogram'a koyarsan birkaç saatte Prometheus'unu dize getirebilirsin. Bunu kendi acı tecrübemden değil, çok kez gördüğüm bir tuzak olarak söylüyorum.
Sık karşılaşılan tuzaklar
- Pod'lar arası p99 ortalaması almak:
avg(p99_per_pod)matematiksel olarak yanlıştır; histogram bucket'larını topla, sonrahistogram_quantileçağır. +Infbucket'ında trafik birikmesi: Eğer en yüksek sonlu bucket'ın üstünde ciddi trafik varsa p99 hesabı çöker. Üst sınırı yükselt veya bir 'overflow oranı' alarmı kur.- SLO eşiğini bucket'a koymamak: 200ms hedefin varsa
0.2mutlaka bucket boundary olmalı; aksi halde interpolasyon yanılır. - Yüksek kardinaliteli label kullanmak:
user_id,request_idgibi label'lar histogram'da seri patlamasına yol açar;status_classgibi sınırlı set kullan. - Summary'yi cluster ölçeğinde kullanmak: Tek instance için iyi olabilir; çoklu pod'da quantile'leri toplayamayacağın için sana hiçbir şey anlatmaz.
Kapanış
Quantile metrikleri kullanıcı deneyimini ortalamadan çok daha dürüst anlatır; ama ancak metrik tipini ve bucket sınırlarını işine göre seçtiğinde. Bence çoğu servis için histogram + SLO'ya hizalanmış bucket'lar + exemplar üçlüsü, fazla yorulmadan kuracağın en iyi kombinasyon. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.
