Aller au contenu

Trouvez le goulot d'étranglement

L’an dernier, 152 personnes naviguaient en même temps sur la billetterie au pic d’ouverture. Cette année, l’équipe marketing en annonce 400. Le site tiendra-t-il ? Et s’il ne tient pas, faut-il acheter un processeur plus rapide, un meilleur disque, ou un deuxième serveur web ?

Vous allez voir qu’avec les trois demandes de service calculées au chapitre précédent, et rien d’autre, on peut déjà répondre à ces questions à la main. C’est toute la puissance des bornes asymptotiques.

Reprenons la loi de la demande de service, écrite à l’envers : Ui=X0 DiU_i = X_0 \, D_i. Quand le débit du système X0X_0 augmente, l’utilisation de toutes les ressources augmente, chacune proportionnellement à sa demande. Or une utilisation ne peut pas dépasser 1 : une ressource ne peut pas être occupée plus de 100 % du temps. Donc, pour chaque ressource, X0Di≤1X_0 D_i \leq 1, et la contrainte la plus forte vient de la ressource de plus grande demande :

X0≤1Dmax⁡X_0 \leq \frac{1}{D_{\max}}

où Dmax⁡=max⁡iDiD_{\max} = \max_i D_i est la plus grande demande de service. Cette ressource s’appelle le goulot d’étranglement (bottleneck) : c’est elle qui atteint 100 % d’utilisation la première, et elle fixe le débit maximal du système entier.

Remarquez que le goulot n’est pas forcément la ressource la plus lente par visite. Le processeur fait 5 visites de 6 ms, le disque 4 visites de 15 ms : c’est le produit ViSiV_i S_i qui compte.

0,02,55,07,510,012,515,017,520,0débit du système X₀ (commandes/s)0,00,20,40,60,81,01,2utilisation U0,20,30,6U = 1 : occupée 100 % du tempsle disque sature :X₀ ≤ 1 / 0,06 ≈ 16,7mesuré : 10 req/sweb : U = X₀ × 20 msprocesseur : U = X₀ × 30 msdisque : U = X₀ × 60 ms
Chaque utilisation croît en ligne droite, de pente Dᵢ. Le disque, de plus grande demande, touche 100 % le premier, à 16,7 commandes/s : c'est lui qui plafonne tout le système.

Considérons un système fermé : NN utilisateurs, chacun avec un temps de réflexion ZZ, et KK ressources de demandes D1,…,DKD_1, \dots, D_K. Notons D=∑iDiD = \sum_i D_i la demande totale, c’est-à-dire le temps de réponse d’une requête qui ne rencontrerait aucune file d’attente.

Deux contraintes limitent le débit :

  • Peu d’utilisateurs : même sans aucune attente, un utilisateur ne peut pas envoyer plus d’une requête toutes les D+ZD + Z secondes. Avec NN utilisateurs, X0≤N/(D+Z)X_0 \leq N / (D + Z).
  • Beaucoup d’utilisateurs : le goulot impose X0≤1/Dmax⁡X_0 \leq 1 / D_{\max}.
X0(N)≤min⁡(ND+Z,  1Dmax⁡)X_0(N) \leq \min\left( \frac{N}{D + Z}, \; \frac{1}{D_{\max}} \right)

De même, le temps de réponse est minoré de deux façons :

  • une requête doit au minimum recevoir son service : R≥DR \geq D ;
  • avec la loi du temps de réponse interactif, R=N/X0−ZR = N / X_0 - Z, et comme X0≤1/Dmax⁡X_0 \leq 1/D_{\max}, on obtient R≥NDmax⁡−ZR \geq N D_{\max} - Z.
R(N)≥max⁡(D,  NDmax⁡−Z)R(N) \geq \max\left( D, \; N D_{\max} - Z \right)

Les deux droites de la borne sur le débit se croisent en :

N∗=D+ZDmax⁡N^* = \frac{D + Z}{D_{\max}}

C’est le point de saturation. En dessous de N∗N^*, le système peut en théorie servir tout le monde sans attente, et le débit croît avec NN. Au-delà, le goulot est saturé : ajouter des utilisateurs n’augmente plus le débit, mais seulement le temps de réponse, qui croît alors de Dmax⁡D_{\max} par utilisateur supplémentaire.

Le graphique ci-dessous montre les deux bornes du débit (en pointillés) et, en trait plein, le débit exact que calculera l’algorithme MVA au chapitre 5. La vraie courbe reste sous les bornes et s’en éloigne surtout autour de N∗N^*, là où les files d’attente se forment.

0100200300400nombre d’utilisateurs N05101520débit X (req/s)N / (D + Z)1 / Dmax ≈ 16,7débit exact(MVA)N* ≈ 252152 : 10 req/s0100200300400nombre d’utilisateurs N0246810temps de réponse R (s)R ≥ D = 0,11 sR ≥ N Dmax − Z400 : R ≥ 9 sN* ≈ 252temps de réponseexact (MVA)
Les bornes (pointillés) encadrent la courbe exacte de MVA (trait plein). L'écart est le plus grand autour de N* ≈ 252, là où les files se forment ; au-delà, chaque utilisateur ajouté coûte 60 ms de temps de réponse.

Les bornes donnent une règle de décision très simple. Seule la réduction de Dmax⁡D_{\max} relève le plafond de débit. Améliorer une autre ressource réduit un peu DD (donc le temps de réponse à faible charge) mais ne change rien au débit maximal.

Comparons trois investissements pour la billetterie :

AméliorationNouvelles demandes (web, CPU, disque)Xmax⁡X_{\max}N∗N^*
Aucune20, 30, 60 ms16,7 req/s252
Serveur web deux fois plus rapide10, 30, 60 ms16,7 req/s252
Processeur deux fois plus rapide20, 15, 60 ms16,7 req/s252
Disque SSD deux fois plus rapide20, 30, 30 ms33,3 req/s503

Seul le disque fait bouger les choses, et il double le débit maximal. Mais attention : avec un disque à 30 ms, le processeur (30 ms aussi) devient co-goulot. Pour aller plus loin, il faudra améliorer les deux. C’est le phénomène du goulot qui se déplace : on n’en a jamais fini, on repousse simplement la limite jusqu’à la ressource suivante.

Et avec le SSD, les 400 utilisateurs de cette année passent-ils ? N∗≈503>400N^* \approx 503 > 400 : oui, en théorie on reste sous la saturation. La borne R≥max⁡(0,08 ; 400×0,03−15)R \geq \max(0{,}08 \, ; \, 400 \times 0{,}03 - 15) vaut 0,08 s, ce qui est encourageant, mais une borne reste optimiste : le chapitre 5 donnera le vrai temps de réponse.

0200400600nombre d’utilisateurs N05101520253035borne sur le débit X (req/s)16,7 req/s33,3 req/s400 cetteannéeaujourd’hui : N* ≈ 252SSD : N* ≈ 5030200400600nombre d’utilisateurs N05101520borne sur le temps de réponse R (s)R ≥ 9 sR ≥ 0,08 s
Diviser par deux la demande du goulot double le plafond et repousse N* au-delà des 400 utilisateurs attendus ; améliorer le web ou le processeur ne déplacerait aucune de ces courbes.
def bornes(D, Z, N):
"""Bornes asymptotiques d'un système fermé : N utilisateurs, temps de réflexion Z."""
D_tot, D_max = sum(D.values()), max(D.values())
goulot = max(D, key=D.get)
X_max = min(N / (D_tot + Z), 1 / D_max)
R_min = max(D_tot, N * D_max - Z)
N_sat = (D_tot + Z) / D_max
return goulot, X_max, R_min, N_sat
D = {"web": 0.020, "cpu": 0.030, "disque": 0.060} # demandes de service (s)
for N in (152, 400):
goulot, X, R, N_sat = bornes(D, Z=15, N=N)
print(f"N={N}: goulot={goulot}, X <= {X:.1f} req/s, R >= {R:.2f} s, N* = {N_sat:.0f}")
  • Comme Ui=X0Di≤1U_i = X_0 D_i \leq 1, le débit est plafonné par la ressource de plus grande demande : X0≤1/Dmax⁡X_0 \leq 1/D_{\max}. C’est le goulot.
  • Borne sur le débit d’un système fermé : X0(N)≤min⁡(N/(D+Z), 1/Dmax⁡)X_0(N) \leq \min(N/(D + Z), \, 1/D_{\max}).
  • Borne sur le temps de réponse : R(N)≥max⁡(D, NDmax⁡−Z)R(N) \geq \max(D, \, N D_{\max} - Z).
  • Le point de saturation N∗=(D+Z)/Dmax⁡N^* = (D + Z)/D_{\max} sépare le régime où le débit croît de celui où seul le temps de réponse augmente.
  • Seule la réduction de Dmax⁡D_{\max} relève le plafond ; après amélioration, le goulot se déplace.
  • Les bornes sont optimistes : elles ignorent les files d’attente, que les chapitres 4 et 5 prennent en compte.

Exercice · Trouver le goulot

facile

Un serveur d’analyse de données traite 20 tâches par seconde. On mesure les utilisations suivantes : processeur 0,5, disque A 0,7, disque B 0,3, réseau 0,2.

  1. Calculez la demande de service de chaque ressource.
  2. Quel est le goulot, et quel est le débit maximal ?
Voir la solution
  1. Par la loi de la demande de service, Di=Ui/X0D_i = U_i / X_0 : processeur 0,5/20=250{,}5/20 = 25 ms, disque A 0,7/20=350{,}7/20 = 35 ms, disque B 0,3/20=150{,}3/20 = 15 ms, réseau 0,2/20=100{,}2/20 = 10 ms.
  2. Le goulot est le disque A (35 ms). Le débit maximal vaut 1/0,035≈28,61/0{,}035 \approx 28{,}6 tâches/s. On pouvait le voir directement : c’est la ressource la plus utilisée, et elle atteindra 100 % quand le débit sera multiplié par 1/0,7≈1,431/0{,}7 \approx 1{,}43.

En code

X0 = 20 # tâches/s
U = {"processeur": 0.5, "disque A": 0.7, "disque B": 0.3, "réseau": 0.2}
D = {nom: u / X0 for nom, u in U.items()} # loi de la demande de service
for nom, d in D.items():
print(f"{nom:<10} D = {d * 1000:.0f} ms") # 25, 35, 15, 10 ms
goulot = max(D, key=D.get)
print(goulot, round(1 / D[goulot], 1)) # disque A 28.6 tâches/s

Exercice · Bornes d'un système interactif

moyen

Un intranet a trois ressources de demandes DCPU=40D_{\text{CPU}} = 40 ms, Ddisque=100D_{\text{disque}} = 100 ms et Dreˊseau=20D_{\text{réseau}} = 20 ms. Les employés réfléchissent en moyenne Z=10Z = 10 s entre deux requêtes.

  1. Calculez le point de saturation N∗N^*.
  2. Donnez les bornes sur le débit et sur le temps de réponse pour 50 puis 200 employés.
Voir la solution

D=0,16D = 0{,}16 s et Dmax⁡=0,1D_{\max} = 0{,}1 s (le disque).

  1. N∗=(0,16+10)/0,1=101,6N^* = (0{,}16 + 10)/0{,}1 = 101{,}6, soit environ 102 employés.
  2. Pour N=50N = 50 : X0≤min⁡(50/10,16 ; 10)≈4,92X_0 \leq \min(50/10{,}16 \, ; \, 10) \approx 4{,}92 req/s et R≥max⁡(0,16 ; 5−10)=0,16R \geq \max(0{,}16 \, ; \, 5 - 10) = 0{,}16 s. Pour N=200N = 200 : X0≤min⁡(19,7 ; 10)=10X_0 \leq \min(19{,}7 \, ; \, 10) = 10 req/s et R≥max⁡(0,16 ; 200×0,1−10)=10R \geq \max(0{,}16 \, ; \, 200 \times 0{,}1 - 10) = 10 s. Avec 200 employés, le système est largement saturé : au moins 10 s par requête.

En code

D = {"cpu": 0.040, "disque": 0.100, "réseau": 0.020} # demandes de service (s)
Z = 10 # temps de réflexion (s)
D_tot, D_max = sum(D.values()), max(D.values())
N_sat = (D_tot + Z) / D_max # point de saturation
print(f"N* = {N_sat:.1f}") # N* = 101.6
for N in (50, 200):
X_borne = min(N / (D_tot + Z), 1 / D_max) # borne sur le débit
R_borne = max(D_tot, N * D_max - Z) # borne sur le temps de réponse
print(f"N = {N}: X <= {X_borne:.2f} req/s, R >= {R_borne:.2f} s")
# N = 50: X <= 4.92 req/s, R >= 0.16 s
# N = 200: X <= 10.00 req/s, R >= 10.00 s

Exercice · Dimensionner un achat

difficile

Reprenez l’intranet de l’exercice précédent. L’entreprise veut que 200 employés aient un temps de réponse borné inférieurement par moins de 1 s (c’est une condition nécessaire, pas suffisante). Deux options : un disque deux fois plus rapide, ou deux disques identiques à l’actuel entre lesquels on répartit équitablement les données.

  1. Calculez N∗N^* et la borne sur RR pour 200 employés dans chaque cas.
  2. Laquelle choisir ?
Voir la solution
  1. Disque deux fois plus rapide : Ddisque=50D_{\text{disque}} = 50 ms. Le nouveau goulot est le disque à 50 ms (le processeur est à 40 ms). D=0,11D = 0{,}11 s, N∗=10,11/0,05≈202N^* = 10{,}11/0{,}05 \approx 202. Pour 200 employés, R≥max⁡(0,11 ; 200×0,05−10)=max⁡(0,11 ; 0)=0,11R \geq \max(0{,}11 \, ; \, 200 \times 0{,}05 - 10) = \max(0{,}11 \, ; \, 0) = 0{,}11 s. Deux disques : chacun reçoit la moitié des accès, donc D=50D = 50 ms par disque. Les demandes sont 40, 50, 50 et 20 ms, D=0,16D = 0{,}16 s, Dmax⁡=0,05D_{\max} = 0{,}05 s, N∗=10,16/0,05≈203N^* = 10{,}16/0{,}05 \approx 203. Pour 200 employés, R≥max⁡(0,16 ; 0)=0,16R \geq \max(0{,}16 \, ; \, 0) = 0{,}16 s.
  2. Les deux options donnent le même plafond de débit (20 req/s) et une borne sur RR bien inférieure à 1 s. Mais dans les deux cas, 200 employés sont juste sous le point de saturation : les files d’attente seront importantes et le vrai temps de réponse bien supérieur à la borne. Il faudrait aussi traiter le processeur, qui devient presque un co-goulot, ou vérifier avec MVA (chapitre 5). La leçon : la borne est une condition nécessaire, et un système dimensionné pile à N∗N^* est un système qui souffrira.

En code

Z, N = 10, 200
options = {
"disque x2": [0.040, 0.050, 0.020], # CPU, disque, réseau (s)
"deux disques": [0.040, 0.050, 0.050, 0.020], # CPU, disque 1, disque 2, réseau
}
for nom, D in options.items():
D_tot, D_max = sum(D), max(D)
N_sat = (D_tot + Z) / D_max
R_borne = max(D_tot, N * D_max - Z)
print(f"{nom:<13} N* = {N_sat:.0f}, X_max = {1 / D_max:.0f} req/s, R >= {R_borne:.2f} s")
# disque x2 N* = 202, X_max = 20 req/s, R >= 0.11 s
# deux disques N* = 203, X_max = 20 req/s, R >= 0.16 s