Le nombre de métriques déclarées dans une application ne dimensionne presque jamais un serveur Prometheus : c’est la cardinalité, le nombre de séries temporelles uniques réellement stockées, qui décide. Un seul label mal choisi peut multiplier ce nombre par mille, sans qu’aucune métrique supplémentaire n’ait été ajoutée.
La multiplication qui prend tout le monde par surprise
Chaque combinaison distincte de nom de métrique et de valeurs de labels crée une série indépendante. http_requests_total déclinée sur 10 routes, 5 codes de statut et 20 pods ne pèse pas une métrique : elle pèse 1 000 séries, chacune avec son propre historique stocké.
# Une seule ligne de métrique instrumentée...
http_requests_total{route, status, pod}
# ...devient 1000 séries distinctes une fois combinée :
# 10 routes × 5 status × 20 pods = 1000 séries actives
Le piège classique : un label à valeurs non bornées, comme un identifiant de requête ou une adresse IP cliente, dont chaque nouvelle valeur crée une série de plus, indéfiniment. Un histogramme aggrave encore le calcul, puisqu’il émet une série par bucket de latence en plus de la dimension déjà multipliée par les autres labels.
# Piège : request_id a autant de valeurs possibles
# qu'il y a de requêtes, la cardinalité ne s'arrête jamais
http_requests_total.labels(route=route, status=status, request_id=request_id).inc()
Kubernetes aggrave le problème par construction
Le renouvellement normal des pods sur Kubernetes produit le même effet qu’un label non borné, même sans faute d’instrumentation : chaque redéploiement génère de nouveaux noms de pods, donc de nouvelles séries pour toute métrique qui porte un label pod. Un déploiement qui redéploie plusieurs fois par jour (le cas courant avec une CI/CD active) accumule ainsi des séries mortes en continu, chacune conservée en mémoire tant que Prometheus la considère encore active malgré l’absence de nouvel échantillon.
Réduire à la source : metric_relabel_configs
Le levier le plus rentable s’applique au moment du scrape, avant que la série n’entre jamais dans la TSDB : supprimer ou regrouper un label trop fin directement dans la configuration Prometheus.
scrape_configs:
- job_name: checkout
metric_relabel_configs:
# Supprime le label request_id avant stockage :
# la série ne sera jamais créée, quel que soit son volume
- action: labeldrop
regex: request_id
Un label déjà instrumenté dans le code applicatif mais jamais utile en pratique se traite de la même façon, sans avoir à redéployer l’application : la configuration de scrape suffit.
Le filet de sécurité : sample_limit
sample_limit plafonne le nombre d’échantillons acceptés par cible de scrape, un garde-fou dur qui protège le serveur Prometheus d’une cible qui dérive brutalement (un bug d’instrumentation qui génère soudainement des milliers de valeurs de label), au prix d’un rejet complet du scrape si la limite est dépassée.
scrape_configs:
- job_name: checkout
sample_limit: 10000
Ce plafond n’est pas une solution au problème de fond, c’est une digue : un scrape entièrement rejeté au-delà de la limite vaut mieux qu’un Prometheus qui tombe en manque de mémoire, mais la vraie correction reste de traiter la cause (le label non borné) à la source.
Pré-agréger ce qui compte, via recording rules
Une requête PromQL qui agrège sur des milliers de séries à chaque évaluation d’alerte ou chaque rafraîchissement de dashboard consomme un coût récurrent évitable : une recording rule pré-calcule cette agrégation une fois, à intervalle régulier, et stocke le résultat comme une série unique bien moins coûteuse à requêter ensuite.
groups:
- name: checkout-aggregations
rules:
- record: checkout:http_requests:rate5m
expr: sum(rate(http_requests_total{job="checkout"}[5m])) by (route)
Mesurer avant de corriger à l’aveugle
prometheus_tsdb_head_series donne le total de séries actives à un instant donné, la première métrique à surveiller. Pour identifier précisément quelles métriques pèsent le plus, topk(10, count by (__name__)({__name__=~".+"})) classe les plus grosses contributrices, une étape indispensable avant de choisir où appliquer un labeldrop plutôt que de deviner.
topk(10, count by (__name__)({__name__=~".+"}))
À retenir
La cardinalité, pas le nombre de métriques déclarées, dimensionne réellement un serveur Prometheus : un seul label à valeurs non bornées multiplie les séries stockées sans limite naturelle, un effet que Kubernetes amplifie par le simple renouvellement normal de ses pods. metric_relabel_configs corrige à la source, avant que la série n’existe jamais ; sample_limit protège contre une dérive brutale sans en corriger la cause ; les recording rules évitent de recalculer une agrégation coûteuse à chaque requête. Mesurer avant de corriger (prometheus_tsdb_head_series, le classement par topk) évite de deviner où agir, une discipline qui fait partie du socle d’une fiabilité et observabilité qui tient dans la durée, pas seulement au lancement.