Aller au contenu

Analysez une file d'attente M/M/1

Votre API de prédiction tourne sur un seul serveur, capable de traiter 10 requêtes par seconde. Elle en reçoit 8 par seconde : le serveur est occupé à 80 %, il lui reste de la marge… Et pourtant, les utilisateurs se plaignent de temps de réponse cinq fois plus longs que le temps de calcul d’une requête. Pourquoi ? Et que se passerait-il si le trafic montait à 9,5 requêtes par seconde ?

Les files d’attente répondent exactement à ce genre de questions. Dans ce chapitre, vous allez apprendre à décrire une file avec la notation de Kendall, puis à analyser la plus simple et la plus importante d’entre elles, la file M/M/1. Nous suivrons cette API mono-serveur tout au long du chapitre.

Une file d’attente « isolée » se compose de clients qui arrivent, attendent éventuellement dans une salle d’attente (le tampon, ou buffer), sont servis par un ou plusieurs serveurs, puis repartent. Ici, les clients sont des requêtes et le serveur est une instance du modèle. Pour la décrire, il faut préciser cinq caractéristiques :

  • le processus d’arrivée : la loi des durées entre deux arrivées ;
  • le service : la loi de la durée de service d’un client ;
  • le nombre de serveurs qui travaillent en parallèle ;
  • la capacité du système : le nombre maximal de clients présents (en attente et en service). Par défaut, on la suppose infinie. Une salle d’attente de cabinet médical, ou le tampon d’un routeur réseau, ont une capacité finie : un client qui arrive quand tout est plein est perdu ;
  • la discipline de service : l’ordre dans lequel on sert les clients en attente.
arrivéesloi desinter-arrivéestampon (salle d’attente)capacité N, discipline FIFO, LIFO, RR, PS…serveurs serveurs,loi du servicedépartsclient perdu si le système est plein
Les cinq ingrédients d'une file : la loi des arrivées, le tampon (sa capacité et sa discipline), le nombre de serveurs et la loi du service. Si la capacité est finie, un client qui trouve le système plein est perdu.

Les disciplines les plus courantes sont FIFO (premier arrivé, premier servi, la discipline par défaut), LIFO (dernier arrivé, premier servi, comme une pile), RR (tourniquet, ou round robin : chaque client reçoit à tour de rôle un petit quantum de service, comme les processus d’un système d’exploitation) et PS (partage du serveur, processor sharing : la limite du tourniquet quand le quantum tend vers zéro, tous les clients présents sont servis simultanément à vitesse réduite).

On peut aussi introduire des priorités. Avec une priorité non préemptive, un client prioritaire passe devant la file mais attend la fin du service en cours. Avec une priorité préemptive, il interrompt le client en service, qui reprendra ensuite de trois façons possibles : là où il s’était arrêté (preemptive resume), en recommençant un service de même durée (repeat identical) ou en recommençant avec une nouvelle durée tirée au hasard (repeat different). Aux urgences, un blessé grave interrompt la consultation en cours : c’est une priorité préemptive.

Pour nommer une file en quelques caractères, on utilise la notation de Kendall :

FT / FS / s / NF_T \,/\, F_S \,/\, s \,/\, N
  • FTF_T est la loi des temps inter-arrivées ;
  • FSF_S est la loi des durées de service ;
  • ss est le nombre de serveurs ;
  • NN est la capacité du système, omise quand elle est infinie.

Les lois se notent par une lettre :

LettreLoiExemple
Mexponentielle (markovienne, sans mémoire)arrivées de Poisson
Ddéterministe (durée constante)un robot qui traite chaque pièce en 30 s exactement
EkE_kErlang d’ordre kk : somme de kk phases exponentielles successivesune inscription en kk guichets successifs
HkH_khyper-exponentielle : une exponentielle choisie au hasard parmi kkdes requêtes simples ou lourdes
Ggénérale, quelconqueun service mesuré, sans loi connue
GIgénérale, avec des durées indépendantesdes arrivées renouvelées quelconques

Ainsi, M/M/1 désigne une file à arrivées de Poisson, service exponentiel, un serveur et capacité infinie ; M/D/1 la même avec un service de durée fixe ; M/M/3/10 une file à trois serveurs qui accepte au plus 10 clients.

La file M/M/1 : un processus de naissance et de mort

Section intitulée « La file M/M/1 : un processus de naissance et de mort »

Dans une file M/M/1, les requêtes arrivent selon un processus de Poisson de taux λ\lambda et chaque service dure un temps exponentiel de taux μ\mu. Grâce à l’absence de mémoire, le nombre XtX_t de requêtes présentes (en attente ou en service) est une chaîne de Markov, et plus précisément un processus de naissance et de mort (chapitre 6) :

  • une arrivée fait passer de nn à n+1n + 1, au taux λn=λ\lambda_n = \lambda ;
  • une fin de service fait passer de nn à n−1n - 1, au taux μn=μ\mu_n = \mu dès que n≥1n \geq 1.

On définit l’intensité de trafic (ou charge) du serveur :

ρ=λμ\rho = \frac{\lambda}{\mu}

C’est le rapport entre le rythme des arrivées et le rythme de service. Pour notre API, ρ=8/10=0,8\rho = 8/10 = 0{,}8.

Le chapitre 6 nous donne directement γn=ρn\gamma_n = \rho^n. La série géométrique converge si et seulement si ρ<1\rho < 1 : c’est la condition de stabilité. Il faut, en moyenne, servir plus vite qu’on ne reçoit. Sous cette condition,

Πn=(1−ρ) ρn,n=0,1,2,…\Pi_n = (1 - \rho)\,\rho^n, \qquad n = 0, 1, 2, \dots

Le nombre de clients suit une loi géométrique. En particulier, Π0=1−ρ\Pi_0 = 1 - \rho est la probabilité que le serveur soit inoccupé, et la probabilité d’avoir au moins kk clients vaut P(X≥k)=ρkP(X \geq k) = \rho^k.

02468101214161820nombre n de requêtes dans le système0,000,050,100,150,200,25Πₙ0,2000,1600,128serveur inoccupé : Π₀ = 1 − ρ = 0,2moyenne N̄ = 4moins de 5 requêtes présentesau moins 5 : P(X ≥ 5) = 0,8⁵ ≈ 0,33
Pour ρ = 0,8, l'état le plus probable est la file vide (20 % du temps), mais la queue est longue : les barres roses, 5 requêtes ou plus, pèsent ensemble 0,33, et la moyenne atteint 4.
010203040requêtesλ = 8, μ = 10 : ρ = 0,8, la file revient sans cesse à 00102030405060t (secondes)050100150requêtesdérive moyenne (λ − μ) t = 2tλ = 12, μ = 10 : ρ = 1,2, la file grossit sans limite
En haut (ρ = 0,8), la file fait des excursions mais revient toujours à 0 : il existe un régime stationnaire. En bas (ρ = 1,2), elle grossit d'environ 2 requêtes par seconde : aucune loi stationnaire n'existe.

Le nombre moyen de clients dans le système est l’espérance de la loi géométrique :

Nˉ=∑n≥0n Πn=ρ1−ρ.\bar N = \sum_{n \geq 0} n\,\Pi_n = \frac{\rho}{1 - \rho}.

Mais l’utilisateur se moque du nombre de requêtes présentes : ce qui l’intéresse, c’est le temps de séjour Wˉ\bar W, le temps moyen entre l’arrivée de sa requête et la réponse. Le lien entre les deux est l’un des résultats les plus utiles de toute la théorie, la loi de Little :

Nˉ=λ Wˉ\bar N = \lambda\, \bar W
  • Nˉ\bar N est le nombre moyen de clients présents dans le système ;
  • λ\lambda est le débit moyen des clients qui entrent (et, en régime stationnaire, qui sortent) ;
  • Wˉ\bar W est le temps moyen passé dans le système.

Son intuition est simple : si 8 clients entrent chaque seconde et que chacun reste en moyenne 0,5 seconde, il y a en permanence 8×0,5=48 \times 0{,}5 = 4 clients dans le système. La loi de Little est remarquablement générale : elle ne suppose ni arrivées de Poisson, ni service exponentiel, ni discipline FIFO, seulement un régime stationnaire. Elle s’applique aussi à une partie du système, par exemple à la seule salle d’attente.

0,000,250,500,751,001,251,501,752,00t (secondes)0,02,55,07,510,012,515,017,5nombre de requêtesséjour de la requête 5 :0,49 s (écart horizontal)3 requêtes présentesà t = 1,31 s (écart vertical)aire = somme des séjoursarrivées cumulées A(t)départs cumulés D(t)
L'écart vertical entre arrivées et départs cumulés est le nombre de requêtes présentes, l'écart horizontal le séjour d'une requête. L'aire ombrée se calcule dans les deux sens : c'est l'idée de la loi de Little, N̄ = λ W̄.

Pour la M/M/1, on en tire :

GrandeurFormuleAPI (λ=8\lambda = 8, μ=10\mu = 10)
Nombre moyen dans le système Nˉ\bar Nρ/(1−ρ)\rho/(1-\rho)4 requêtes
Temps moyen de séjour Wˉ=Nˉ/λ\bar W = \bar N/\lambda1/(μ−λ)1/(\mu - \lambda)0,5 s
Temps moyen de service1/μ1/\mu0,1 s
Temps moyen d’attente Wˉq=Wˉ−1/μ\bar W_q = \bar W - 1/\muρ/(μ−λ)\rho/(\mu - \lambda)0,4 s
Nombre moyen en attente Nˉq=λWˉq\bar N_q = \lambda \bar W_qρ2/(1−ρ)\rho^2/(1-\rho)3,2 requêtes

Nous tenons notre explication : une requête passe en moyenne 0,4 seconde à attendre pour 0,1 seconde de calcul. Le temps de réponse est cinq fois le temps de service, alors que le serveur n’est chargé qu’à 80 %.

On peut même aller plus loin : en discipline FIFO, le temps de séjour d’une requête dans une M/M/1 suit une loi exponentielle de taux μ−λ\mu - \lambda. La probabilité qu’une requête attende plus d’une seconde sa réponse vaut donc e−2≈0,135e^{-2} \approx 0{,}135 : environ une requête sur sept.

Que se passe-t-il quand le trafic augmente ? Gardons μ=10\mu = 10 et faisons varier λ\lambda :

Arrivées λ\lambda (requêtes/s)Charge ρ\rhoTemps moyen de séjour Wˉ=1/(μ−λ)\bar W = 1/(\mu - \lambda)
550 %0,2 s
880 %0,5 s
990 %1 s
9,595 %2 s
9,999 %10 s

Le temps de séjour se comporte comme 1/(1−ρ)1/(1 - \rho) : il diverge quand la charge s’approche de 100 %. Passer de 80 % à 95 % de charge, soit moins de 20 % de trafic en plus, multiplie le temps de réponse par 4. C’est pour cette raison que les équipes d’exploitation visent des taux d’utilisation de l’ordre de 60 à 70 % sur les services sensibles à la latence : la marge restante n’est pas du gaspillage, c’est ce qui absorbe les fluctuations aléatoires des arrivées.

0 %20 %40 %60 %80 %100 %charge ρ du serveur0246810temps moyen de séjour (s)temps de service seul : 0,1 s50 % : 0,2 s80 % : 0,5 s90 % : 1 s95 % : 2 s99 % : 10 szone viséepar les équipesW̄ = 1/(μ − λ), μ = 10
Le temps de séjour 1/(μ − λ) reste modeste tant que la charge est raisonnable, puis explose à l'approche de 100 % : les points du tableau montrent qu'entre 80 % et 99 % de charge, il est multiplié par 20.

La simulation d’une file FIFO à un serveur tient en quelques lignes : chaque requête commence son service à son arrivée, ou à la fin du service précédent si le serveur est occupé.

import numpy as np
rng = np.random.default_rng(0)
lam, mu, n = 8.0, 10.0, 200_000
arrivees = np.cumsum(rng.exponential(1 / lam, n)) # instants d'arrivée
services = rng.exponential(1 / mu, n) # durées de service
debut = np.empty(n)
fin = np.empty(n)
fin_precedente = 0.0
for i in range(n):
debut[i] = max(arrivees[i], fin_precedente) # attendre que le serveur se libère
fin[i] = debut[i] + services[i]
fin_precedente = fin[i]
sejour = fin - arrivees
print(sejour.mean()) # proche de 0.5 = 1/(mu - lam)
print((debut - arrivees).mean()) # attente, proche de 0.4
print((sejour > 1).mean()) # proche de exp(-2) ≈ 0.135

Avec ces paramètres, on obtient bien un séjour moyen voisin de 0,5 s et une attente moyenne voisine de 0,4 s. Ce petit simulateur vous permet ensuite de tester ce que les formules ne couvrent pas : un service de durée constante, des arrivées en rafales, une discipline LIFO.

  • Une file d’attente se décrit par ses arrivées, son service, son nombre de serveurs, sa capacité et sa discipline (FIFO par défaut, LIFO, RR, PS), avec d’éventuelles priorités préemptives ou non.
  • Kendall : FT/FS/s/NF_T/F_S/s/N, avec M (exponentielle), D (déterministe), EkE_k (Erlang), HkH_k (hyper-exponentielle), G et GI (générales).
  • La M/M/1 est un processus de naissance et de mort de taux constants ; intensité de trafic ρ=λ/μ\rho = \lambda/\mu, stable si et seulement si ρ<1\rho < 1.
  • Régime stationnaire géométrique : Πn=(1−ρ)ρn\Pi_n = (1-\rho)\rho^n ; Nˉ=ρ/(1−ρ)\bar N = \rho/(1-\rho) ; Wˉ=1/(μ−λ)\bar W = 1/(\mu - \lambda) ; Wˉq=ρ/(μ−λ)\bar W_q = \rho/(\mu - \lambda).
  • Loi de Little : Nˉ=λWˉ\bar N = \lambda \bar W, valable pour tout système stable, et même pour une partie du système.
  • Le temps de séjour diverge en 1/(1−ρ)1/(1-\rho) près de la saturation : il faut garder de la marge.

Exercice · Lire la notation de Kendall

facile

Décrivez en une phrase chacune des files suivantes : M/D/1, M/M/3/10, M/M/∞, E2E_2/M/1.

Voir la solution
  • M/D/1 : arrivées de Poisson, durée de service constante, un seul serveur, capacité infinie (par exemple, une machine qui emballe chaque colis en un temps fixe).
  • M/M/3/10 : arrivées de Poisson, services exponentiels, trois serveurs, au plus 10 clients présents en tout (3 en service et 7 en attente) ; un client qui arrive quand le système est plein est perdu.
  • M/M/∞ : arrivées de Poisson, services exponentiels, une infinité de serveurs : chaque client est servi dès son arrivée, sans jamais attendre.
  • E2E_2/M/1 : les durées entre arrivées sont des sommes de deux phases exponentielles (arrivées plus régulières que Poisson), service exponentiel, un serveur.

Exercice · L'imprimante du laboratoire

moyen

Une imprimante partagée reçoit en moyenne 12 impressions par heure, selon un processus de Poisson. Une impression dure en moyenne 4 minutes, selon une loi exponentielle.

  1. Quelle est la charge de l’imprimante ? La file est-elle stable ?
  2. Combien d’impressions sont présentes en moyenne ? Combien de temps attend-on en moyenne sa feuille, depuis l’envoi jusqu’à la fin de l’impression ?
  3. Quelle est la probabilité de trouver au moins 4 impressions devant soi en envoyant la sienne ?
Voir la solution
  1. λ=12\lambda = 12 par heure, μ=60/4=15\mu = 60/4 = 15 par heure, donc ρ=0,8<1\rho = 0{,}8 < 1 : la file est stable.
  2. Nˉ=0,8/0,2=4\bar N = 0{,}8/0{,}2 = 4 impressions. Par Little, Wˉ=Nˉ/λ=4/12\bar W = \bar N/\lambda = 4/12 h =20= 20 minutes (on retrouve 1/(μ−λ)=1/31/(\mu - \lambda) = 1/3 h). Sur ces 20 minutes, 16 sont de l’attente et 4 d’impression.
  3. Les arrivées de Poisson voient le système dans son régime stationnaire (c’est une propriété des arrivées de Poisson). La probabilité de trouver au moins 4 impressions vaut ρ4=0,84≈0,41\rho^4 = 0{,}8^4 \approx 0{,}41.

En code

lam = 12 # impressions par heure
mu = 60 / 4 # une impression dure 4 minutes
rho = lam / mu
N = rho / (1 - rho) # nombre moyen présent
W = N / lam # loi de Little (en heures)
print(rho, N, W * 60, (W - 1 / mu) * 60) # 0.8 4.0 20.0 16.0 (minutes)
print(rho ** 4) # 0.4096 : au moins 4 impressions devant soi

Exercice · Dimensionner pour une latence cible

difficile

L’API reçoit λ=8\lambda = 8 requêtes par seconde. On peut choisir une machine plus puissante, de taux de service μ\mu plus élevé.

  1. Quel μ\mu minimal garantit un temps de séjour moyen inférieur à 0,2 s ?
  2. L’équipe produit préfère un objectif en pourcentage : moins de 1 % des requêtes doivent attendre leur réponse plus d’une seconde. Quel μ\mu minimal faut-il ?
  3. Avec ce dernier μ\mu, quelle est la charge du serveur ? Commentez.
Voir la solution
  1. Wˉ=1/(μ−8)≤0,2\bar W = 1/(\mu - 8) \leq 0{,}2 équivaut à μ−8≥5\mu - 8 \geq 5, soit μ≥13\mu \geq 13 requêtes par seconde.
  2. Le temps de séjour est exponentiel de taux μ−λ\mu - \lambda, donc P(W>1)=e−(μ−8)P(W > 1) = e^{-(\mu - 8)}. On veut e−(μ−8)≤0,01e^{-(\mu - 8)} \leq 0{,}01, soit μ−8≥ln⁡100≈4,61\mu - 8 \geq \ln 100 \approx 4{,}61, donc μ≥12,61\mu \geq 12{,}61 requêtes par seconde environ.
  3. La charge vaut alors ρ=8/12,61≈0,63\rho = 8/12{,}61 \approx 0{,}63. Pour tenir un objectif de latence exigeant, il faut accepter que le serveur soit inoccupé plus d’un tiers du temps : c’est le prix de l’aléa des arrivées.

En code

import numpy as np
lam = 8
mu_moyenne = lam + 1 / 0.2 # 1/(mu - lam) <= 0.2
mu_quantile = lam + np.log(100) # exp(-(mu - lam)) <= 0.01
print(mu_moyenne, mu_quantile) # 13.0 12.605
print(lam / mu_quantile) # 0.635 : charge du serveur
print(np.exp(-(mu_quantile - lam))) # 0.01 : contrôle