Efficacité : pourquoi « plus rapide » n’est pas toujours mieux

Cet article vous a aidé ?

Le pager sonne à 02:13. « Latence API en hausse. » Vous ouvrez le tableau de bord et voyez un schéma familier : la latence moyenne semble correcte, mais le p99 est une falaise.
Quelqu’un prononce les mots magiques : « Il faut accélérer. »

C’est ainsi que l’on finit par acheter des instances plus grosses, activer des caches agressifs, activer des modes « turbo », et rater quand même son SLO — sauf qu’on a maintenant aussi augmenté le coût,
la complexité et le rayon d’explosion du prochain incident. La vitesse sauve les démos. L’efficacité maintient la production ennuyeuse.

Vitesse vs efficacité : la différence qui paye vos factures

« Plus rapide » est une mesure : latence plus faible, débit plus élevé, plus de requêtes par seconde. « Efficace » est une relation : quelle valeur vous obtenez par unité de coût,
risque et effort opérationnel. Si la vitesse est une voiture de sport, l’efficacité est le véhicule ennuyeux que vous pouvez réellement entretenir, assurer et garer.

Dans les systèmes de production, vous ne voulez que rarement la vitesse maximale. Vous voulez assez de vitesse avec un comportement prévisible.
Le chemin vers « plus rapide » passe souvent par :

  • Variance plus élevée (la latence en queue s’aggrave)
  • Contention de ressources (voisin bruyant à l’intérieur de votre propre machine)
  • Couplage caché (votre optimisation dépend de conditions que vous ne pouvez garantir)
  • Fragilité (un petit changement casse une grande hypothèse)

Les systèmes efficaces sont conçus autour des contraintes : CPU, bande passante mémoire, latence IO, gigue réseau, localité du cache, files d’attente et humains.
Les humains sont une contrainte réelle. Votre ingénieur d’astreinte n’est pas une ressource renouvelable.

Efficacité : un contrat en trois parties

  1. Performance : respecter les SLO en état stable et en pic, avec une latence en queue acceptable.
  2. Coût : garder une économie unitaire saine (coût par requête, par GB stocké, par rapport généré).
  3. Opérabilité : les changements sont testables, observables et réversibles à 02:13.

Voici la vérité qui fâche : la plupart des « projets vitesse » sont en réalité des projets de stress. Vous augmentez l’utilisation pour extraire plus de production,
puis vous êtes surpris quand la théorie des files d’attente débarque comme un invité non invité.

Une citation, parce qu’elle résume le travail en une phrase. Werner Vogels (CTO d’Amazon) l’a formulé simplement :
Tout échoue, tout le temps.

Si votre optimisation suppose que tout fonctionne, ce n’est pas une optimisation. C’est un incident futur.

Faits et histoire : ce que nous avons appris à la dure

Les débats sur l’efficacité ne sont pas nouveaux. Ils surgissent chaque fois que nous construisons un composant plus rapide et découvrons que nous avons juste déplacé le goulot quelque part de plus laid.
Quelques faits concrets et points historiques qui comptent dans les systèmes d’aujourd’hui :

  1. La loi d’Amdahl (1967) : accélérer une partie d’un système a des rendements décroissants si le reste reste inchangé.
    En pratique, c’est pourquoi « stockage 2× plus rapide » ne rend rarement pas un service 2× plus rapide.
  2. La loi de Little (théorie des files) : nombre moyen dans le système = taux d’arrivée × temps dans le système.
    Vous ne pouvez pas « optimiser » les files ; vous pouvez seulement gérer le taux d’arrivée, le temps de service ou la concurrence.
  3. L’augmentation de fréquence CPU a atteint un mur (milieu des années 2000) à cause de la densité thermique et de la chaleur.
    C’est pourquoi nous avons obtenu de nombreux cœurs, pas des cœurs infiniment rapides — et pourquoi le parallélisme est maintenant obligatoire et pénible.
  4. Le « mur mémoire » : la vitesse CPU s’est améliorée plus vite que la latence mémoire pendant des décennies.
    La performance moderne dépend souvent de « comment vous utilisez les caches », pas « combien de GHz vous avez achetés ».
  5. La pénalité d’écriture RAID est devenue un piège classique : plus de disques peut signifier plus de débit, mais les écritures de parité peuvent amplifier l’IO et la latence.
  6. Évolution du contrôle de congestion TCP : nous avons appris que « envoyer plus vite » effondre les réseaux partagés.
    Les systèmes qui ignorent la congestion créent des pannes auto-infligées.
  7. La latence en queue est devenue un problème de premier plan dans les grands systèmes distribués : le p99 domine l’expérience utilisateur et les retries amplifient la charge.
    La latence moyenne est un menteur bien posé.
  8. L’adoption des SSD a changé les modes de défaillance : moins de pannes mécaniques, plus de bizarreries firmware, amplification des écritures et falaises de performance soudaines sous écritures soutenues.

Quand « plus rapide » vous rend pire

« Plus rapide » peut être un piège parce qu’il vous pousse à optimiser la chose la plus facile à mesurer, pas celle qui limite le résultat métier.
Quelques façons courantes dont la vitesse devient anti-efficacité :

1) Vous optimisez le débit et détruisez la latence en queue

Beaucoup de gains de performance consistent à « regrouper plus », « mettre en file davantage », « paralléliser davantage ». Super pour le débit. Pas toujours pour le temps de réponse.
Quand l’utilisation s’approche de la saturation, la latence en queue croît de façon non linéaire. Votre médiane reste correcte. Votre p99 devient un film d’horreur.

2) Vous réduisez la latence en augmentant la variance

Les caches agressifs, le travail spéculatif et les pipelines asynchrones peuvent réduire la latence typique tout en augmentant le pire cas.
C’est acceptable si votre SLO est la médiane. La plupart ne le sont pas. Les clients ne se plaignent pas du p50. Ils se plaignent que « ça bloque parfois ».

3) Vous achetez de la vitesse avec de la complexité

La complexité est le taux d’intérêt que vous payez sur chaque changement futur. Un système plus rapide qui nécessite trois experts tribaux et une pleine lune pour déployer
n’est pas « meilleur ». C’est une dette de fiabilité.

4) Vous créez une amplification de charge

Le bug de performance le plus facile à manquer est l’amplification :

  • Amplification de lecture : une requête déclenche beaucoup de lectures backend.
  • Amplification d’écriture : une écriture devient plusieurs écritures (journaux, WAL, parité, réplication).
  • Amplification par retries : des réponses lentes provoquent des retries, provoquant plus de charge, provoquant des réponses plus lentes.

Blague #1 : Relancer une requête lente sans backoff, c’est comme klaxonner dans les embouteillages pour faire disparaître les voitures. C’est satisfaisant et complètement inefficace.

5) Vous optimisez le mauvais niveau

Les ingénieurs stockage voient cela constamment : une équipe applicative « a besoin de disques plus rapides », alors que leur service est en réalité lié au CPU pour le parsing JSON,
ou bloqué sur le DNS, ou sérialisé sur un verrou. Le disque est innocent. Les tableaux de bord ne le sont pas.

Métriques qui comptent (et celles qui mentent)

L’efficacité est mesurable si vous arrêtez d’adorer les nombres uniques. Les bonnes métriques répondent : « Qu’est-ce qui nous limite, combien ça coûte, et à quel point c’est prévisible ? »

À utiliser

  • p95/p99/p999 de latence par endpoint et par dépendance (DB, cache, stockage d’objets).
  • Profondeur de file (disque, NIC, pools de threads applicatifs).
  • Utilisation avec signaux de saturation : CPU avec run queue, IO avec await, réseau avec retransmissions.
  • Taux d’erreur et taux de retries (les retries sont des erreurs latentes).
  • Coût par unité : coût par requête, par GB-mois, par exécution de job.
  • Taux d’échec des changements : une optimisation qui casse les déploiements n’est pas une victoire.

Se méfier de ces métriques (sauf avec contexte)

  • Latence moyenne sans percentiles.
  • % CPU sans run queue et steal time.
  • « util % » du disque seul ; vous avez besoin de latence/await et de profondeur de file.
  • Débit réseau sans perte de paquets et retransmissions.
  • Taux de hit du cache sans taux d’éviction et comportement en queue.

Tableau d’efficacité

Si vous voulez une page unique à montrer à la direction sans mentir, suivez :

  • Conformité aux SLO (y compris la latence en queue)
  • Coût par transaction (ou par utilisateur actif, par rapport, par exécution de pipeline)
  • Incidents attribuables aux changements de performance/scale
  • Temps moyen pour détecter le goulot (MTTDB), parce que votre temps compte

Méthode de diagnostic rapide : trouver le goulot en minutes

Le but n’est pas de « collecter toutes les métriques ». Le but est de trouver la ressource limitante assez vite pour ne pas commencer un tuning au hasard.
Le tuning au hasard est la façon dont on crée du folklore.

Première étape : confirmer le symptôme et la portée

  1. Quel percentile échoue ? p95 vs p99 importe.
  2. Quels endpoints ? tout le trafic vs une route.
  3. Quelles machines/pods ? un shard ou systémique.
  4. Qu’est-ce qui a changé ? déploiement, config, forme du trafic, comportement d’une dépendance.

Deuxième étape : choisir la classe de goulot probable

Décidez laquelle de ces options est la plus plausible en vous basant sur vos graphiques et modes d’erreur :

  • Saturation CPU : run queue élevé, throttling, GC long, contention sur des verrous.
  • Pression mémoire : paging, défauts majeurs, thrashing de cache, kills OOM.
  • Latence stockage : await élevé, profondeur de file, problèmes d’ordonnanceur IO, erreurs de périphérique.
  • Problèmes réseau : retransmissions, latence DNS, pertes de paquets, backlog SYN.
  • Dépendance amont : requêtes DB lentes, stampede de cache, timeouts tiers.

Troisième étape : valider avec une seule machine et une commande par couche

Ne vous éparpillez pas. Choisissez une machine la plus affectée et exécutez un ensemble serré de vérifications. Si le signal est faible, élargissez.
Votre job est de faire avouer le goulot.

Quatrième étape : décider d’atténuer ou de corriger

  • Atténuer maintenant : réduire la charge, limiter la concurrence, augmenter les timeouts prudemment, revenir en arrière, scaler horizontalement.
  • Corriger ensuite : tuner, refactorer, réindexer, changer l’architecture, planifier la capacité.

Tâches pratiques : commandes, sorties et décisions (12+)

Ce sont de vraies commandes que vous pouvez exécuter sur des hôtes Linux (et quelques-unes pour ZFS/Kubernetes si vous les avez).
Chaque tâche inclut : commande, exemple de sortie, ce que cela signifie et quelle décision prendre.

Task 1: Check load and run queue (CPU saturation vs “busy but fine”)

cr0x@server:~$ uptime
 02:41:12 up 17 days,  6:03,  2 users,  load average: 18.92, 17.40, 12.11

Sens : Une moyenne de charge bien au-dessus du nombre de cœurs signale souvent une saturation CPU ou des threads exécutables bloqués sur de l’IO. C’est une métrique « quelqu’un attend ».

Décision : Si la charge est élevée, vérifiez immédiatement la run queue CPU et l’attente IO ensuite (ne supposez pas « besoin de plus de CPU »).

Task 2: See CPU, iowait, and steal (virtualization tax matters)

cr0x@server:~$ mpstat -P ALL 1 3
Linux 6.5.0-18-generic (api-3)  01/12/2026  _x86_64_  (8 CPU)

02:41:22 PM  CPU   %usr  %nice   %sys %iowait  %irq  %soft  %steal  %idle
02:41:23 PM  all  72.11   0.00  12.44    9.83  0.00   0.62    3.90   1.10
02:41:23 PM    0  81.00   0.00  10.00    6.00  0.00   1.00    2.00   0.00

Sens : %iowait suggère du temps CPU passé à attendre le stockage ; %steal suggère une contention hyperviseur.

Décision : Iowait élevé → empruntez la voie stockage. Steal élevé → déplacez la charge, changez de type d’instance, ou réduisez la sensibilité aux voisins bruyants.

Task 3: Identify top CPU consumers and whether it’s user time or kernel time

cr0x@server:~$ top -b -n 1 | head -n 20
top - 14:41:28 up 17 days,  6:03,  1 user,  load average: 18.92, 17.40, 12.11
Tasks: 318 total,  12 running, 306 sleeping,   0 stopped,   0 zombie
%Cpu(s): 72.1 us, 12.4 sy,  0.0 ni,  1.1 id,  9.8 wa,  0.0 hi,  0.6 si,  3.9 st
MiB Mem :  32028.7 total,    614.2 free,  28970.1 used,   2444.4 buff/cache

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
18231 app       20   0 2445684 812340  10920 R  386.7   2.5  19:33.11 api-server

Sens : Un %sy élevé peut indiquer du travail intensif en appels système : réseau, IO disque, changements de contexte.

Décision : Si un seul processus domine, profilez-le. Si le temps système domine, vérifiez les patterns réseau et IO avant d’ « optimiser le code ».

Task 4: Confirm memory pressure (page faults, swap activity)

cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
12  2  524288  60212  10240 2011224   45   61   220  1350 8200 22000 66 14  2 12  6

Sens : Des si/so non nuls indiquent du swapping. Les processus bloqués « b » et un « r » élevé montrent de la contention.

Décision : Si swapping : réduisez l’usage mémoire, corrigez les fuites, ajustez les caches, ou ajoutez de la RAM. Ne tentez pas « d’optimiser l’IO » pendant que vous paginez.

Task 5: Check OOM kills and memory cgroup pressure (containers lie)

cr0x@server:~$ dmesg -T | tail -n 8
[Mon Jan 12 02:34:10 2026] Memory cgroup out of memory: Killed process 18231 (api-server) total-vm:2445684kB, anon-rss:790120kB, file-rss:2480kB, shmem-rss:0kB
[Mon Jan 12 02:34:10 2026] oom_reaper: reaped process 18231 (api-server), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

Sens : Votre « problème de latence » peut être des redémarrages. Un OOM peut se manifester par des pics p99 sporadiques et des tempêtes de retries étranges.

Décision : Corrigez les limites/requests, l’usage mémoire ou les pics de charge. Ne poursuivez pas des fantômes de stockage.

Task 6: Disk latency and queue depth (the IO truth serum)

cr0x@server:~$ iostat -x 1 3
Linux 6.5.0-18-generic (api-3)  01/12/2026  _x86_64_  (8 CPU)

Device            r/s     w/s   rkB/s   wkB/s  aqu-sz  await  r_await  w_await  %util
nvme0n1         220.0   980.0  8120.0 42100.0    9.40   8.60     2.10     10.10  97.80

Sens : Un %util élevé plus un await élevé indique saturation ; aqu-sz montre la profondeur de file. w_await plus élevé que r_await pointe souvent vers une pression d’écriture.

Décision : Si saturé : réduisez l’amplification d’écriture (batching, comportement fsync), déplacez les données chaudes vers du stockage plus rapide, ou scalez horizontalement.

Task 7: Find which processes are issuing IO

cr0x@server:~$ sudo iotop -b -n 1 | head -n 12
Total DISK READ: 12.34 M/s | Total DISK WRITE: 78.22 M/s
  PID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN      IO>    COMMAND
21011 be/4  postgres  1.10 M/s   62.50 M/s   0.00 %  35.22 %  postgres: checkpointer
18298 be/4  app       0.25 M/s    8.10 M/s   0.00 %   5.10 %  api-server

Sens : Si le checkpointer ou le job de compaction domine les écritures, la latence de votre appli peut être un dommage collatéral.

Décision : Ajustez les checkpoints/compactions DB, exécutez-les hors pointe, ou isolez l’IO (cgroups, volumes séparés).

Task 8: Filesystem health and mount options (silent performance killers)

cr0x@server:~$ mount | grep -E ' /var/lib/postgresql| /data'
/dev/nvme0n1p2 on /var/lib/postgresql type ext4 (rw,relatime,discard,errors=remount-ro)

Sens : Des options comme discard peuvent nuire à la performance d’écriture soutenue sur certaines configurations SSD.

Décision : Envisagez un fstrim programmé plutôt que discard continu si les écritures soutenues comptent et que votre SSD/contrôleur en souffre.

Task 9: Network retransmits and drops (bandwidth is not your problem)

cr0x@server:~$ ss -s
Total: 1542 (kernel 0)
TCP:   912 (estab 803, closed 72, orphaned 0, synrecv 4, timewait 72/0), ports 0

Transport Total     IP        IPv6
RAW       0         0         0
UDP       14        12        2
TCP       840       812       28
INET      854       824       30
FRAG      0         0         0
cr0x@server:~$ netstat -s | grep -E 'retransmit|segments retransmited|RTO'
    144523 segments retransmited

Sens : Les retransmissions indiquent perte ou congestion. Cela se manifeste comme une latence en queue même si le débit moyen semble correct.

Décision : Investiguer les erreurs NIC, la mise en file, les mismatches MTU, ou la santé réseau upstream avant de scaler le calcul.

Task 10: DNS latency (the dependency everyone forgets)

cr0x@server:~$ resolvectl statistics
DNSSEC supported: no
Transactions: 124532
  Current Transactions: 0
Cache Size: 4096
Cache Hits: 100122
Cache Misses: 24410
DNSSEC Verdicts: 0
cr0x@server:~$ dig +stats api.internal A
;; Query time: 148 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Mon Jan 12 02:41:55 UTC 2026
;; MSG SIZE  rcvd: 54

Sens : Des requêtes DNS à 148ms pollueront le p99 si vous les faites par requête ou après des évictions de cache.

Décision : Corrigez la santé du résolveur, augmentez le caching, réduisez les recherches par requête et fixez des TTL raisonnables.

Task 11: Application-level queueing (threads, backlog, connection pools)

cr0x@server:~$ ss -lntp | grep ':443'
LISTEN 0 4096 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=1123,fd=8))

Sens : La taille du backlog d’écoute et la pression sur la file d’acceptation peuvent causer des délais de connexion sporadiques qui ressemblent à de la « latence aléatoire ».

Décision : Si vous voyez un backlog SYN ou des problèmes de file d’acceptation (via ss et compteurs noyau), ajustez le backlog et corrigez la rafale en amont.

Task 12: Kernel scheduler pressure (context switching can be the bottleneck)

cr0x@server:~$ pidstat -w 1 3
Linux 6.5.0-18-generic (api-3)  01/12/2026  _x86_64_  (8 CPU)

02:42:10 PM   UID       PID   cswch/s nvcswch/s  Command
02:42:11 PM  1001     18231   2200.00   9800.00  api-server

Sens : Des switches de contexte non volontaires élevés suggèrent de la contention : verrous, pression de run queue, ou trop de threads.

Décision : Réduisez la concurrence, corrigez la contention de verrous, ou profilez les hotspots. Plus de threads n’est pas automatiquement plus de vitesse.

Task 13: ZFS reality check (ARC, dirty data, pool health)

cr0x@server:~$ sudo zpool status
  pool: tank
 state: ONLINE
config:

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          mirror-0  ONLINE       0     0     0
            sda     ONLINE       0     0     0
            sdb     ONLINE       0     0     0

errors: No known data errors
cr0x@server:~$ sudo arcstat 1 1
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
14:42:25  9721   812      8    49    6   621   76   142   18   28.3G  30.0G

Sens : Un miss% bas signifie généralement que les lectures sont servies par le cache ; si vous êtes lent quand même, votre goulot n’est pas l’IO brut en lecture.

Décision : Si le taux de hit ARC est bon mais que la latence est mauvaise, examinez les écritures, le comportement sync, la fragmentation ou les blocages applicatifs.

Task 14: Kubernetes throttling (fast code, slow quota)

cr0x@server:~$ kubectl top pod -n prod | head
NAME                         CPU(cores)   MEMORY(bytes)
api-7f8c9d7b5c-2kq9m          980m         610Mi
api-7f8c9d7b5c-8tqz1          995m         612Mi
cr0x@server:~$ kubectl describe pod api-7f8c9d7b5c-2kq9m -n prod | grep -i thrott
  cpu throttling:  38% (cgroup)

Sens : Le throttling CPU produit des pics de latence qui ressemblent à du « GC » ou à un « ralentissement aléatoire ».

Décision : Augmentez les limites CPU, ajustez correctement les requests, ou réduisez le CPU par requête. N’achetez pas des nœuds plus rapides pendant que les pods sont limités.

Trois mini-histoires d’entreprise du terrain

Mini-histoire 1 : Un incident causé par une mauvaise hypothèse

Une entreprise SaaS de taille moyenne avait un graph propre : plus de trafic, plus de CPU, et une montée constante de la latence tous les lundis matin.
L’hypothèse fut immédiate et confiante : « Nous sommes liés au CPU. Scalez les nœuds API. »

Ils l’ont fait. Deux fois. La latence s’est améliorée pendant environ une heure, puis est remontée. Les coûts ont augmenté en permanence, ce qui a rapidement intrigué la finance.
La rotation d’astreinte a découvert un nouveau hobby : regarder des graphiques et se sentir jugée par eux.

La vraie cause était une dépendance : un service de métadonnées qui faisait des résolutions DNS synchrones pour chaque requête parce que l’équipe avait « optimisé » le cache local.
Quand le cache résolveur a churné sous la forme du trafic du lundi, les temps de requête DNS ont grimpé, ce qui a enchaîné des délais d’établissement de connexion.
Les serveurs API étaient « occupés » principalement parce qu’ils attendaient.

La correction fut ennuyeuse : restaurer le caching, ajouter un petit cache TTL en processus pour les endpoints résolus, et limiter la concurrence pendant la dégradation du résolveur.
Le scaling CPU est resté, mais comme mesure de capacité, pas comme superstition.

La leçon n’était pas « DNS est lent ». C’était : ne jamais déduire le goulot d’après une seule métrique. Un CPU élevé peut être un symptôme d’attente autant qu’une cause de lenteur.

Mini-histoire 2 : Une optimisation qui s’est retournée

Une grande plateforme analytique avait un job nocturne qui prenait des heures. Quelqu’un l’a profilé et a trouvé que la base faisait beaucoup de petites écritures.
La correction proposée paraissait élégante : augmenter la taille des lots et rendre les écritures asynchrones. Le job fut plus rapide en staging. Tout le monde a applaudi.

En production, le job s’exécutait plus vite — jusqu’à ce qu’il ne le fasse plus. Environ 40 minutes après le départ, la latence d’écriture DB a explosé, le lag de réplication a augmenté, et les latences de lecture applicative
ont commencé à vaciller. L’astreinte a vu les p99 monter en flèche et a rollbacké le job. Le système s’est rétabli lentement, comme quelqu’un qui se réveille après une mauvaise décision.

Le retour de bâton était classique : de plus grands lots asynchrones ont augmenté l’amplification d’écriture et la pression sur les checkpoints. Le background writer et le checkpointer de la BD
ont commencé à combattre la charge. Les files IO se sont remplies. La latence est devenue non linéaire. Un job qui « finissait plus vite » a provoqué une panne de stockage temporaire.

La correction finale n’a pas été « lots plus petits pour toujours ». Ce fut un débit contrôlé : limitation du taux d’écriture, planification explicite hors pointe, et isolation de l’IO DB sur des volumes séparés. Le système est devenu plus lent au sens étroit — les jobs ont pris plus de temps — mais il est devenu stable et prévisible.
Voilà l’efficacité.

Blague #2 : La base de données la plus rapide est celle avec laquelle vous ne faites pas accidentellement un benchmark en provoquant une panne de production.

Mini-histoire 3 : Une pratique ennuyeuse mais correcte qui a sauvé la mise

Un service de paiements tournait sur un mélange d’hôtes physiques et virtuels avec un SLO strict et un faible goût pour le drame. Ils avaient une habitude :
chaque changement de performance nécessitait un plan de rollback et un critère de succès mesurable avant fusion.

Un après-midi, un changement est passé en production qui modifiait le comportement du pool de connexions. La latence n’a pas explosé immédiatement ; elle s’est dégradée subtilement.
Le p95 a légèrement grimpé, le p99 a plus augmenté, puis le taux de retries s’est accru. Le service n’était pas tombé. Il devenait juste coûteux à maintenir.

La discipline ennuyeuse de l’équipe a joué. Ils avaient un dashboard montrant les retries par endpoint et les signaux de saturation par dépendance.
Ils avaient aussi un déploiement canari avec rollback automatisé sur régression de latence en queue. Le changement s’est reverti avant que la plupart des clients ne le remarquent.

Le postmortem n’était pas héroïque. Il était pratique. Ils ont ajusté les limites du pool, ajouté un backoff jitterisé, et mis à jour les tests de charge pour refléter les rafales réelles de trafic.
Pas de nouveau hardware. Pas d’escalade à minuit. Juste de la compétence et de la retenue.

La leçon : l’efficacité est une hygiène opérationnelle. Le système est resté suffisamment rapide parce que l’équipe est restée suffisamment disciplinée.

Erreurs courantes : symptôme → cause racine → correctif

Voici des modes de défaillance qui réapparaissent dans les incidents de performance — surtout ceux déclenchés par des initiatives « rendre plus rapide ».
Chacun inclut une correction spécifique parce que les conseils vagues reproduisent les incidents.

1) Symptom: p99 latency spikes, p50 looks normal

Cause racine : files d’attente sous rafale ; une dépendance en aval a de la gigue ; les retries amplifient la charge ; pauses GC.

Fix : ajouter du backpressure (limiter la concurrence), implémenter un backoff exponentiel jitterisé, et instrumenter les latences des dépendances séparément du global.

2) Symptom: CPU is high, scaling doesn’t help

Cause racine : contention sur des verrous, overhead noyau, ou attente IO/réseau pendant que les threads tournent ou switchent très souvent.

Fix : réduire le nombre de threads, profiler la contention (profilage mutex/verrou), isoler les dépendances lentes ; vérifier avec pidstat -w et des flamegraphs.

3) Symptom: disk %util near 100%, but throughput is not impressive

Cause racine : petits IO aléatoires, écritures synchrones, amplification d’écriture, ou un processus background mal comporté.

Fix : changer les patterns IO (batching avec limites, pas illimité), tuner l’usage d’fsync, séparer WAL/logs, et identifier les processus IO-intensifs avec iotop.

4) Symptom: “We upgraded to faster SSDs but it’s not faster”

Cause racine : le goulot s’est déplacé vers le CPU, la bande passante mémoire, le réseau, ou la sérialisation applicative ; aussi possible contention PCIe.

Fix : mesurer bout en bout ; vérifier steal CPU, run queue, et hotspots applicatifs. Des composants plus rapides ne corrigent pas du code sérialisé.

5) Symptom: latency spikes during deployments

Cause racine : caches froids, thundering herd au démarrage, tempêtes de connexions, ou churn d’autoscaling.

Fix : réchauffer les caches progressivement, étaler les startups, utiliser pooling de connexions, et limiter le débit des tâches d’initialisation.

6) Symptom: periodic latency spikes every N minutes

Cause racine : jobs planifiés (checkpoints, compaction, sauvegardes), cron storms, ou autosnapshots.

Fix : déplacer les jobs hors pointe, les brider, ou isoler les ressources. Confirmer en corrélant les timestamps avec les logs de jobs et les graphs IO.

7) Symptom: Kubernetes pods “use 1 core” but latency is high

Cause racine : throttling CPU aux limites ; mismatch request/limit ; contention sur le nœud.

Fix : ajuster limites/requests CPU, utiliser moins de pods plus gros si l’overhead domine, et suivre explicitement les métriques de throttling.

8) Symptom: network graphs look fine but clients time out

Cause racine : perte de paquets, retransmissions, latence DNS, ou backlog SYN sous rafale.

Fix : vérifier retransmissions, stats NIC, santé du résolveur ; tuner le backlog et réduire le churn de connexion avec keep-alives/pooling.

Checklists / plan étape par étape

Checklist A: Before you optimize anything

  1. Écrire l’objectif comme une déclaration SLO : « p99 < X ms à Y RPS avec taux d’erreur < Z. »
  2. Décider du budget : augmentation de coût autorisée, complexité autorisée, risque autorisé.
  3. Définir le plan de rollback. Si vous ne pouvez pas revenir en arrière, vous ne « tunez » pas, vous « espérez ».
  4. Choisir la fenêtre de mesure et la forme de charge réaliste (y compris rafales et churn de cache).
  5. Capturer une baseline : percentiles, métriques de saturation et latences des dépendances.

Checklist B: Safe optimization sequence (the order matters)

  1. Supprimer du travail : éviter les appels inutiles, réduire la taille des payloads, éviter la sérialisation redondante.
  2. Supprimer l’amplification : arrêter les tempêtes de retries, arrêter les requêtes N+1, arrêter les patterns d’amplification d’écriture.
  3. Contrôler la concurrence : définir des limites, appliquer du backpressure, protéger les dépendances.
  4. Améliorer la localité : caching borné, placement des données, réduire le chatter inter-zones.
  5. Only then scale : scaler uniquement si le travail est inévitable et mesuré comme saturé.

Checklist C: Storage-specific efficiency plan

  1. Mesurer la latence et la profondeur de file d’abord (await, aqu-sz), pas seulement le débit.
  2. Identifier les écrivains sync-intensifs (WAL, journaling, patterns fsync).
  3. Séparer les chemins d’écriture chauds des données froides quand possible (logs/WAL vs fichiers de données).
  4. Valider les options de système de fichiers ; éviter les flags « performance » que vous ne comprenez pas.
  5. Planifier la capacité avec marge : un stockage presque plein devient plus lent, et vous vous en voudrez plus tard.

Checklist D: If you must “make it faster” under pressure

  1. Choisir un levier avec un faible rayon d’explosion : scaler la couche stateless, limiter les abuseurs, désactiver les fonctionnalités non critiques.
  2. Surveiller la latence en queue et le taux d’erreur pendant 10–15 minutes après le changement.
  3. Si la queue s’améliore mais que le coût explose, traitez cela comme une atténuation, pas une correction.
  4. Planifier la correction réelle : supprimer du travail, corriger l’amplification, isoler les dépendances.

Que faire au lieu de « plus rapide » : concevoir pour la performance efficace

Concevoir pour le comportement en queue, pas pour des benchmarks héroïques

Les benchmarks sont nécessaires, mais le benchmark typique est une ligne droite : charge constante, caches chauds, pas de déploiement, pas d’injection de panne.
La production n’est pas une ligne droite. C’est une collection de mardis bizarres.

Les systèmes efficaces traitent la latence en queue comme une donnée d’entrée de conception :

  • Utiliser des timeouts et des budgets par dépendance.
  • Annuler le travail quand le client est parti.
  • Arrêter de retryer immédiatement ; retryer avec backoff et jitter, et plafonner les tentatives.
  • Préférer les opérations idempotentes pour que les retries n’entraînent pas corruption de données ou écritures dupliquées.

Les objectifs d’utilisation ne sont pas un jugement moral

Il existe un culte du « tout garder à 80% d’utilisation ». Ça sonne efficace. C’est parfois imprudent.
Une haute utilisation est acceptable quand la variance est faible et le travail prévisible. Les systèmes distribués ne sont pas à faible variance.

Pour les services sensibles à la latence, l’objectif est souvent : baisser l’utilisation moyenne pour conserver une marge de manœuvre lors des rafales.
Cette marge empêche l’effondrement des files d’attente lors des pics de trafic, des miss de cache, des basculements et des déploiements.

Connaître votre classe de goulot, puis choisir la bonne optimisation

Le travail de performance devient coûteux lorsqu’il est aveugle. La même demande « rendre plus rapide » a des réponses différentes selon ce qui est saturé :

  • Saturation CPU : réduire le CPU par requête, améliorer les algorithmes, réduire les coûts de sérialisation, utiliser de meilleures structures de données.
  • Saturation mémoire : améliorer la localité, réduire les allocations, réduire le working set, tuner les caches avec des limites explicites.
  • Saturation IO : réduire les écritures sync, batcher prudemment, changer les patterns d’accès, isoler les volumes, réduire l’amplification d’écriture.
  • Saturation réseau : compresser, réduire le chatter, co-localiser les dépendances, corriger pertes/retransmissions.
  • Contention/verrou : réduire l’état partagé, sharder, utiliser prudemment des structures lock-free, éviter les mutex globaux.

FAQ

1) Si les utilisateurs se plaignent que c’est lent, ne devrait-on pas juste accélérer ?

Rendre cela d’abord prévisible. Les utilisateurs détestent l’incohérence plus que « ne pas être le plus rapide possible ».
Corrigez la latence en queue, les retries et les timeouts avant de courir après des gains de benchmark.

2) Quelle est la définition la plus simple de l’efficacité pour une équipe SRE ?

Respecter les SLO avec un coût minimal et un risque opérationnel minimal. Si un gain de vitesse augmente les incidents, ce n’est pas efficace — même si le graphique est flatteur.

3) Pourquoi le scaling ne réduit-il parfois pas la latence ?

Parce que vous n’êtes pas limité par la ressource scalée. Ou vous êtes limité par une dépendance partagée (DB, cache, réseau, stockage).
Le scaling peut aussi augmenter l’overhead de coordination et le churn de connexions.

4) Le caching est-il toujours un gain d’efficacité ?

Non. Les caches peuvent créer des stampedes, augmenter la variance, masquer des problèmes de fraîcheur des données et compliquer l’invalidation.
Cachez avec des limites explicites, observez les évictions et les pics de miss, et concevez pour les cold starts.

5) Comment savoir si mon problème de stockage est de la latence ou du débit ?

Regardez await, profondeur de file (aqu-sz) et corrélations avec la latence service p99.
Un débit élevé avec une latence basse stable peut être acceptable ; un faible débit avec une latence élevée indique généralement de petits IO aléatoires ou des écritures sync-intensives.

6) Pourquoi les retries empirent-ils tout ?

Les retries augmentent la charge précisément quand le système est le moins capable de la supporter. C’est l’amplification par retry.
Utilisez un backoff exponentiel avec jitter, plafonnez les tentatives et traitez les timeouts comme un signal pour réduire la charge.

7) Quelle est la plus grosse erreur « plus rapide » en environnements conteneurisés ?

Ignorer le throttling et les effets voisins-bruyants. Votre appli peut être « rapide » mais limitée par cgroup.
Suivez le throttling CPU, la pression mémoire et la saturation au niveau du nœud.

8) Quand l’achat de hardware plus rapide est-il réellement la bonne réponse ?

Quand le goulot est mesuré, persistant et le travail inévitable — et quand le changement n’augmente pas la fragilité opérationnelle.
Même alors, préférez le scale-out et l’isolation plutôt qu’un tuning héroïque d’un seul nœud quand la fiabilité compte.

9) Quelle est la manière la plus rapide de perdre du temps en ingénierie de performance ?

Optimiser sur un coup de hunch. Mesurez d’abord, changez une chose à la fois, et vérifiez avec la même forme de charge.

Prochaines étapes qui fonctionnent en production réelle

Si vous retenez une chose : arrêtez de demander « comment rendre ça plus rapide ? » et commencez à demander « que payons‑nous pour cette vitesse ? »
L’efficacité est la discipline de rendre explicites les compromis.

  1. Choisir un SLO orienté utilisateur unique et suivre la latence en queue, les erreurs et les retries par dépendance.
  2. Adopter la méthode de diagnostic rapide et la répéter pendant des heures de bureau, pas en incident.
  3. Construire une porte de changement performance : métriques de succès, plan de rollback, et vérification canari sur p95/p99.
  4. Supprimer l’amplification (retries, requêtes N+1, amplification d’écriture) avant de scaler.
  5. Utiliser la marge intentionnellement : garder assez de slack pour absorber rafales, déploiements et pannes sans effondrement des files.

On ne gagne pas la production en étant le plus rapide dans la salle. On gagne en étant suffisamment rapide, de façon régulière, pendant que les autres sont occupés à reconstruire après leur « optimisation ».

← Précédent
Netscape vs Internet Explorer : la guerre des navigateurs qui a façonné le web
Suivant →
Docker « Text file busy » lors du déploiement : la correction qui stoppe les redémarrages instables

Laisser un commentaire