İçeriğe geç
Muhammet Şafak
en
Soran: Kaya Cevaplandı:

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

  1. 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.
  2. 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 +Inf bucket’ı, _sum ve _count gelir). Üstüne user_id felakettir.

Ne yapmalı

  1. 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.
  2. 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})
  3. 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 gerisini other olarak toplayın.
  4. Koruma bandı koyun. Scrape’te sample_limit tanımlayın; prometheus_tsdb_head_series ve scrape_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.

Diğer Sorular

Tüm sorular

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi