Le principe d'Exponential Backoff
La majorité des applications modernes sont amenées à communiquer avec des systèmes tiers, tels que des services Web, des bases de données, des systèmes de messageries ou de file d’attente, etc. Ces derniers peuvent rencontrer des pannes temporaires liées à des facteurs externes, perte de connexion réseau, pic d’activité ou panne entrainant une interruption temporaire de service. Un des moyens possibles permettant d’améliorer la résilience de nos projets et d’appliquer le principe d’exponential backoff.
L’exponential backoff est un algorithme qui est utilisé pour gérer les tentatives de reconnexion ou de réexécution d’une opération après un échec. Le principe consiste à effectuer plusieurs tentatives d’une opération en appliquant un temps d’attente entre chaque nouvelle tentative. Ce temps d’attente sera de plus en plus important après chacun des essais infructueux. Cette approche permet notamment de réduire la pression sur le système en défaillance et d’augmenter les chances de succès lors de défaillances temporaires.
Le principe de fonctionnement de base est très simple à mettre en place, puisqu’après chaque échec, on attend un délai qui va augmenter selon une fonction exponentielle avant de retenter l’opération. Cela peut se traduire par le code suivant: $delay = $initialDelay * ($factor ^ $attempt)
. Le délai augmentera ainsi jusqu’au nombre maximal de tentative que vous souhaitez effectuer. Il peut également être intéressant de définir un nombre maximal de tentatives autorisées.
Les algorithmes d’exponential backoff permettent généralement de définir un paramètre nommé jitter
. Il s’agit d’un élément aléatoire qui va permettre de faire varier le délai calculé afin d’éviter que plusieurs tentatives d’opérations simultanées n’aient lieu au même moment dans le cas de traitements effectués en parallèle. Incluant ce paramètre, la formule de calcul du délai d’attente pourrait être la suivante: $delayToApply = $delay * 1 + (mt_rand(-$jitter, $jitter))
.
Une implémentation complète en PHP pourrait se traduire ainsi:
function executeExponentialBackoff(
callable $fn,
int $maximumAttempt = 5,
int $initialDelay = 100,
float $factor = 2,
float $jitter = 0.25,
int $maximumDelay = 30_000
) {
$attempts = 0;
while ($attempts < $maximumAttempt) {
try {
return $fn();
} catch (Exception $e) {
$attempts++;
if ($attempts == $maximumAttempt) {
throw new Exception("Failed after {$maximumAttempt} attempts: " . $e->getMessage());
}
$delay = min($initialDelay * pow($factor, $attempts - 1), $maximumDelay);
$delayWithJitter = $delay * (1 + mt_rand(-$jitter * 100, $jitter * 100) / 100);
$delayToApply = (int)($delayWithJitter * 1000);
echo "Attempt {$attempts} failed. New attempt in " . round($delayWithJitter / 1000, 2) . "s\n";
usleep($delayToApply);
}
}
}
Afin de pouvoir mettre en place ce mécanisme dans les meilleures conditions, il sera essentiel de veiller à ce que les opérations effectuées soient idempotentes et qu’elles puissent être exécutées plusieurs fois tout en ayant le même résultat à chaque appel. Il faudra également veiller à bien configurer les paramètres qui vont incluer sur les temps d’attentes.
L’exponential backoff est une technique à la fois simple et efficace. Elle est aujourd’hui mise en place dans de nombreux projets et est implémentée dans la plupart des frameworks. On retrouve par exemple ce principe dans Symfony ou encore Laravel. Mais il existe également des bibliothèques indépendantes, tel que eventsauce/backoff
.