Analyses

Comment nous sommes passés de P95 à P99 en réduisant la latence de 847ms à 112ms

Editor écrit depuis plus de 13 ans sur Création d'entreprise. Gestion budgétaire. Services de nettoyage. et partage des aperçus pratiques issus de projets réels.

Aurélie Nicolas
14 05 20268 min lecture
Comment nous sommes passés de P95 à P99 en réduisant la latence de 847ms à 112ms
11 min de lecture 26 avr. 2026
Partager :

Le mythe de la queue longue et ce qu'elle masquait réellement

Pendant dix-huit mois, notre tableau de bord Datadog affichait un P95 stable à 230ms pour l'endpoint principal de notre API. Le directeur technique considérait cette métrique comme satisfaisante, et lors de nos réunions hebdomadaires avec les partenaires de conception, personne ne remettait en question ces chiffres. Pourtant, notre équipe support recevait des signalements sporadiques de lenteur, toujours de la part des mêmes comptes entreprise, toujours dans la même fenêtre horaire entre quatorze et seize heures, heure de Paris. Nous avons commencé à suspecter un problème de N+1 query dans notre couche ORM, mais les traces n'indiquaient rien de tel.

En creusant plus profondément, nous avons découvert que notre monitoring ne capturait pas les requêtes qui dépassaient trois secondes, car elles tombaient dans un timeout configuré au niveau du load balancer. Ces requêtes n'apparaissaient jamais dans nos percentiles. Nous avions donc une visibilité tronquée sur notre distribution réelle de latence. Le P99 effectif, une fois ces timeouts comptabilisés, atteignait 847ms, soit presque quatre fois notre P95. Cette découverte a motivé la création d'un document RFC interne intitulé "Instrumentation complète de la queue de latence", signé par trois ingénieurs seniors et validé en quarante-huit heures.

Editor écrit depuis plus de 13 ans sur Création d'entreprise

Phase un : cartographie exhaustive des chemins critiques

Nous avons commencé par identifier tous les composants impliqués dans le trajet d'une requête utilisateur, de l'entrée dans notre infrastructure jusqu'à la réponse finale. Cette cartographie a pris une semaine complète et a mobilisé deux ingénieurs à temps plein. Nous avons documenté chaque handshake mTLS entre services, chaque passage par un egress NAT, chaque appel à une base de données ou à un service tiers. Le résultat était un diagramme de soixante-trois nœuds, bien plus complexe que ce que nous imaginions.

Cette phase nous a permis de constater que le problème n'était pas uniformément réparti. Certains tenants généraient des requêtes avec un P99 de deux secondes, tandis que d'autres restaient sous 150ms. La distribution des latences suivait une courbe bimodale, ce qui indiquait deux populations distinctes de comportement. Nous avons alors soupçonné un hot partition dans notre base de données principale, où certaines clés étaient sollicitées de manière disproportionnée. Le vendredi soir de cette première semaine, nous avions suffisamment de données pour passer à la phase suivante.

Phase deux : isolation du coupable et test des hypothèses

Nous avons formulé trois hypothèses concurrentes. La première supposait un problème de contention au niveau de la base de données, la deuxième pointait vers un goulot d'étranglement dans notre service d'agrégation de métriques, et la troisième suspectait un feature gating mal configuré qui déclenchait des chemins de code coûteux pour certains utilisateurs. Pour trancher, nous avons mis en place une série d'expériences contrôlées sur notre environnement de staging, en rejouant le trafic de production avec des configurations modifiées.

Nous avons découvert que quatre-vingt-trois pour cent des requêtes lentes provenaient de comptes utilisant une fonctionnalité bêta activée par flag.

Cette fonctionnalité, lancée six mois plus tôt, faisait appel à un microservice externe pour enrichir les données utilisateur en temps réel. Le service répondait correctement dans quatre-vingt-dix-sept pour cent des cas, mais pour les trois pour cent restants, il entrait dans une boucle de retry avec un backoff exponentiel qui pouvait atteindre cinq secondes. Comme ce service était appelé de manière synchrone dans le chemin critique, toute latence se répercutait directement sur l'utilisateur final. Nous avions identifié notre coupable. Le lundi suivant, nous avons ouvert un incident timeline détaillant cette découverte et proposant deux solutions architecturales.

Phase trois : refonte architecturale et passage en asynchrone

Nous avions deux options sur la table. La première consistait à désactiver purement et simplement la fonctionnalité bêta pour les comptes entreprise, ce qui aurait résolu le problème immédiatement mais au prix d'une régression fonctionnelle. La seconde impliquait de réécrire le flux d'enrichissement pour le rendre asynchrone, en stockant les données brutes immédiatement et en les enrichissant en arrière-plan via une queue de messages. Nous avons choisi la seconde option, malgré le coût en temps de développement, car elle offrait une solution pérenne.

Mise en œuvre de la solution asynchrone

L'implémentation a nécessité trois semaines de travail concentré. Nous avons introduit une queue Kafka dédiée pour les événements d'enrichissement, configuré un pool de workers qui consommaient ces messages avec un parallélisme de vingt instances, et mis en place un mécanisme de fallback pour servir les données non enrichies en attendant leur traitement. Le défi principal résidait dans la gestion de la cohérence éventuelle : l'interface utilisateur devait gérer gracieusement le cas où une donnée était affichée avant d'être enrichie. Nous avons résolu cela en ajoutant un champ de statut et une logique de polling côté client.

  1. Extraction de la logique d'appel synchrone du chemin critique et remplacement par une publication de message Kafka avec confirmation d'écriture
  2. Déploiement d'un service worker dédié, écrit en Go pour des performances optimales, capable de traiter cinq mille messages par seconde
  3. Mise en place d'un cache Redis pour stocker les résultats d'enrichissement pendant vingt-quatre heures, réduisant les appels redondants de soixante-douze pour cent
  4. Ajout d'une logique de circuit breaker pour éviter la propagation de défaillances en cas d'indisponibilité du service externe
  5. Configuration d'une stratégie de retry avec jitter pour lisser les pics de charge et éviter les thundering herds

Phase quatre : déploiement progressif et validation des métriques

Nous avons procédé à un déploiement en canary sur deux semaines, en activant la nouvelle architecture pour cinq pour cent du trafic, puis vingt-cinq pour cent, puis cinquante pour cent, avant le basculement complet. À chaque palier, nous avons surveillé nos dashboards Grafana en continu, comparant le P99 de la cohorte canary avec celui du groupe de contrôle. Les résultats étaient spectaculaires : le P99 de la cohorte canary est passé de 847ms à 112ms dès le premier jour. Nous avons également constaté une baisse de quarante pour cent du nombre de pages PagerDuty nocturnes, car les timeouts n'entraînaient plus de cascades d'erreurs.

Le temps moyen d'enrichissement en arrière-plan s'établissait à 380ms, ce qui signifiait que la plupart des utilisateurs voyaient leurs données enrichies avant même de rafraîchir leur interface. Pour les cas où l'enrichissement prenait plus longtemps, nous affichions une icône discrète indiquant que les données étaient en cours de traitement, et l'interface se mettait à jour automatiquement dès réception. Cette approche a été saluée par nos partenaires de conception lors du engineering all-hands du mois suivant. Nous avons également observé une amélioration de notre NRR, car les clients entreprise ont cessé de signaler des problèmes de lenteur dans leurs tickets de renouvellement.

Leçons retenues et ce que nous ferions différemment

Rétrospectivement, notre erreur initiale était de considérer le P95 comme suffisant sans jamais questionner ce qui se passait dans les cinq pour cent restants. Les SLA contractuels que nous signons avec nos clients entreprise stipulent des temps de réponse inférieurs à 500ms pour quatre-vingt-quinze pour cent des requêtes, mais nos utilisateurs les plus exigeants attendent une expérience uniforme. Un P99 à 847ms signifie qu'un utilisateur sur cent attend presque une seconde, et dans une session typique de cinquante requêtes, cela se traduit par une demi-requête lente par session. C'est perceptible, frustrant, et suffisant pour éroder la confiance.

Si nous devions recommencer, nous aurions instrumenté le P99 dès le premier jour, et nous aurions audité chaque appel synchrone dans notre chemin critique pour évaluer s'il devait réellement bloquer la réponse utilisateur. Nous aurions également mis en place une revue trimestrielle de nos feature flags pour nettoyer ceux qui n'étaient plus utilisés ou qui introduisaient des chemins de code coûteux. Cette discipline aurait évité l'accumulation de dette technique qui a conduit à cette situation. Aujourd'hui, notre temps médian pour détecter une régression de latence est passé de trois semaines à quarante minutes, grâce aux alertes configurées sur notre P99 par endpoint. Nous avons également intégré une vérification de latence P99 dans notre pipeline CI pour bloquer tout merge qui dégraderait cette métrique de plus de quinze pour cent. Ces garde-fous nous permettent de maintenir une expérience cohérente, quel que soit le percentile considéré, et de tenir nos engagements contractuels sans exception.

Optimisez vos performances dès aujourd'hui

Nos ingénieurs analysent votre infrastructure et identifient les goulots d'étranglement cachés. Une session diagnostic de deux heures vous donne une feuille de route claire.

Planifier un audit
Service
Service

Restez informé

Études de cas et playbooks. Zéro spam, zéro remplissage.

💬
LinkedInTwitterFacebook