Cet article part d’un constat simple : on peut prévoir quand un système va s’effondrer avec une seule formule. La loi de Little relie trois grandeurs qu’on manipule tous les jours — débit, latence, concurrence — et permet de raisonner sur la capacité d’une application sans sortir l’artillerie lourde.

La loi de Little Link to heading

L = λ × W
  • L est le nombre d’éléments présents dans le système
  • λ est le débit
  • W est le temps de traversée du système

Traduit en termes plus familiers :

  • L : la concurrence — le nombre de requêtes en cours de traitement à un instant donné
  • λ : le débit, en requêtes par seconde (RPS)
  • W : la latence

Dit autrement : concurrence = débit × latence.

La formule se manipule dans les trois sens. Deux valeurs connues suffisent pour retrouver la troisième :

On chercheFormuleQuestion
La concurrenceL = λ × WCombien de requêtes sont traitées en même temps ?
La latenceW = L / λCombien de temps une requête peut-elle mettre ?
Le débitλ = L / WCombien de requêtes par seconde peut-on traiter ?

Un point clé pour la suite : la taille d’un pool (threads, connexions…) fixe la concurrence maximale qu’il accepte. En remplaçant L par cette taille, la loi de Little ne donne plus une valeur moyenne mais une borne : la latence maximale admissible, ou le débit maximal atteignable.

L’exercice Link to heading

Partons d’un cas concret : notre système reçoit 2 000 requêtes par seconde. Chaque requête met 40 ms à traverser le système, dont 5 ms passées dans la base de données. Aucune requête n’attend dans une file.

On veut déterminer à quel moment notre application Spring Web (Tomcat), qui appelle une base de données, va craquer.

Notre architecture, avec les valeurs par défaut de Spring Boot :

ÉtageRessource occupéeTaille par défautPropriété
TomcatUn thread pendant toute la requête200server.tomcat.threads.max
HikariCPUne connexion JDBC pendant la requête SQL uniquement10spring.datasource.hikari.maximum-pool-size

Combien de requêtes en vol ? Link to heading

On a le débit et la latence, on en déduit la concurrence : L = λ × W.

  • débit : 2 000 req/s
  • latence : 40 ms, soit 0,04 s

L = 2 000 × 0,04 = 80.

80 requêtes vivent en permanence dans le système. Avec 200 threads Tomcat, on a de la marge.

Et le pool Hikari ? Link to heading

80 requêtes en vol, mais seulement 10 connexions Hikari. Comment ça tient ?

Parce que les 40 ms représentent le temps total de bout en bout. Ce temps se répartit entre la requête SQL, le code métier Java, la sérialisation JSON, etc. Une connexion Hikari n’est mobilisée que pendant la requête SQL — 5 ms, pas 40 ms.

Notons x ce temps passé en base, et appliquons L = λ × W au pool Hikari seul :

L = 2 000 × 0,005 = 10

À 2 000 req/s avec des requêtes SQL de 5 ms, il faut 10 connexions simultanées. Ou, vu autrement, chaque connexion absorbe 200 req/s.

Notre pool en a justement 10 : il tourne à 100 %.

La latence passe de 40 ms à 100 ms Link to heading

Un jour, sans variation de trafic, la latence grimpe à 100 ms. La loi de Little donne :

L = 2 000 × 0,1 = 200

200 requêtes en permanence dans le système. Le trafic n’a pas changé, mais la charge réelle a été multipliée par 2,5. Or Tomcat a exactement 200 threads : il est à 100 %, sans marge.

Reste à comprendre d’où sortent ces 60 ms supplémentaires. Tout dépend de x, le temps de la requête SQL.

Cas 1 : x reste à 5 ms — le problème est dans l’application Link to heading

Côté Hikari :

L = 2 000 × 0,005 = 10

Toujours 10 connexions nécessaires, et on en a 10. La base répond aussi vite qu’avant, Hikari n’y est pour rien.

Les 60 ms en plus sont consommées ailleurs dans l’application : code métier, sérialisation JSON, pause du Garbage Collector, appel HTTP vers un service tiers… C’est par là qu’il faut creuser.

Le système tient encore, mais tout juste. Tomcat est à 100 %, Hikari aussi. La moindre milliseconde de plus et les requêtes se mettront à faire la queue pour obtenir un thread.

Cas 2 : x passe de 5 ms à 50 ms — la base de données sature Link to heading

Côté Hikari :

L = 2 000 × 0,05 = 100

100 requêtes réclament une connexion en même temps, mais il n’y en a que 10. Les 90 autres patientent.

On peut regarder la situation sous l’angle du débit. On connaît la taille du pool et la latence, on cherche le débit maximal : λ = L / W.

λ = 10 / 0,05 = 200 req/s

Avec des requêtes SQL de 50 ms, le pool ne laisse passer que 200 req/s. Or il en arrive 2 000.

Et pendant qu’une requête attend sa connexion Hikari, elle garde son thread Tomcat. Ce temps d’attente gonfle la latence totale, ce qui augmente la concurrence côté Tomcat. Tomcat finit par saturer lui aussi, mais c’est un symptôme : la cause première, c’est la base de données.

Contrairement au cas 1, la latence ne va pas se stabiliser à 100 ms. Ces 100 ms ne sont qu’un instantané : il arrive dix fois plus de requêtes que le pool n’en écoule, la file d’attente s’allonge en continu et la latence monte jusqu’à l’effondrement.

Mesurer x en production Link to heading

Inutile de deviner : Spring Boot Actuator et Micrometer exposent tout ce qu’il faut.

MétriqueCe qu’elle mesureCas 1Cas 2
hikaricp.connections.usageLe temps pendant lequel une connexion est utilisée (x)5 ms50 ms
hikaricp.connections.pendingLe nombre de requêtes en attente d’une connexion0des dizaines, puis plus
tomcat.threads.busyLe nombre de threads Tomcat occupés200200

Dans les deux cas, Tomcat affiche 200 threads occupés. Ce sont hikaricp.connections.usage et hikaricp.connections.pending qui font la différence : ils orientent le diagnostic vers le code applicatif ou vers la base de données.

À quel débit le système casse-t-il ? Link to heading

Reprenons la question initiale.

Commençons par Tomcat. On connaît la taille du pool (200 threads) et le débit (2 000 req/s), on en tire la latence maximale admissible : W = L / λ.

W = 200 / 2 000 = 0,1 s

Tant qu’une requête met moins de 100 ms, Tomcat encaisse. C’est la limite exacte du cas 1.

Mais une requête passe par plusieurs pools, chacun avec sa propre limite. Même calcul pour Hikari :

W = 10 / 2 000 = 0,005 s

À 2 000 req/s, une requête SQL ne peut pas dépasser 5 ms. C’est pile la durée actuelle de nos requêtes : le pool Hikari fonctionne sans aucune marge. La moindre augmentation de la latence SQL fait tomber le système, bien avant que Tomcat n’atteigne sa propre limite de 100 ms.

La loi du goulot d’étranglement Link to heading

Le débit d’un système est borné par sa ressource la plus contrainte.

C’est la bottleneck law :

λ_max = min(capacité de chaque ressource)

Jusqu’ici, on est parti du débit (2 000 req/s) pour identifier ce qui allait lâcher en premier. On peut aussi raisonner dans l’autre sens : connaissant le temps passé dans chaque pool, on estime le débit maximal du système.

Pour chaque pool, λ = L / W donne :

PoolCalculCapacité max
Tomcat200 / 0,04 s5 000 req/s
HikariCP10 / 0,005 s2 000 req/s
λ_max = min(5 000, 2 000) = 2 000 req/s

Le système plafonne à 2 000 req/s — exactement le trafic actuel. Zéro marge, confirmé par un autre angle.

Si demain le trafic passe à 3 000 req/s, Hikari ne suivra pas. Il faudrait au minimum :

L = 3 000 × 0,005 = 15 connexions

Conclusion Link to heading

On a utilisé la loi de Little dans ses trois lectures :

  • débit + latence → concurrence, donc taille de pool nécessaire : L = λ × W
  • taille du pool + débit → latence maximale : W = L / λ
  • taille du pool + latence → débit maximal : λ = L / W

Et puisqu’une requête traverse plusieurs pools, il faut appliquer ce calcul à chacun : c’est toujours le plus contraint qui dicte la limite du système.