Aller au contenu

TechniqueDécouverte5 min de lecture (+2 technique)Vidéo

La cardinalité expliquée

Pourquoi une facture d'observabilité peut-elle doubler sans que le trafic bouge ? À cause d'un critère de trop sur une métrique. L'explication pour tous, avec une vidéo, puis les détails pour les équipes techniques et un lab qui rejoue le scénario.

Pour : ingénieurs et SRE · architectes · managers d’équipePrérequis : Aucun. La partie « Pour les équipes techniques » suppose de connaître Prometheus.

Mode de lecture

Un lundi matin, un développeur doit comprendre pourquoi un client précis voit des erreurs. Pour aller vite, il ajoute l’identifiant du client à une métrique. Le bug est trouvé dans l’heure, le ticket fermé. Trois semaines plus tard, la facture d’observabilité a doublé. Personne ne fait le lien : le trafic n’a pas bougé et les tableaux de bord sont verts.

Ce scénario est inventé, mais il est classique. Il porte un nom un peu barbare, la cardinalité. L’idée, elle, est simple.

Lire le texte de la vidéo

Ce trimestre, la facture d’observabilité a doublé. Le trafic n’a pas bougé. Personne ne sait pourquoi.

Une métrique fonctionne comme un tableau de suivi. Chaque combinaison différente ouvre une nouvelle ligne. Le nombre de lignes, c’est ce qu’on appelle la cardinalité.

Dans le lab, un service simulé compte ses visites avec quatre critères. Les lignes se multiplient : 3 × 8 × 5 × 3 = 360 pour ce seul compteur. Avec ses autres mesures, le service occupe environ 2 000 lignes. Rien ne bouge.

Un développeur ajoute l’identifiant du client, pour déboguer un ticket. Avec 200 clients, ce compteur passe à 72 000 lignes. Le service occupe maintenant environ 74 000 lignes. Le trafic n’a pas changé.

Chaque ligne se stocke, s’indexe et se parcourt à chaque recherche. Plus de lignes, c’est plus de stockage, plus de mémoire, des recherches plus lentes. Dans le lab : 37 fois plus de stockage. Facturée à la série, la facture suit.

Personne ne vient avouer. On compte les valeurs de chaque colonne. La région a l’air suspecte, mais elle n’aura jamais que quelques valeurs. L’identifiant client n’a pas de limite. C’est lui qui fait grimper la facture.

D’abord, la plateforme retire la colonne à l’entrée, avant de stocker. Les lignes retombent à environ 2 000 en quelques minutes. Ensuite, on corrige le code. Puis on fixe un plafond pour la prochaine fois.

Ce qu’il faut retenir pour ne plus être surpris par la facture.

À retenir :

  1. Une colonne doit avoir peu de valeurs possibles : région, page, résultat.
  2. Les identifiants (client, requête) vont dans les traces et les logs.
  3. On surveille le nombre de lignes et la vitesse à laquelle il grimpe.

Hypothèses du lab : une mesure toutes les 15 secondes, 0,7 octet par mesure. Le coût dépend de votre contrat.

La cardinalité expliquée, avec le lab pour la rejouer : meantimetolearn.com.

Musique : « Radar Focus », Blue Saga (Epidemic Sound).

Imaginez un tableau où vous comptez les visites d’un site. Une colonne pour le serveur qui a répondu, une pour la page demandée, une pour le résultat, sous la forme d’un code de réponse (200 quand tout va bien, 404 pour une page introuvable, 500 pour une erreur du serveur…). Chaque combinaison différente ouvre une nouvelle ligne : « serveur 1, page panier, 200 » est une ligne, « serveur 1, page panier, 500 » en est une autre.

Les outils de supervision fonctionnent exactement comme ça. Les colonnes s’appellent des labels, chaque ligne s’appelle une série et le nombre de lignes, c’est la cardinalité. Chaque ligne se stocke, s’indexe et, chez beaucoup de fournisseurs, se paie.

Le nombre de lignes ne s’additionne pas, il se multiplie. Trois serveurs, huit pages et cinq résultats possibles donnent déjà 3 × 8 × 5 = 120 lignes. Ajoutez trois régions et vous passez à 360.

C’est ce que montre le lab qui accompagne cette page : un petit service simulé, avec un compteur de visites et ces quatre critères. En comptant ses autres mesures, le service occupe environ 2 000 lignes. Tant que personne n’ajoute de colonne, ce chiffre ne bouge pas.

Revenons au lundi matin. Ajouter l’identifiant du client revient à ajouter une colonne dont les valeurs n’ont pas de fin : un nouveau client, ce sont de nouvelles lignes, pour chaque serveur, chaque page et chaque résultat.

Dans le lab, avec seulement 200 clients, le compteur passe de 360 à 72 000 lignes et le service d’environ 2 000 à 74 000. Le trafic, lui, est exactement le même. Rien ne casse et aucun tableau de bord habituel ne devient rouge. Seul le nombre de lignes trahit le problème.

Et 200 clients, c’est peu. Avec 50 000 clients, faites le calcul. Un identifiant de requête ou une adresse web complète produisent le même effet, en pire.

Plus de lignes, c’est plus de stockage, plus de mémoire pour retrouver les données et des recherches plus lentes, souvent au moment d’un incident, quand on en a le plus besoin.

Le lab chiffre le stockage : environ 242 Mo par mois pour le service sain, environ 8,9 Go avec la colonne de trop. Trente-sept fois plus, pour une information qu’on n’exploite jamais en agrégat. Comme ailleurs sur le site, je ne donne pas de prix : ils varient selon les contrats. Si votre fournisseur facture à la série, la facture suit la même pente. Le simulateur de facture des métriques fait le calcul avec votre grille.

Personne ne vient avouer. Le réflexe utile est de compter, pour chaque colonne, combien de valeurs différentes elle contient. Dans le lab, la colonne « région » est une fausse piste : on la soupçonne parce qu’elle multiplie toutes les lignes par trois, mais elle n’a que trois valeurs et n’en aura jamais beaucoup plus. L’identifiant client en a 200 aujourd’hui, 300 le mois prochain, davantage le mois suivant.

À mon avis, c’est le point à retenir : une colonne n’est pas dangereuse parce qu’elle a beaucoup de valeurs à un instant donné, mais parce que ses valeurs n’ont pas de limite.

Dans la vraie vie, la dérive est souvent lente : la colonne arrive avec un déploiement, puis le nombre de lignes grimpe jour après jour. Une alerte sur la vitesse à laquelle de nouvelles lignes apparaissent prévient bien avant la facture. La page Qui fait grossir la facture de métriques ? fait de cette recherche une méthode, avec les outils qui donnent les chiffres.

D’abord, arrêter la fuite : la plateforme peut retirer la colonne au moment où les données arrivent, avant de les stocker. Dans le lab, les lignes actives retombent à environ 2 000 en deux ou trois minutes. Ce qui a déjà été écrit reste stocké jusqu’à la fin de la durée de conservation : la facture ne redescend pas d’un coup.

Ensuite, corriger pour de bon dans le code de l’application, qui continue sinon d’envoyer la colonne pour rien. L’identifiant client a toute sa place ailleurs : dans les traces et les logs, il coûte beaucoup moins cher et reste utile pour enquêter. Pour garder le lien entre la métrique et la trace d’un client précis, il existe un pont : les exemplars, détaillés plus bas.

Enfin, préparer la prochaine fois : un plafond de lignes par application limite les dégâts quand une nouvelle colonne de trop apparaît. Il a un prix, puisqu’une fois atteint il refuse aussi les lignes légitimes. C’est un filet de sécurité, pas une solution.

Le plus efficace reste de ne pas laisser passer la colonne : une règle écrite, vérifiée automatiquement avant chaque mise en production, qui dit quelles colonnes une métrique a le droit de porter. C’est l’objet de l’article Gouverner les données d’observabilité.

Pour les équipes techniques : requêtes, configuration et plafonds

Une série est identifiée par le nom de la métrique et l’ensemble de ses labels. Pour un histogramme, chaque combinaison de labels produit en plus une série par bucket, plus _sum et _count : ce sont souvent les plus gros contributeurs.

Repérer les métriques et les labels responsables :

# Les métriques qui ont le plus de séries
topk(10, count by (__name__) ({job="synthetic-app"}))
# Le nombre de valeurs distinctes de chaque label candidat
count(count by (user_id) (http_requests_total)) # environ 200
count(count by (region) (http_requests_total)) # 3
count(count by (endpoint) (http_requests_total)) # 8
count(count by (status) (http_requests_total)) # 5

Supprimer le label à l’ingestion, dans un agent compatible Prometheus (vmagent dans le lab) :

metric_relabel_configs:
- regex: "user_id"
action: labeldrop

Cette règle arrête la fuite, mais elle a un défaut qu’il faut connaître. Une fois user_id retiré, les 72 000 séries se replient sur 360 jeux de labels identiques : plusieurs séries portent alors exactement les mêmes labels, avec des valeurs de compteur entrelacées. Selon le stockage, des échantillons sont rejetés comme doublons ou hors d’ordre, ou bien rate() voit de faux redémarrages de compteur et donne des valeurs aberrantes. Le nombre de séries est juste, les totaux ne le sont plus.

Pour garder des totaux justes, agrégez au lieu de supprimer : l’agrégation en flux de vmagent additionne les compteurs sans le label avant l’écriture, ou une règle d’enregistrement fait le même travail après coup. Le lab fournit cette variante en annexe (config/streamaggr.yml et docker-compose.streamaggr.yml) :

- match: 'http_requests_total'
interval: 30s
by: [job, app_instance, endpoint, status]
outputs: [total]

Avec un Collector OpenTelemetry, le même piège existe : supprimer l’attribut avec le processeur attributes (action: delete) fait entrer en collision les points de données. La voie propre est la fonction OTTL aggregate_on_attributes du processeur transform, présente dans les versions récentes du Collector contrib (le lab n’utilise pas de Collector : vérifiez la vôtre).

Pour relier une métrique à la trace d’un client sans créer de série, utilisez les exemplars : quelques échantillons de la métrique portent l’identifiant de la trace qui les a produits. Le tableau de bord montre le pic d’erreurs, un clic ouvre la trace d’une requête fautive où se lit l’identifiant client. C’est exactement ce dont avait besoin le développeur du lundi matin.

Côté plafonds, le lab montre deux garde-fous de vmagent : series_limit par cible et un plafond global de nouvelles séries par heure (-remoteWrite.maxHourlySeries). Côté alertes, il déclenche sur le nombre de séries actives, sur leur vitesse de création et sur leur taux de croissance ; le profil leak montre que les deux dernières sonnent avant le seuil absolu.

Hypothèses de stockage du lab : une collecte toutes les 15 secondes (172 800 échantillons par série et par mois) et environ 0,7 octet par échantillon après compression.

Le lab cardinality-cost-lab rejoue tout le scénario en une heure à une heure et demie, sur un poste avec Docker : on démarre le service sain, on ajoute la colonne de trop, on cherche la responsable, on corrige et on mesure le gain.

Fenêtre de terminal
docker compose up -d --build # le service sain
make toxic # la colonne de trop, d'un coup
make leak # la même chose, progressivement sur 5 minutes
make remediate # la correction
./validate.sh # rejoue les trois étapes et vérifie les seuils