7 points par GN⁺ 2024-09-03 | 1 commentaires | Partager sur WhatsApp
  • La difficulté essentielle des systèmes distribués n’est pas la latence mais les défaillances partielles, et même une simple gestion d’exception sur une seule machine devient, sur plusieurs machines, une partie de la conception du protocole
  • Les systèmes distribués robustes présentent des bugs qui n’apparaissent que sur plusieurs vraies machines, dans des conditions réseau de datacenter, ou avec de gros jeux de données, ce qui les rend plus coûteux que les systèmes sur une seule machine
  • Pour construire un système exploitable, il faut réduire la coordination et accroître l’indépendance, en faisant de la backpressure, de la disponibilité partielle, des métriques et des percentiles des outils de base
  • Les feature flags, la conception de l’espace d’ID, la localité des données et l’extraction de services sont des techniques pratiques qui réduisent le périmètre des pannes et le coût de coordination pendant les migrations et la montée en charge
  • Le théorème CAP est moins un point de départ pour construire un système qu’un outil de critique de conception, et dans les systèmes distribués, la coordination sociale entre équipes et organisations est aussi difficile que les problèmes techniques

Ce qu’on oublie facilement quand on découvre les systèmes distribués

  • Une grande partie des leçons des ingénieurs en systèmes distribués vient d’erreurs commises sous trafic de production et de cicatrices d’exploitation, mais les ingénieurs débutants ont besoin d’un contexte plus direct et plus actionnable
  • Fallacies of Distributed Computing et le théorème CAP aident pour l’autoformation, mais restent trop abstraits pour permettre à un ingénieur peu expérimenté de passer immédiatement à l’action
  • Les systèmes distribués demandent plus de machines et plus de capital, ce qui implique plus facilement davantage d’équipes et des organisations plus grandes
    • Les problèmes sociaux sont en général la partie la plus difficile du travail de développement logiciel, et cela peut être particulièrement marqué dans le développement de systèmes distribués
    • Il arrive que les solutions sociales soient plus efficaces et plus satisfaisantes que les solutions techniques, mais le bagage, la formation et l’expérience poussent souvent les ingénieurs vers des réponses techniques

Concevoir en partant du principe que l’échec et le coût sont normaux

  • Les systèmes distribués échouent souvent, et pire encore, ils échouent partiellement
    • Si l’échec d’un mutex unlock dans un processus unique peut se régler en faisant planter le processus, l’échec d’un unlock dans un mutex distribué doit être intégré au protocole de verrouillage lui-même
    • Des approches comme « il suffit d’envoyer le write aux deux machines » ou « il suffit de réessayer jusqu’à ce que ça marche » ne traitent pas correctement les cas où un seul write réussit et l’autre échoue
    • On rencontre des pannes de switch, la disparition d’un leader à cause d’une pause GC, l’échec distant d’un socket write qui semblait avoir réussi, ou encore un seul disque lent qui ralentit tout le protocole de communication du cluster
  • Les solutions distribuées robustes coûtent plus cher que les solutions sur une seule machine
    • Il existe des pannes qui n’apparaissent que sur plusieurs machines, et même si les VM et le cloud réduisent le coût, cela ne sera jamais aussi économique que de concevoir, implémenter et tester sur une seule machine déjà disponible
    • Certaines défaillances ne se révèlent qu’avec des jeux de données trop volumineux pour tenir sur une machine partagée, ou dans des conditions réseau de datacenter, ce qui exige un véritable environnement distribué
    • La simulation est utile, mais ne remplace pas tous les bugs des systèmes distribués
  • Les systèmes distribués open source robustes sont bien plus rares que les systèmes robustes sur une seule machine
    • Le coût d’exécuter longtemps un grand nombre de machines pèse sur les communautés open source
    • Les développeurs amateurs et dilettantes construisent de l’open source avec les machines qu’ils possèdent déjà et leur temps libre, il est donc difficile de trouver des développeurs prêts à lancer, maintenir et financer plusieurs machines
    • Les ingénieurs d’entreprise comblent parfois une partie du manque, mais les priorités de leur organisation ne coïncident pas toujours avec celles des utilisateurs

Réduire la coordination et absorber les pannes

  • Il faut éviter autant que possible la coordination entre machines
    • Le cœur de la scalabilité horizontale consiste à répartir les données de façon suffisamment indépendante pour minimiser les échanges et les accords entre machines
    • Chaque fois que deux machines doivent se mettre d’accord sur quelque chose, l’implémentation du service devient plus difficile
    • La vitesse de propagation de l’information a une limite, les communications réseau sont plus instables qu’on ne l’imagine, et notre intuition sur le consensus peut être trompeuse
    • Il est utile de comprendre le problème des Two Generals, des Byzantine Generals et la difficulté d’implémenter Paxos
  • Si le problème tient en mémoire, pour un ingénieur en systèmes distribués, il s’agit souvent d’un problème assez mineur
    • Il est bien plus difficile de traiter rapidement des données qui se trouvent à plusieurs switches de distance plutôt qu’à quelques pointeurs de distance
    • Les algorithmes et implémentations pour machine unique sont abondants, alors que la littérature et les implémentations pour systèmes distribués sont bien plus rares
  • « C’est lent » est le problème le plus difficile à déboguer
    • Un ou plusieurs systèmes impliqués dans une requête utilisateur peuvent être lents, ou bien une partie d’un pipeline de transformation traversant plusieurs machines
    • La description du problème donne en elle-même très peu d’indices sur l’emplacement du défaut, et il peut y avoir des défaillances partielles cachées qui n’apparaissent pas sur les graphes habituels
    • Tant que la dégradation des performances n’est pas très évidente, il est difficile d’obtenir assez de ressources — temps, argent, outils — pour enquêter correctement
    • Dapper et Zipkin ont été créés pour cette raison
  • Il faut implémenter de la backpressure à l’échelle du système
    • La backpressure consiste, pour le système qui sert, à signaler un échec au système demandeur, afin que celui-ci le traite et évite la surcharge de lui-même comme de l’autre système
    • Limiter l’utilisation des ressources pendant les surcharges ou les pannes est un composant fondamental des systèmes distribués robustes
    • En pratique, cela signifie souvent jeter les nouveaux messages ou renvoyer une erreur à l’utilisateur quand les ressources sont limitées ou qu’une panne survient, et dans les deux cas il faut incrémenter les métriques
    • Des timeouts et un backoff exponentiel sont indispensables pour les connexions et requêtes vers d’autres systèmes
    • Sans backpressure, les défaillances en cascade ou les pertes de messages involontaires deviennent probables
  • Il faut trouver des moyens d’offrir une disponibilité partielle
    • La disponibilité partielle est la capacité d’un système à renvoyer une partie des résultats même lorsqu’une partie du système a échoué
    • Un système de recherche peut arbitrer entre la qualité des résultats et le temps d’attente imposé à l’utilisateur, et renvoyer uniquement les résultats collectés une fois la limite de temps atteinte
    • Dans la messagerie privée, il peut être préférable qu’une panne n’affecte qu’une partie des utilisateurs plutôt que de faire disparaître une partie des messages pour tous
    • Il faut décider dans quelle mesure isoler les domaines de panne pour qu’un incident sur la messagerie privée n’affecte pas une fonctionnalité sans rapport, comme l’envoi d’images publiques

Faire de l’observabilité, de la capacité et du déploiement des outils d’exploitation

  • Les métriques sont un moyen indispensable de savoir ce que fait réellement un système en production
    • Les percentiles de latence, les compteurs d’incréments pour certains comportements, ou les taux de variation réduisent l’écart entre le comportement qu’on croit avoir et le comportement réel du système
    • Savoir en quoi le comportement du système au vingtième jour diffère de celui du quinzième fait la différence entre une ingénierie réussie et une forme de sorcellerie ratée
    • Les métriques sont nécessaires pour comprendre les problèmes et le comportement, mais ne suffisent pas à elles seules à dire quoi faire ensuite
  • Les fichiers de log sont utiles, mais mentent facilement
    • Même si certaines classes d’erreurs occupent une grande partie de l’espace dans les logs, elles peuvent représenter une faible proportion des requêtes réelles
    • Les logs de succès sont généralement redondants, peuvent faire exploser le disque, et les ingénieurs se trompent souvent sur les logs d’erreur qui seront réellement utiles
    • Il vaut mieux écrire les logs en supposant qu’ils seront lus par quelqu’un qui n’a jamais vu le code
    • Surévaluer une anomalie observée dans les logs sans la confirmer par des métriques peut prolonger un incident
  • Il faut utiliser des percentiles plutôt que des moyennes
    • Les 50th, 99th, 99.9th et 99.99th percentiles sont plus précis et plus utiles que les moyennes dans la plupart des systèmes distribués
    • Les moyennes supposent que les métriques suivies ont une distribution en cloche, ce qui est rarement le cas pour les indicateurs qui intéressent réellement les ingénieurs
    • Si la latence d’un système distribué ne suit pas une distribution en cloche, la latence moyenne conduit à de mauvaises décisions et à une mauvaise compréhension
  • Il faut apprendre à estimer la capacité
    • Savoir combien de machines une charge de travail exige fait la différence entre un système durable et un système à remplacer au bout de trois mois
    • Fin 2012, une machine courante disposait d’environ 24 Go de mémoire, dont 4 à 5 Go pour l’OS, quelques Go au minimum pour traiter les requêtes, et un tweet id occupait 8 octets : ce genre de calcul approximatif est nécessaire
    • Le document de Jeff Dean, Numbers Everyone Should Know, aide à se forger des ordres de grandeur
  • Les feature flags sont une méthode de déploiement de l’infrastructure
    • Les feature flags sont puissants non seulement pour les tests A/B frontend, mais aussi pour remplacer une infrastructure
    • L’approche du « big cutover » a fait échouer de nombreux projets, contraints à un rollback à cause de bugs découverts trop tard
    • Lors d’une migration depuis une base de données unique vers un service qui masque un nouveau stockage, on peut augmenter progressivement les writes envoyés en parallèle vers le nouveau service, tester les reads sans les utiliser encore pour répondre à l’utilisateur après le backfill, puis comparer les reads de l’ancien et du nouveau système avant d’augmenter progressivement les vrais reads
    • En cas de problème, on peut immédiatement réduire la valeur du flag ou la ramener à 0, tout en ajustant le volume de trafic pour déboguer et expérimenter
    • Les feature flags sont un compromis qui échange de la complexité locale dans le code contre de la simplicité globale et de la résilience
    • Il faut accepter que plusieurs versions d’une infrastructure et de données coexistent normalement, et non comme une exception

Concevoir le modèle de données et les frontières

  • L’espace d’ID détermine la forme du système
    • Plus il faut d’ID pour atteindre une donnée, plus on a d’options pour partitionner cette donnée
    • Moins il faut d’ID pour une donnée, plus il est facile de consommer la sortie du système
    • L’API Twitter v1 permettait de récupérer, créer et supprimer un tweet via un identifiant numérique 64 bits unique, sans lien avec d’autres données
    • Quand le nombre de tweets augmente, on peut stocker les tweets d’un même utilisateur sur la même machine pour construire efficacement la timeline des tweets de l’utilisateur et la timeline des abonnements, mais comme l’API publique permettait d’adresser tous les tweets uniquement par tweet id, un service de lookup est devenu nécessaire pour savoir quel user possède quel tweet id
    • Une alternative aurait été d’exiger le user id pour récupérer un tweet, ou d’inclure le user id dans le tweet id lui-même, mais cela aurait eu pour coût de rendre le tweet id non k-sortable numeric
    • La structure des ID peut avoir un impact sur des attaques ou risques comme la désanonymisation de données privées, le crawling inattendu, les problèmes liés aux ID auto-incrémentés, ou les Insecure Direct Object References
  • Il faut exploiter la localité des données
    • Plus le traitement des données et le cache sont proches du stockage persistant, plus le traitement est efficace et plus il est facile de préserver la cohérence et la vitesse du cache
    • Le réseau accumule bien plus d’échecs et de latence qu’un déréférencement de pointeur ou qu’un fread(3)
    • La localité ne vaut pas seulement dans l’espace, mais aussi dans le temps
    • Si plusieurs utilisateurs effectuent presque simultanément la même requête coûteuse, on peut les fusionner en une seule, et si des requêtes de même nature arrivent à peu d’intervalle, on peut les regrouper en une requête plus grosse
    • Cette approche réduit l’overhead de communication et facilite la gestion des incidents
  • Réécrire des données mises en cache dans le stockage persistant est une mauvaise idée
    • Ce défaut apparaît surtout dans des systèmes conçus au départ par des personnes ayant peu d’expérience des systèmes distribués
    • Quand une implémentation parle de « Russian-doll caching », il y a de fortes chances de tomber sur des bugs visibles
    • Un symptôme courant est le retour à une ancienne valeur pour des informations utilisateur comme le screenname, l’email ou le mot de passe hashé
  • Les ordinateurs peuvent faire plus de choses qu’on ne le pense
    • Fin 2012, même un serveur web léger disposait de plus de 6 processeurs, de 24 Go de mémoire et d’un espace disque très important
    • Même des applications CRUD relativement complexes sur des runtimes modernes peuvent traiter sur une seule machine des milliers de requests per second en quelques centaines de millisecondes
    • Dans la plupart des cas, quelques centaines de requests per second par machine ne sont pas un chiffre particulièrement impressionnant du point de vue de l’exploitation
    • En profilant l’application et en introduisant des optimisations guidées par la mesure, il n’est pas difficile d’obtenir de meilleures performances
  • Le théorème CAP doit être utilisé comme outil de critique de conception, pas comme méthode de construction d’un système
    • Le théorème CAP est trop général et l’espace des solutions possibles trop vaste pour servir de premier principe permettant de dériver un système fonctionnel
    • En réexaminant de façon répétée une conception à travers les contraintes que CAP impose aux sous-systèmes, on peut aboutir à une meilleure architecture
    • Parmi C, A et P, on ne peut pas choisir CA
  • Extraire des services permet de bénéficier des avantages de l’encapsulation et du déploiement
    • Ici, un service désigne un système distribué qui contient une logique d’un niveau plus élevé qu’un système de stockage et propose en général une API de type requête-réponse
    • Il faut se demander en permanence s’il serait plus simple de faire évoluer un certain code en le sortant du système interne pour en faire un service séparé
    • Un service extrait fournit une encapsulation comparable à celle d’une bibliothèque, tout en rendant le déploiement des changements plus rapide et plus facile qu’une mise à niveau de bibliothèque dans les systèmes clients
    • Les petits services ont moins de dépendances de code et d’exploitation, et des frontières strictes réduisent les raccourcis tolérés dans une bibliothèque
    • Lorsqu’il existe plusieurs systèmes clients, une bibliothèque partagée impose de coordonner le déploiement chez chaque client, et cela devient encore plus difficile si une corruption de données peut dépendre de l’ordre de déploiement
    • Si les mainteneurs des systèmes clients sont différents, le coût de coordination sociale augmente aussi, car leurs priorités ne sont pas forcément alignées pour accepter une mise à niveau
    • Un cas d’usage représentatif consiste à masquer une couche de stockage en cours de modification derrière un service offrant une API plus pratique et de surface plus réduite

1 commentaires

 
GN⁺ 2024-09-03
Commentaires sur Hacker News
  • L’article aurait absolument dû couvrir CALM (la cohérence comme monotonie logique). C’est bien plus facile à comprendre que CAP, c’est un résultat plus fondamental, et cela permet même à des personnes peu expérimentées de construire des systèmes distribués très robustes.
    L’idempotence, les CRDT, le WAL et Raft sont tous des cas particuliers du principe CALM.
    https://arxiv.org/pdf/1901.01930

    • Cet article est paru 6 ans avant ce papier
    • En regardant le dépôt bloom, il semble assez stagnant ; je me demande si le travail se poursuit encore
  • Un point absent ici, mais que j’aime comme principe général : la livraison exactement une fois est impossible.
    On peut avoir au plus une fois ou au moins une fois, mais il faut choisir le mode de défaillance que l’on accepte et concevoir le système en conséquence.

    • Plus précisément, cela vaut entre deux systèmes distribués qui ne partagent pas le même domaine transactionnel ou qui ne sont pas logiquement monotones.
      Dans une base de données en cluster, déplacer une donnée d’une ligne vers une autre est possible, et l’on peut interpréter cela comme la livraison d’un message.
      Si l’ensemble du système est idempotent, ou si l’on peut traiter tout le système distribué comme une seule unité pouvant être rollbackée ensemble, on peut obtenir une livraison exactement une fois. Autrement dit, il ne doit pas y avoir d’effets de bord sur d’autres systèmes en dehors du domaine.
      Dans les deux cas, il s’agit d’une forme de monotonie logique. L’idempotence est facile à reconnaître, et la transactionalité repose elle aussi sur la monotonie via des algorithmes comme WAL et Raft.
      L’article aurait dû traiter CALM (la cohérence comme monotonie logique). C’est bien plus facile à comprendre que CAP et c’est un résultat plus fondamental.
      https://arxiv.org/pdf/1901.01930
    • Tout au long de ma carrière, j’ai vu beaucoup d’ingénieurs concevoir des systèmes distribués sans connaître ce concept, et on n’insistera jamais assez dessus
    • Dans un environnement où des pannes réseau arbitraires sont possibles, même la livraison au moins une fois est impossible
    • La partie importante de cette leçon, c’est « et en réalité, on n’en a même pas besoin »
    • Apache Flink fournit des garanties exactly-once de bout en bout lorsqu’il est utilisé avec des sources et des sinks de données qui participent au mécanisme de checkpoint.
      An Overview of End-to-End Exactly-Once Processing in Apache Flink (with Apache Kafka, too!) — https://flink.apache.org/2018/02/28/an-overview-of-end-to-en...
      Flink's Fault Tolerance Guarantees — https://nightlies.apache.org/flink/flink-docs-release-1.20/d...
  • En corollaire de « si le problème tient en mémoire, il est probablement trivial », ce qui tient en mémoire est bien plus vaste qu’on ne le pense.
    Je pensais savoir ce qu’était une grosse quantité de RAM, puis les grands clouds ont commencé à proposer des VM de 12 To pour SAP HANA, et j’ai changé d’avis.
    L’article aborde très brièvement ce point avec « les ordinateurs peuvent faire plus de choses qu’on ne le pense », mais il ne parle que de machines à 24 Go. Même en tenant compte du fait qu’on était en 2012, il devait déjà exister pas mal de machines avec dix fois plus de RAM.

    • Même des ingénieurs relativement seniors font assez souvent cette erreur. Pour un SaaS dont les données analytiques par client montent au maximum à 100 Go, un Postgres shardé finit par suffire
  • Je partage ce document avec les personnes les plus prometteuses avec qui je travaille.
    Quand je travaillais chez Lookout, Jeff Hodges avait présenté cet essai sous forme de talk, et avait ajouté à la fin le corollaire « ne faites pas comme si l’ingénierie n’était pas politique ».
    Les gens qui pensent que le code parle de lui-même passent à côté d’un aspect important pour influencer la manière dont quelque chose sera construit et obtenir de vrais résultats.
    Dix ans plus tard, peu de gens comprennent encore de façon aussi concise l’intersection entre le leadership d’ingénierie et ce que je considère comme les compétences de base d’un SRE/DevOps ordinaire.

    • Je serais curieux de connaître d’autres bonnes ressources à lire sur ce sujet
  • Anciennes discussions :
    https://news.ycombinator.com/item?id=5055371
    346 points|jcdavis|12 years ago|42 comments
    https://news.ycombinator.com/item?id=12245909
    386 points|kiyanwang|8 years ago|133 comments

  • Excellente liste, et j’aime ses explications pratiques et réalistes. Pas de buzzwords, pas de « microservices ».
    Je pense qu’une bonne partie de ces conseils s’applique aussi aux systèmes sur une seule machine. Il peut y avoir de nombreux sous-composants distribués dans une certaine mesure, comme l’IPC entre programmes ou la coordination de threads au sein d’un même processus.
    Même la notion de mémoire unifiée sur une seule machine est en partie une fiction, même si le matériel peut fournir de meilleures garanties qu’un environnement distribué « réel ».
    Beaucoup des conseils de l’article qui comparent « distribué » et « machine unique » s’appliquent assez bien aussi à la comparaison entre multithread et monothread.
    Sur un autre axe, si vous écrivez un programme puis le distribuez pour que plusieurs personnes l’exécutent, cela devient aussi une sorte de situation « distribuée ». Différentes versions existent dans la nature, et il faut se soucier de compatibilité et de mises à niveau ; les feature flags mentionnés dans l’article sont donc pertinents.
    La distribution ressemble davantage à un spectre allant d’un seul CPU à plusieurs CPU, puis à plusieurs ordinateurs étroitement connectés, puis à plusieurs ordinateurs répartis dans le monde entier, avec de nombreux points et de nombreuses dimensions entre les deux.

    • L’expression « système distribué » n’implique aucune contrainte sur le mode de déploiement. La caractéristique essentielle de la définition est simplement que différents flux de contrôle communiquent en réseau par passage de messages.
      Plusieurs processus communiquant via localhost sur la même machine constituent un exemple très connu de système distribué, et c’est d’ailleurs là que beaucoup de gens découvrent les systèmes distribués.
    • Je pense souvent à cet univers voisin, si proche qu’il en est d’autant plus frustrant, où AMD aurait pu fournir un espace mémoire différent pour chaque chiplet.
      Dans ce cas, tout le monde écrirait tout son code sous forme de magnifiques programmes MPI à mémoire distribuée. Le faux partage disparaîtrait, et nous serions obligés de réfléchir explicitement et en profondeur aux schémas de communication.
  • J’ai eu l’occasion de travailler brièvement avec l’auteur de cet article ces dernières années. Jeff a été l’une des personnes les plus perspicaces et positives dont j’aie appris.
    Il était étonnamment franc sur les difficultés qu’il rencontrait, et très accessible pour le mentorat et les conseils.

  • Dans « les systèmes distribués sont différents parce qu’ils échouent souvent », le point clé n’est pas le simple taux de panne, mais le taux de panne dans un système à plusieurs nœuds.
    Et les « problèmes de systèmes distribués » ne surviennent pas seulement sur plusieurs serveurs reliés par un réseau. Tout ensemble de nœuds liés entre eux peut rencontrer des problèmes similaires : des fichiers sur des disques logiquement reliés, ou des buffers sur différents périphériques d’entrée/sortie.

    • Exact. En réalité, ce type de problème peut apparaître, et apparaît effectivement, dans presque tout système logiciel qui dépasse un processus monothread unique épinglé en mémoire.
      Certains vétérans se moquent de la complexité excessive induite par l’atténuation de ces problèmes, et se plaignent qu’il serait beaucoup plus simple de faire tourner le logiciel sur un seul serveur.
      Dans les faits, même à l’époque des AS/400 ou des VAXft, ce n’était presque pas vrai, et cela l’était encore moins dans le monde plus chaotique d’Unix multi-utilisateur et multiprocessus.
  • « Si le problème tient en mémoire, il est probablement trivial » est une erreur courante chez certains ingénieurs en systèmes distribués.
    Ce n’est pas du tout trivial ; les problèmes à traiter se déplacent simplement vers un domaine complémentaire.
    Cette erreur conduit facilement à des situations où l’on utilise un cluster de 100 machines pour faire quelque chose qu’une machine unique correctement optimisée pourrait faire.