Robustness in Complex Systems: résumé d'article académique

Photo de Vladimir Kudinov sur Unsplash

Aujourd’hui, nous allons examiner le document intitulé «Robustness in Complex Systems» publié en 2001 par Steven D. Gribble. Toutes les citations et les chiffres sont extraits du document.

Cet article soutient qu'un paradigme de conception commun pour les systèmes est fondamentalement défectueux, ce qui entraîne un comportement instable et imprévisible à mesure que la complexité du système augmente.

Le «paradigme de conception commun» fait référence à la pratique consistant à prévoir l'environnement dans lequel le système fonctionnera et ses modes de défaillance. Le document indique qu’un système traitera des conditions imprévues au fur et à mesure de sa complexité, il doit donc être conçu pour faire face aux défaillances avec élégance. Le document explore ces idées à l'aide de «structures de données distribuées (DDD), un serveur de stockage évolutif basé sur des grappes».

De par leur nature, les grands systèmes fonctionnent grâce à l'interaction complexe de nombreux composants. Cette interaction conduit à un couplage omniprésent des éléments du système; ce couplage peut être fort (par exemple, des paquets envoyés entre des routeurs adjacents dans un réseau) ou subtil (par exemple, une synchronisation d'annonces de routage sur un réseau étendu).

Une caractéristique commune à ces systèmes de grande taille est ce que l’on appelle l’effet papillon. Cela fait référence à une petite perturbation inattendue dans le système résultant de l'interaction complexe de divers composants qui provoque un changement généralisé.

Un objectif commun à la conception de système est la robustesse: la capacité d’un système à fonctionner correctement dans diverses conditions et à échouer de manière élégante dans une situation inattendue. Le document conteste le schéma habituel consistant à essayer de prédire un certain ensemble de conditions de fonctionnement du système et à le concevoir de manière à fonctionner correctement dans ces conditions uniquement.

Il est également impossible de prévoir toutes les perturbations qu'un système subira à la suite de modifications des conditions environnementales, telles que des défaillances matérielles, des surcoûts ou l’introduction de logiciels non conformes. Compte tenu de cela, nous pensons que tout système qui tente de gagner en robustesse uniquement par la précognition est sujet à la fragilité.

DDS: une étude de cas

L’hypothèse énoncée ci-dessus est explorée à l’aide d’un système de stockage évolutif basé sur un cluster, Distributed Data Structures (DDD) - «une table de hachage virtuelle à grande capacité et haut débit qui est partitionnée et répliquée sur de nombreux nœuds de stockage individuels, appelés briques».

Ce système a été construit en utilisant une philosophie de conception prédictive comme celle décrite ci-dessus.

Forts d'une vaste expérience de tels systèmes, nous avons tenté de raisonner sur le comportement des composants logiciels, des algorithmes, des protocoles et des éléments matériels du système, ainsi que sur les charges de travail qu'il recevrait.

Lorsque le système fonctionnait dans le cadre des hypothèses formulées par les concepteurs, cela fonctionnait bien. Ils ont été en mesure de faire évoluer et d'améliorer les performances. Toutefois, dans le cas où une ou plusieurs des hypothèses concernant les conditions de fonctionnement ont été violées, le système s'est comporté de manière inattendue, entraînant une perte de données ou des incohérences.

Ensuite, nous parlons de plusieurs de ces anomalies.

Récupération des ordures et synchronisation synchronisée

Les concepteurs du système ont utilisé des délais d'attente pour détecter les défaillances de composants du système. Si un composant particulier ne répond pas dans le délai spécifié, il est considéré comme mort. Ils ont assumé la synchronisation synchronisée dans le système.

Le DDS a été implémenté en Java et a donc utilisé la récupération de place. Le ramasse-miettes de notre machine virtuelle Java était un collecteur à balises; Par conséquent, plus le nombre d'objets actifs résidant dans le segment de mémoire de la machine virtuelle Java étant important, la durée d'exécution du récupérateur de place afin de récupérer une quantité fixe de mémoire augmenterait.

Lorsque le système était saturé, même de légères variations de charge sur les briques augmenteraient le temps pris par le ramasse-miettes, ce qui réduirait le débit de la brique. Ceci est appelé thrashing GC. Les briques touchées seraient en retard par rapport à leurs homologues, ce qui entraînerait une dégradation supplémentaire des performances du système.

Par conséquent, le ramassage des ordures a violé l’hypothèse de synchronisme borné quand il s’approchait ou dépassait le point de saturation.

Fuites lentes et échecs liés

Une autre hypothèse faite lors de la conception du système était que les défaillances étaient indépendantes. DDS a utilisé la réplication pour rendre le système tolérant aux pannes. La probabilité d'échec simultané de plusieurs réplicas était très faible.

Cependant, cette hypothèse a été violée quand ils ont rencontré une condition de concurrence critique dans leur code qui a provoqué une fuite de mémoire sans affecter l'exactitude.

Chaque fois que nous lançons notre système, nous avons tendance à lancer toutes les briques en même temps. Avec une charge approximativement équilibrée dans le système, toutes les briques manqueraient donc presque à peu près au même moment, plusieurs jours après leur lancement. Nous avons également émis l'hypothèse que nos mécanismes de basculement automatique aggraveraient cette situation en augmentant la charge sur un réplica après l'échec d'un homologue, ce qui augmente le taux de perte de mémoire du réplica.

Étant donné que toutes les répliques ont été soumises à une charge uniforme sans tenir compte de la dégradation des performances ni d'autres problèmes, cela a créé un couplage entre les répliques et…

… Lorsque combiné à une fuite de mémoire lente, nous avons violé notre hypothèse de pannes indépendantes, ce qui a entraîné une indisponibilité et une perte partielle des données sur notre système

Dépendances non contrôlées et échec-stop

Partant de l'hypothèse que si un composant a échoué, les concepteurs ont également supposé des échecs «échec-arrêt», c'est-à-dire qu'un composant qui a échoué ne fonctionnera plus après un certain temps. Les briques du système effectuaient tous les travaux à latence longue (E / S de disque) de manière asynchrone.

Cependant, ils n'ont pas remarqué que certaines parties de leur code utilisaient des appels de fonction bloquants. Cela a eu pour effet que le fil principal de gestion des événements a été emprunté de manière aléatoire, ce qui a entraîné une saisie inexplicable des briques pendant quelques minutes et la reprise du post.

Bien que cette erreur soit due à notre propre incapacité à vérifier le comportement du code que nous utilisions, elle sert à démontrer que l’interaction de bas niveau entre des composants construits indépendamment peut avoir de profondes implications sur le comportement général du système. Un changement de comportement très subtil a entraîné la violation de notre hypothèse d'échec absolu sur l'ensemble du cluster, ce qui a finalement entraîné la corruption des données dans notre système.

Vers des systèmes robustes

..petits changements dans un système couplé complexe peuvent entraîner des changements de comportement importants et inattendus, entraînant éventuellement le système en dehors du régime de fonctionnement prévu par ses concepteurs.

Quelques solutions qui peuvent nous aider à créer des systèmes plus robustes:

Surapprovisionnement systématique

À l'approche du point de saturation, les systèmes ont tendance à devenir fragiles lorsqu'ils tentent de s'adapter à un comportement inattendu. Une façon de lutter contre cela est de sur-alimenter délibérément le système.

Cependant, cela a son propre ensemble de problèmes: cela conduit à la sous-utilisation des ressources. Cela nécessite également de prévoir l'environnement d'exploitation attendu et donc le point de saturation du système. Cela ne peut pas être fait de manière précise dans la plupart des cas.

Utiliser le contrôle d'admission

Une autre technique consiste à rejeter la charge dès que le système commence à approcher du point de saturation. Cependant, cela nécessite de prévoir le point de saturation, ce qui n’est pas toujours possible, en particulier avec les grands systèmes qui ont beaucoup de variables.

Le rejet de demandes consomme également certaines ressources du système. Les services conçus avec le contrôle d’admission en tête ont généralement deux modes de fonctionnement: normal où les demandes sont traitées et un mode extrêmement léger où elles sont rejetées.

Intégrer l'introspection dans le système

Un système introspectif est un système dans lequel la capacité de surveiller le système est conçue dès le début.

Lorsqu'un système peut être surveillé et que les concepteurs et les opérateurs peuvent obtenir des mesures significatives de son fonctionnement, il est beaucoup plus robuste qu'un système à boîte noire. C’est plus facile d’adapter un tel système à son environnement, de le gérer et de le maintenir.

Introduisez l'adaptivité en fermant la boucle de contrôle

Les concepteurs et les opérateurs humains adaptent la conception en réponse à une modification de son environnement d’exploitation indiquée par diverses mesures. Cependant, la chronologie d’une telle boucle de contrôle n’est pas très prévisible. Les auteurs soutiennent que les systèmes devraient être construits avec des boucles de contrôle internes.

Ces systèmes intègrent les résultats de l'introspection et tentent d'adapter les variables de contrôle de manière dynamique pour que le système continue à fonctionner dans un régime stable ou performant.
Tous ces systèmes ont la propriété que le composant effectuant l'adaptation est capable d'émettre une hypothèse assez précise sur les effets de l'adaptation; sans cette capacité, le système fonctionnerait «dans le noir» et deviendrait probablement imprévisible. Une nouvelle approche intéressante pour émettre des hypothèses sur les effets de l’adaptation consiste à utiliser l’apprentissage statistique; Compte tenu de cela, un système peut expérimenter des modifications afin de créer un modèle de leurs effets.

Planifier en cas d'échec

Les systèmes complexes doivent s'attendre à une défaillance et planifier en conséquence.

Quelques techniques pour le faire:

  1. découplage des composants pour contenir localement les pannes
  2. minimiser les dommages en utilisant des abstractions robustes telles que des transactions
  3. minimiser le temps passé en état d'échec (utilisation du point de contrôle pour une récupération rapide)

Dans cet article, les auteurs soutiennent que la conception de systèmes en supposant les contraintes et la nature de leur fonctionnement, leurs défaillances et leur comportement aboutit souvent à des systèmes fragiles et imprévisibles. Nous avons besoin d'une approche radicalement différente pour construire des systèmes plus robustes face aux échecs.

Ce paradigme de conception différent est celui dans lequel les systèmes ont la meilleure chance possible d'obtenir un comportement stable (par le biais de techniques telles que le surapprovisionnement, le contrôle d'admission et l'introspection), ainsi que la capacité de s'adapter à des situations imprévues (en traitant l'introspection comme un retour d'information). en boucle de contrôle fermée). En fin de compte, les systèmes doivent être conçus pour gérer les pannes avec élégance, la complexité semblant conduire à une imprévisibilité inévitable.