Dans cet article de blog, j’explorerai le modèle Scatter-Gather, une conception d’évolutivité du cloud qui peut traiter de grandes quantités de données et effectuer des calculs complexes et chronophages. Le modèle fonctionne comme un guide pour créer des systèmes distribués afin d’atteindre le parallélisme. L’approche peut réduire considérablement les délais de traitement d’une application en ligne pour répondre à autant d’utilisateurs. L’idée est de décomposer une opération en tâches indépendantes pour obtenir un temps de réponse approximativement constant.
Qu’est-ce que le motif Scatter-Gather ?
Prenons un exemple pour comprendre le problème où le motif peut être efficace. Supposons qu’une application s’exécute sur un processeur monocœur. Il peut traiter une demande entrante en 64 secondes pour produire un résultat. Si nous migrons la même application vers un processeur à 16 cœurs, il peut générer la même sortie en environ quatre secondes. Le processeur multicœur générera seize threads pour travailler en parallèle pour calculer le résultat, avec quelques microsecondes supplémentaires pour gérer plusieurs threads de traitement. Un temps de réponse de quatre secondes est bon mais toujours considéré comme un slugging pour une application Web. La mise à niveau supplémentaire du processeur, la mise à l’échelle verticale, atténuera le problème, ne le résoudra pas.
Le modèle Scatter-Gather peut résoudre ce problème. La structure du Scatter-Gather ressemble à un arbre, le contrôleur d’application jouant le rôle de racine. Lorsque le contrôleur reçoit une requête entrante, il la divise en plusieurs tâches indépendantes et les affecte aux nœuds feuilles disponibles, la phase de « dispersion ». Les nœuds feuilles sont plusieurs machines ou conteneurs disponibles sur un réseau distribué. Chaque feuille de processeur travaille indépendamment et en parallèle sur son calcul, produisant une partie de la réponse. Une fois qu’ils ont terminé leur tâche, chaque feuille renvoie sa réponse partielle à la racine, qui les collecte toutes dans le cadre de la phase de « collecte ». Le contrôleur racine combine ensuite les réponses partielles et renvoie le résultat final au client. Le schéma ci-dessous illustre le motif.
La stratégie nous permet d’exploiter l’infrastructure cloud pour allouer autant de machines virtuelles que nécessaire et évoluer horizontalement. L’application peut répartir la charge dynamiquement pour atteindre le temps de réponse souhaité. Si un nœud feuille particulier a un temps de réponse élevé (par exemple, en raison de voisins bruyants), le contrôleur racine peut également redistribuer la charge pour une réponse plus rapide. Examinons un cas d’utilisation dans le passage ci-dessous.
Cas d’utilisation
Maintenant que nous comprenons le modèle, explorons un cas d’utilisation pratique où il peut démontrer une efficacité remarquable malgré son apparente simplicité. Imaginez un site Web d’une compagnie ferroviaire qui vend des billets à ses clients. Supposons qu’un client planifie un week-end au départ de Londres et souhaite optimiser ses dépenses en identifiant la distance qu’il peut parcourir avec le même budget. Il soumet une requête au site Web pour répertorier tous les trains directs au départ de Londres dans tout le pays.
Une approche pour gérer cette tâche consiste à envoyer des requêtes individuelles à chaque opérateur ferroviaire au Royaume-Uni, à collecter les résultats, puis à les présenter à l’utilisateur. Cependant, cela ne sera pas pratique, car l’application émet une requête à la fois. Pour améliorer l’efficacité de cette solution, nous pouvons diviser le Royaume-Uni en cinq régions : Est, Nord-Ouest et Centre, Pays de Galles et Ouest, Écosse et Sud. Lorsqu’une requête arrive, le contrôleur racine la divise et l’attribue aux cinq nœuds feuilles pour qu’ils la traitent en parallèle. Chaque nœud feuille se concentre sur une région spécifique, ce qui réduit la nécessité de rechercher dans tout le pays. Chaque nœud feuille renvoie une liste de tous les trains directs de Londres à cette région. Enfin, le contrôleur racine fusionne tous les résultats pour créer une liste finale à afficher au client.
Détails d’implémentation
La section précédente a démontré un cas d’utilisation pratique pour améliorer l’évolutivité d’une application Web. Cependant, il est essentiel de considérer les points suivants avant d’adopter cette tactique :
1. Gestion des nœuds feuilles non réactifs
Même si les applications utilisent un environnement cloud, il est toujours possible que les machines deviennent indisponibles en raison de problèmes de réseau ou d’infrastructure. De plus, les nœuds feuilles peuvent communiquer avec des API externes (comme nous l’avons vu précédemment dans l’exemple de cas d’utilisation) qui peuvent ne pas répondre rapidement. Pour atténuer ces problèmes, le contrôleur racine doit définir une limite supérieure pour le temps de réponse ou la durée d’attente d’une réponse. Si un nœud feuille ne répond pas dans ce délai, le contrôleur racine peut l’ignorer et agréger les résultats qu’il a déjà recueillis pour formuler une réponse pour l’utilisateur. Cette approche peut minimiser l’impact sur l’utilisateur en sacrifiant une petite partie du résultat.
2. Découplage du contrôleur racine du traitement des nœuds feuilles
Il est crucial d’éviter de coupler le contrôleur racine avec les nœuds feuilles pour améliorer l’architecture. Un couplage étroit entre eux signifierait que le contrôleur racine suit en permanence les nœuds feuilles de travail, entraînant une surcharge inutile sur l’application. Au lieu de cela, nous pouvons utiliser un courtier de messages. Un style de communication pub-sub peut créer un contrôleur racine et des nœuds feuilles faiblement couplés. Chaque fois qu’il y a une demande entrante, le contrôleur racine diffuse des messages aux nœuds feuilles de travail potentiels. Tous les nœuds de travail peuvent alors s’abonner au message entrant, le traiter et publier leurs résultats dans une file d’attente distincte. Le contrôleur racine peut consommer les résultats de cette file d’attente, les agréger et répondre à l’utilisateur. La figure ci-dessous illustre la mise en œuvre.
3. Traitement des demandes asynchrones
La mise en œuvre du modèle discutée jusqu’à présent fonctionne mieux pour les réponses immédiates, allant de quelques millisecondes à une seconde. Cependant, cette implémentation ne fonctionnerait pas pour les tâches gourmandes en calculs, qui peuvent prendre des minutes à des heures. Certains exemples de ces tâches de calcul incluent des applications gourmandes en données telles que les simulations scientifiques, l’analyse de données ou l’apprentissage automatique. Pour aborder efficacement ces scénarios, il est essentiel de diviser le contrôleur racine en deux composants distincts : un répartiteur et un processeur. Cette division nous permettrait de traiter les demandes de manière asynchrone. Vous trouverez ci-dessous les étapes d’une telle communication asynchrone.
- Lorsqu’une demande arrive, le répartiteur accepte la demande et génère un identifiant unique. Le répartiteur répond à l’utilisateur avec une estimation du temps nécessaire pour terminer la tâche et fournit l’identifiant unique généré précédemment pour suivre la demande.
- La demande est ensuite publiée sur les nœuds feuilles.
- Les nœuds feuilles traitent la demande et envoient leurs résultats à une file d’attente souscrite par le processeur.
- Le service de processeur combine les résultats reçus des feuilles de processeur individuelles et les conserve dans une base de données.
- Une fois le temps estimé écoulé, l’utilisateur peut fournir l’identifiant unique pour récupérer le résultat. Si le résultat n’est pas disponible dans le délai estimé, le processeur peut soit demander à l’utilisateur de vérifier plus tard, soit fournir un résultat partiel.
La figure montre une implémentation asynchrone étape par étape :
Considérations importantes : nombre de nœuds feuilles
Après avoir vu l’implémentation dans la section précédente, la prochaine étape cruciale consiste à choisir le nombre optimal de nœuds feuilles. Le modèle semble suggérer que l’ajout de processeurs feuille et l’augmentation de la parallélisation peuvent améliorer l’efficacité, mais cela se fait au détriment de la gestion des frais généraux.
- Lorsqu’une requête arrive, le contrôleur racine doit la diviser, l’envoyer à l’infrastructure de distribution, etc. Bien que le coût du fractionnement, de la lecture et de l’écriture à partir du réseau soit négligeable au début par rapport à la logique de calcul, ce coût augmente à mesure que nous redimensionnons les nœuds feuilles. Si la parallélisation se poursuit, la surcharge peut dépasser le coût de calcul.
- La considération suivante est que le contrôleur racine attend le résultat de tous les nœuds feuilles, ce qui signifie que le temps de réponse global dépend du nœud le plus lent. Il est communément connu sous le nom de problème de « retardataire », et il peut retarder considérablement le temps d’achèvement du système en raison d’un seul nœud lent. Pour atténuer le problème du retardataire, des techniques établies telles que l’exécution spéculative et le partitionnement dynamique peuvent répartir la charge de manière uniforme.
- Le nombre le plus favorable de nœuds feuilles dépend également de la disponibilité et de la précision du service. Supposons qu’un service souhaite atteindre une disponibilité de 97 % sans sacrifier le résultat, et que l’environnement du fournisseur de cloud fournit un nœud feuille avec une disponibilité de 99 %. Dans ce cas, choisir Scatter-Gather semble être le bon choix. Cependant, un calcul minutieux révèle que le service peut avoir au plus trois congés de traitement. L’ajout d’une quatrième feuille de traitement peut entraîner une violation du SLA, en raison de la probabilité de défaillance du nœud (0,99 × 0,99 × 0,99 × 0,99 == 0,96). C’est encore pire si nous ajoutons plus de feuilles.
Par conséquent, le choix du nombre de nœuds feuilles est essentiel pour concevoir une implémentation Scatter-Gather réussie.
Résumé
Le modèle Scatter-Gather est un modèle de calcul distribué. Le modèle présente plusieurs avantages, tels que des performances améliorées, une tolérance aux pannes accrue et une meilleure évolutivité. Cependant, le choix du nombre optimal de nœuds feuilles de traitement est essentiel pour atteindre des performances élevées. Dans l’ensemble, le modèle Scatter-Gather est un outil puissant pour développer des applications distribuées hautes performances.