Lab 4.1 — Lire un panel de latence : heatmap, p95 et faux pics
Page de lecture : rien à construire, rien à déployer. Le dashboard « Cart Service Exemplars », livré par la démo, met face à face deux panels pour la même opération du panier : une heatmap et une courbe de p95. Ils affichent la même métrique. On commence par la heatmap — c’est la distribution entière —, et le p95 s’en déduit par une seule opération de lecture.
1. La heatmap, mot à mot
La requête du panel « AddItem Latency Heatmap with Exemplars » :
Trois morceaux, à lire de l’intérieur vers l’extérieur.
_bucket et le : des seaux, pas des durées. Le service cart ne publie pas la durée de chaque appel — ce serait revenir aux traces. Il les range dans des seaux : ..._bucket{le="0.005"} compte les appels qui ont duré 5 ms ou moins (le = less or equal, et la métrique est en secondes). Ces seaux sont cumulatifs — un appel de 3 ms incrémente aussi celui des 10 ms, celui des 25 ms, et ainsi de suite jusqu’à le="+Inf" qui les contient tous.
Relevé sur le cluster de la formation :
Ces lignes se lisent : 1936 appels observés, dont 1935 sous 5 ms. Le 1936ᵉ est passé entre 10 et 25 ms — c’est le seul endroit où le compteur augmente. Pour lister les seaux de votre cluster, dans Explore :
rate(...[$__rate_interval]) : ramener au récent. Chaque seau est un compteur cumulé depuis le démarrage du service. Sans rate, le panel décrirait la distribution des latences depuis toujours, une image presque immobile où l’incident d’il y a trois heures pèse autant que la minute en cours. $__rate_interval est la variable de Grafana qui adapte la fenêtre au zoom du dashboard.
sum by(le) : additionner les instances, garder les seaux. Le service peut tourner en plusieurs pods : sum les réunit, by(le) conserve le découpage par seau. Un sum nu écraserait aussi le : il ne resterait plus aucune distribution à afficher.
Ce que Grafana en fait. La requête rend une série par seau. Grafana pose une ligne par seau, et comme les seaux sont cumulatifs, il soustrait chaque seau de son voisin du dessous pour obtenir la part propre de chacun. Cette part donne la couleur de la cellule :
Chiffres d’illustration, en req/s. Une colonne de la heatmap, lue de bas en haut, est la distribution des latences à cet instant : ici le gros du trafic entre 10 et 25 ms, et une frange qui traîne au-dessus.
💡 La dé-cumulation est bien faite par Grafana, pas par PromQL. Elle se vérifie dans Panel → Inspect → Panel JSON : le panel porte
"calculate": false— « les données arrivent déjà en seaux, ne recalcule pas d’histogramme » — et"filterValues": {"le": 1e-9}, qui masque les cellules restées vides.
2. Ce qu’un chiffre unique ne peut pas montrer
Un p95 ne peut pas dire si les requêtes forment une population ou deux. Or c’est fréquent : une réponse servie depuis un cache et une réponse calculée n’ont pas la même durée, et l’histogramme le voit tout de suite.
Le p95 correspondant est une courbe plate au-dessus de la bande du haut : il ne laisse jamais deviner qu’il y a deux bandes. Et le jour où le cache se dégrade, la bande du bas maigrit avant que le p95 ne bouge vraiment — la masse se déplace bien avant la queue. La heatmap est donc le signal le plus précoce des deux.
3. Le p95, déduit de la même colonne
Reprenons la colonne de l’instant t. Le seau +Inf donne le total : 10 req/s. Le p95, c’est le seuil sous lequel passent 95 % d’entre elles, soit 9,5 req/s. Il suffit de remonter la colonne en cumulant jusqu’à atteindre ce nombre :
Deux lectures, et le résultat tombe :
- Trouver le bon seau. Le cumul vaut 9,2 sous 50 ms et 9,7 sous 75 ms. La cible, 9,5, est entre les deux : le p95 est dans le seau (50 ms ; 75 ms].
- Interpoler dedans. Il manque 0,3 req/s sur les 0,5 que contient ce seau, soit 60 % du chemin. Prometheus applique cette proportion à la largeur du seau : 50 + 0,6 × (75 − 50) = 65 ms.
C’est exactement ce que calcule la fonction histogram_quantile, et c’est pourquoi la requête du panel voisin est la requête de la heatmap avec un étage de plus :
🔑 Le p95 est ce qui reste de la heatmap quand on n’en garde qu’une valeur par colonne. Même métrique, même
rate, mêmesum by(le)— deux lectures.
Pourquoi des seaux plutôt que des durées. Parce qu’un percentile ne s’additionne pas. La moyenne du p95 de deux pods n’est pas le p95 de l’ensemble, c’est un nombre sans signification. Des seaux, eux, s’additionnent sans difficulté : 120 requêtes sous 10 ms ici, 200 là, cela fait bien 320 — c’est tout ce que fait le sum by(le). On renonce donc aux durées exactes pour gagner le droit d’agréger, quitte à recalculer le percentile au moment de l’affichage.
« Et pourquoi ne pas trier les requêtes dans Jaeger et lire celle qui tombe au 95ᵉ centile, tout simplement ? » Parce qu’elle n’y est peut-être pas — au Lab 7, le tail sampling ne gardera qu’un quart des traces — et parce qu’un service réel produit des millions de requêtes par minute : les trier à chaque rafraîchissement du panel, sur six heures de fenêtre, n’est pas tenable. Les traces vivent d’ailleurs quelques jours, les métriques des mois. Chaque signal fait son métier : la métrique dit qu’il y a un problème et depuis quand, pour trois fois rien et sur la longue durée ; la trace dit laquelle des requêtes a souffert.
4. La limite du p95 : la largeur du seau
Sa précision est celle du seau. Les 65 ms du schéma ne sont pas une mesure : les 0,5 req/s de ce seau pourraient aussi bien être toutes à 51 ms que toutes à 74 ms, la réponse serait la même. « 65 ms » signifie surtout « entre 50 et 75 ». Seuls des seaux plus serrés améliorent la précision, et cela se règle à la production de la métrique, pas à la lecture.
5. Heatmap ou percentile ?
| Heatmap | Percentile | |
|---|---|---|
| Répond à | « à quoi ressemble la distribution ? » | « ça va, ou pas ? » |
| Bonne pour | comprendre, explorer un incident | suivre dans le temps, alerter |
| Faiblesse | dense, illisible sur une longue fenêtre ; aucun seuil à y poser | masque la forme de la distribution |
Une alerte a besoin d’un nombre unique à comparer à un seuil : c’est le p95, pas la heatmap. Mais quand l’alerte a sonné, c’est la heatmap qu’on regarde pour savoir ce qui s’est déplacé.
6. Quand le dashboard invente un pic
Un dernier détour, sur la même requête. Le Lab 4 posait une règle sans la démontrer — toujours rate d’abord, sum ensuite — et voici pourquoi, sur un incident que tous les clusters connaissent : le redémarrage d’un pod.
Deux pods de collecteur, A et B, 2 requêtes/s chacun — donc 4 req/s en réalité, stable. Scrape toutes les 15 s. B redémarre à t=45 s.
| t | A | rate(A) | B | rate(B) | sum(rate) ✅ | A+B | rate(sum) ❌ |
|---|---|---|---|---|---|---|---|
| 0 s | 1000 | — | 800 | — | — | 1800 | — |
| 15 s | 1030 | 2/s | 830 | 2/s | 4/s | 1860 | 4/s |
| 30 s | 1060 | 2/s | 860 | 2/s | 4/s | 1920 | 4/s |
| 45 s | 1090 | 2/s | 0 ⚡ | 0/s | 2/s | 1090 | ≈ 73/s 💥 |
| 60 s | 1120 | 2/s | 30 | 2/s | 4/s | 1150 | 4/s |
Les deux écritures donnent le même résultat partout, sauf sur la ligne du redémarrage.
À gauche, rate compare 0 à 860 sur la seule série de B : reset reconnu, delta ramené à 0 — le pod n’a effectivement rien compté pendant qu’il redémarrait. La courbe creuse à 2/s, ce qui est la vérité : la moitié de la capacité était absente.
À droite, la même comparaison se fait sur le total, 1090 contre 1920. Baisse, donc reset, donc delta = 1090 → 1090 / 15 ≈ 73/s. Ce 1090, ce sont les requêtes cumulées de A depuis son propre démarrage, comptées d’un coup comme si elles venaient d’arriver en 15 secondes. Le pic n’est pas du trafic : c’est l’historique de A relâché sur un intervalle. Et plus A tourne depuis longtemps, pire c’est — à 50 000 au compteur, le faux pic monterait à 3 300/s.
Deux détails que le tableau simplifie : un vrai rate[2m] étale ce pic sur la fenêtre au lieu de le concentrer sur un point (plus bas, plus large, même erreur totale) ; et PromQL rend d’ailleurs la mauvaise écriture malaisée — rate(sum(...)[2m]) est invalide, il faut une subquery pour y arriver.
Cette écriture-là, PromQL la rend d’ailleurs difficile à commettre. Ce qui se transpose, c’est le réflexe : un pic sur un dashboard peut être un artefact du calcul et pas un événement. Devant une valeur spectaculaire, la première question à se poser est de savoir si elle décrit le système ou la façon dont on l’interroge.