La CRAFT Conference de Budapest 2017

  • posté le
  • par ESENS


                               « Resilient software design in theory & practice »

   

.

Avec la journée pour la présentation, l’intervenant nous a prévenus que le temps était trop court pour approfondir le sujet, 48h de plus auraient été nécessaires…

Uwe Friedrichsen, puisque c’est son nom, nous a donc proposé une introduction à la problématique de la résilience avec, à l’appui, présentation et classement des patterns à disposition - du moins une partie d’entre eux - exercice applicatif en groupe et surtout une bonne dose de bon sens.

Dans ce qui suit, je vais m’efforcer de rapporter les idées directrices de la présentation d’Uwe Friddrichsen.

Pour commencer, qu'est-ce que la résilience?

                Au sens commun, il s'agit de la capacité d'un système, d'un organisme ou même d'un être vivant à se remettre d'un évènement perturbateur pour retrouver un état stable, que cet état soit ou non son état initial d'avant perturbation.

Uwe Friedrichsen avance la formule suivante pour traduire l'importance pour un système en informatique à se remettre le plus rapidement possible :

   

                                                   avec MTTF pour Mean Time To Failure et MTTR pour Mean Time To Recovery.

La résilience agit donc directement sur la disponibilité de votre système, qualité primordiale une fois que vous l'avez déployé en environnement de production car elle touche directement à votre activité économique. 

Je viens ici d'enfoncer des portes ouvertes mais cela conditionne l'état d'esprit avec lequel aborder la problématique de la résilience.

Mathématiquement, il y a alors 2 façons d'assurer une haute disponibilité de votre système :

                          Augmenter le MTTF

                         Abaisser le MTTR

                La première option revient à jouer sur vos infrastructures : essayer de minimiser les erreurs de code, mettre en place des clusters, des connexions multiples au réseau, etc. Elle conviendrait si votre système était parfaitement isolé. Mais elle ne suffit pas pour des systèmes distribués, ce vers quoi tendent de plus en plus les architectures actuelles avec les tendances du cloud, des microservices, des applications mobiles et de l'IOT.

Cette citation de Lesliel Lamport reprise par Uwe Friedrichsen décrit parfaitement le "problème" des systèmes distribués :

                         A distributed system is one in which the failure

                         of a computer you didn't even know existed

                         can render your own computer unusable

                Il reste alors à abaisser le MTTR : accepter que des défaillances vont se produire parce qu'il est impossible de toutes les prévenir et faire le mieux pour s'en remettre.

La résilience devient alors la capacité d'un système à traiter les situations inattendues, au mieux sans que l'usager du système ne s'en rende compte, au pire en proposant une dégradation acceptable du service rendu.

On commence à voir que la résilience est aussi une problématique métier et pas qu'une  question technique. Mais cela sera développé plus loin.


Techniquement, comment créer  un système résilient?

Il existe des familles de patterns, de solutions qui ont chacune un objectif différent quand aux défaillances :

  • détecter
  • réparer,
  • atténuer (l'usager s'est rendu compte de la défaillance et le service proposé est dégradé)
  • traiter.

La détection recouvre par exemple la mise en place d'un service de monitoring à l'échelle du système, la détection des latences (timeOuts) via les outils de mesure et le code ou encore l'usage de circuit-breaker comme  Hystrix.

Pour réparer il est possible de relancer un traitement, un appel (de façon mesurée cependant), d'effectuer des rollback ou, au contraire, des roll-forward si l'activité concernée n'est pas essentielle ou encore prévoir un système basculement (souvent implémenté comme une combinaison monitoring-routeur dynamique) mais qui nécessite des ressources supplémentaires.

L'atténuation peut consister en cas de forte charge à mettre en place des queues de traitement (voir même à décider d'ignorer les premiers ou les derniers entrants), à prévoir un scénario alternatif, à mettre en place un système de partage de charge.

Enfin,  pour traiter des défaillances il faut se diriger vers des déploiements à chaud et des releases de petite taille.

Ces descriptions sommaires visent à expliciter les différents types de patterns. Elles ne sont pas exhaustives et j'ai laissé de côté par exemple l'injection d'erreurs pratiquée par Netflix qui est un autre pattern complémentaire.

                Si les patterns aident, il ne faut pas oublier que les défaillances sont intimement liées à la façon dont le système a été pensé

Si on se place dans le cadre des microservices, la question du découpage est cruciale. Le DomainDrivenDesign (DDD) est une bonne ligne de conduite, qui peut permettre un bon niveau d'isolation. Mais souvent ce DDD dérive en "EntityDrivenDesign" avec une architecture multi-couche où pour chaque entité on retrouve un DAO et un service.  

Dans la même veine, la réutilisation du code qui est une des bonnes pratiques préférée des développeurs peut pousser à des découpages non nécessaires qui fragilisent davantage les sous-systèmes concernés.

Le  paradigme de communication adopté est lui aussi essentiel : type requête-réponse (Rest), orienté événements ou via messages. Une architecture orientée évènements conduira davantage à des microservices communiquant "à la queue le leu", chaque microservice dépendant fortement de celuiiqui le précède dans la chaine. Une architecture type "Rest" conduit à terme à une architecture en couches horizontales de microservices,  plusieurs microservices communiquant avec plusieurs autres.

Uwe Friddrichsen suggère un parcours possible et répétable pour construire un système résilient:

  • Commencer par un design soigné et qui permet une isolation maximum, un faible couplage des sous-systèmes,
  • Ensuite une sélection de pattern de détection, réparation et atténuation sur la base de scénarios d'échec,
  • Eventuellement rajouter des patterns de prévention,
  • Déployer,
  • Mesurer,
  • Tirer des enseignements
  • Evaluer la pertinence des patterns adoptés (coûts vs avantages).

Si la mesure est essentielle pour connaitre le système et la façon dont il répond aux défaillances, elle permet aussi de convaincre la maîtrise d'ouvrage d'investir des ressources dans la résilience c'est-à dire dans l'anticipation de défaillances imprévisibles. Il ne s'agit de savoir si une défaillance va se produire, car souvent on pense que ce ne sera pas le cas, mais de savoir comment réagir lorsqu'elle se manifestera. C'est donc une décision métier.


Ainsi, comment décider si une défaillance doit être traitée avec un rollback ou un roll-forward. Que faire si une transaction ne se termine pas comme prévu? Faut-il revenir en arrière ou choisir d'enregistrer des données incomplètes et, éventuellement, ensuite, faire passer un batch de rattrapage?

Ou encore, dans le cas d'une solution d'e-commerce : que faire si votre application fait un appel à un service externe comme Paypal pour valider un paiement d'un de vos clients et qu'au bout de10s Paypal n'a toujours pas répondu? Est-ce un timeOut ou un faux positif? Faut-il laisser le client patienter? Ce n'est pas au développeur de décider. Il peut tout au plus aider  la maitrise d'ouvrage à détecter d'éventuels risques.

Ce sera donc la conclusion de cet article : construire un système résilient c'est accepter que des défaillances vont se produire et s'y préparer mais c'est aussi une décision d'ordre métier qui se fera sur la base d'arguments économiques.

Retrouvez tous nos articles sur le Blog ESENS !

Vous êtes à la recherche d'un nouveau challenge ? Rejoignez l'équipe ESENS en postulant à nos offres d'emploi !

PARTAGER CET ARTICLE