Prometheus metriklerime user_id gibi yüksek kardinaliteli label eklemeli miyim?
Soru
Go ile yazdığım bir API'yi Prometheus ile izliyorum ve müşteri bazında latency kırılımı görmek istiyorum; hangi tenant'ın yavaşladığını p95 seviyesinde görebilmek işime çok yarayacak. Aklımdaki en kolay yol, histogram metriklerime user_id (ya da tenant_id) label'ı eklemek. Ama binlerce müşterim var ve bunu yaparsam Prometheus'un cardinality'sini ve bellek/depolama maliyetini patlatmaktan korkuyorum. Bu label'ı eklemeli miyim, yoksa müşteri kırılımını başka bir yerde mi çözmeliyim?
Cevap
Kısa cevap: Hayır — user_id’yi (ya da herhangi bir sınırsız label’ı) metriklerinize koymayın.
Kısa cevap
Prometheus her benzersiz label kombinasyonu için ayrı bir time series tutar; müşteri başına kardinalite belleği ve maliyeti katlanarak büyütür. Müşteri kırılımını exemplar, trace veya loglara bırakın; label’ları yalnızca sınırlı (bounded) boyutlar için saklayın.
Buradaki tuzak, “bir label daha, ne olacak” diye düşünmektir. Prometheus’ta maliyet, metrik sayısıyla değil, aktif seri sayısıyla ölçülür; ve sınırsız bir label o sayıyı doğrusal değil, çarpımsal büyütür.
Neden
- Kardinalite = seri sayısı. Her benzersiz label kombinasyonu ayrı, bellekte tutulan bir time series’tir.
endpoint × status × user_idçarpımı hızla patlar; head block’ta her seri kilobayt mertebesinde RAM demektir. Birkaç milyon seri, Prometheus’unuzu OOM’a götürür. - Histogram maliyetine ayrıca dikkat. Histogram’lar zaten çarpar:
bucket × label kombinasyonu. 12 bucket’lı, 50 endpoint’li bir histogram, siz hiçbir şey eklemeden en az 600 seri eder (üstüne+Infbucket’ı,_sumve_countgelir). Üstüne user_id felakettir.
Ne yapmalı
- Yalnızca bounded label kullanın. İyi label’lar:
endpoint,method,status_code,region— düşük ve sabit kardinaliteli. Kötü olanlar:user_id,email,request_id, içinde ID geçen tam URL — bunlar sınırsız, asla koymayın. - Müşteri bazlı latency başka yere aittir. Seçenekler: exemplar (histogram bucket’ına trace_id/user_id iliştirirsiniz — bounded, scrape başına bucket başına tek örnek), trace (OTel span’ine user_id attribute’u koyup Tempo/Jaeger’a sample edersiniz) veya yapısal log (Loki/ClickHouse’da toplarsınız).
// Bounded label'larla histogram + drill-down için exemplar: timer := prometheus.NewTimer(nil) lat := timer.ObserveDuration().Seconds() reqDuration. With(prometheus.Labels{"endpoint": ep, "status": code}). (prometheus.ExemplarObserver). ObserveWithExemplar(lat, prometheus.Labels{"trace_id": traceID}) - Gerçekten top-N müşteri gerekiyorsa katmanlayın. Bounded bir
customer_tier(free/pro/enterprise) label’ı segment kırılımını ucuza verir. Ya da bir allowlist ile yalnızca en ağır birkaç müşteriyi label’layıp gerisiniotherolarak toplayın. - Koruma bandı koyun. Scrape’te
sample_limittanımlayın;prometheus_tsdb_head_seriesvescrape_samples_scrapedüzerinde alert kurun ki kardinalite sessizce kaçmasın.# Kardinaliteyi izleyin — bu sayı sürekli tırmanıyorsa bir label kaçmıştır: topk(10, count by (__name__)({__name__=~".+"}))
Sonuç: Ben olsam metrikleri bounded label’larda (endpoint/status/tier) tutar, drill-down için histogram’a exemplar iliştirir ve gerçek müşteri bazlı analitiği log/ClickHouse tarafına atardım. “Belki lazım olur” diye eklenen yüksek kardinaliteli label’lar, 4 GB’lık bir Prometheus’u 40 GB’a çeviren şeydir. Önce sorunun trace mi yoksa metrik mi olduğuna karar verin; müşteri kırılımı neredeyse her zaman trace’in işidir.
Yorumlar
Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.