- Redlock, basé sur Redis, vise un verrouillage distribué tolérant aux pannes, mais n’est pas assez sûr pour les tâches où la correction est en jeu, et il est trop complexe pour une simple optimisation d’efficacité
- Il faut d’abord distinguer deux objectifs des verrous distribués : l’efficacité pour réduire les travaux en double, et la correction pour protéger un état partagé ; le critère de décision est de savoir si un échec entraîne un surcoût ou une corruption des données
- Même avec un service de verrouillage parfait, de longues pauses GC, une suspension de processus ou une latence réseau peuvent permettre l’exécution d’une ancienne écriture après l’expiration du lease ; un fencing token est donc nécessaire
- Redlock ne peut pas produire de jeton monotone croissant à chaque acquisition de verrou, et l’expiration des clés Redis dépend de l’horloge système basée sur
gettimeofday, ce qui compromet la sécurité en cas de saut d’horloge ou de délai - Pour les verrous exigeant de la correction, il faut utiliser un système de consensus comme ZooKeeper avec vérification de fencing token, et limiter les verrous Redis mono-nœud aux usages approximatifs et non critiques
Point de départ de l’examen de Redlock
- Redlock est un algorithme qui implémente sur Redis un verrouillage distribué tolérant aux pannes, ou plus précisément un lease
- Il existe déjà plus de dix implémentations indépendantes, et comme on ne sait pas qui dépend de cet algorithme, un examen public est utile
- Redis lui-même est bien adapté au partage entre serveurs de données temporaires, approximatives et changeant rapidement
- Exemples : compteur de requêtes par adresse IP, ensemble d’IP uniques par identifiant utilisateur
- La source d’inquiétude est la tendance à utiliser Redis pour gérer des données où l’on attend une cohérence et une durabilité plus fortes, et les verrous distribués font partie de ces usages
Objectif d’un verrou : efficacité ou correction ?
- Dans une application distribuée, un verrou sert à faire en sorte qu’une seule machine à la fois exécute une tâche quand plusieurs nœuds essaient de faire la même chose
- Les raisons d’utiliser un verrou se divisent en gros en deux catégories
- Efficacité : une optimisation pour éviter de faire deux fois un calcul coûteux ; en cas d’échec, cela se traduit au pire par un léger surcoût AWS ou l’envoi en double d’une même notification par e-mail
- Correction : un mécanisme destiné à empêcher des processus concurrents de corrompre le même état ; en cas d’échec, cela peut entraîner une corruption de fichier, une perte de données, une incohérence permanente ou une administration erronée de médicament
- Pour un verrou visant l’efficacité, le coût et la complexité de Redlock, avec ses 5 serveurs Redis et son quorum majoritaire, sont inutiles
- Une instance Redis unique, avec éventuellement une réplication asynchrone, est plus adaptée
- Dans ce cas, on peut perdre certains verrous lors d’une panne de courant ou d’un problème sur le nœud Redis, mais si ce n’est qu’une optimisation non critique, c’est un échec acceptable
- Redlock peut sembler convenir à des verrous où la correction est importante à cause de ses 5 réplicas et de sa majorité, mais en pratique il ne convient pas à cet usage
Un lease seul ne permet pas de protéger une ressource en toute sécurité
- Les verrous en système distribué sont plus complexes qu’un mutex dans une application multithread, car les nœuds et le réseau peuvent échouer indépendamment les uns des autres
- Un flux typique de mise à jour d’un fichier dans un stockage partagé est : acquisition du verrou, lecture du fichier, modification, réécriture, puis libération du verrou
- Le verrou sert à éviter que deux clients effectuent simultanément un cycle read-modify-write et perdent une mise à jour
- Si un client reste arrêté trop longtemps alors qu’il détient le verrou, le lease peut expirer
- Le GC peut intervenir et suspendre le client pendant une longue durée
- Le lease est une bonne conception pour éviter qu’un client planté garde le verrou pour toujours, mais si la pause dépasse la durée d’expiration, le client peut continuer à écrire dangereusement sans savoir que son lease a expiré
- Ce problème n’est pas théorique : HBase a déjà rencontré un problème similaire
- Des pauses GC “stop-the-world” ont déjà duré plusieurs minutes
- Même des GC “concurrent” comme CMS dans la JVM HotSpot doivent parfois arrêter l’application
- Vérifier juste avant l’écriture si le verrou a expiré ne résout pas le problème
- Le GC peut suspendre le thread à n’importe quel moment, y compris entre la dernière vérification et l’écriture
Suspension de processus et latence réseau font partie du modèle de menace normal
- Même si l’on utilise un runtime sans longues pauses GC, un processus peut être arrêté pour de nombreuses raisons
- La lecture d’une adresse absente de la mémoire peut provoquer une page fault
- Si le disque est un EBS, la lecture d’une variable peut devenir une requête synchrone passant par le réseau Amazon
- Une contention CPU, un retard de planification ou un
SIGSTOPenvoyé par erreur peuvent aussi stopper le processus
- La latence réseau produit le même genre de problème
- L’application envoie une requête d’écriture, mais le paquet est retardé et n’arrive au serveur de stockage qu’après l’expiration du lease
- Lors d’un incident chez GitHub, des paquets réseau ont été retardés d’environ 90 secondes
- Les réseaux à paquets comme Ethernet et IP peuvent retarder arbitrairement les paquets, et cela arrive réellement
- Par conséquent, même dans un réseau bien géré, on ne peut pas faire d’hypothèse de timing ; un code basé sur de simples leases n’est donc pas fondamentalement sûr, quel que soit le service de verrouillage utilisé
Il faut bloquer les anciennes écritures avec un fencing token
- La solution consiste à inclure un fencing token dans chaque requête d’écriture vers le stockage
- Le fencing token est un nombre qui augmente chaque fois qu’un client acquiert le verrou
- Par exemple, le client 1 obtient un lease avec le jeton 33 puis reste bloqué longtemps, si bien que son lease expire
- Le client 2 obtient ensuite un nouveau lease avec le jeton 34 et envoie sa requête d’écriture au stockage
- Plus tard, si le client 1 se réveille et tente une écriture avec le jeton 33, le stockage rejette cette requête car il a déjà traité le jeton supérieur 34
- Pour être sûr, le serveur de stockage doit vérifier activement les jetons et rejeter toute écriture dont le jeton recule
- Si le service de verrouillage génère des jetons strictement monotones, le verrou peut devenir sûr
- Avec ZooKeeper comme service de verrouillage, on peut utiliser
zxidou le numéro de version d’un znode comme fencing token
- Avec ZooKeeper comme service de verrouillage, on peut utiliser
- Le gros problème de Redlock est qu’il ne sait pas générer de fencing token
- Sa valeur aléatoire unique ne fournit pas la monotonie croissante nécessaire
- Un compteur sur un seul nœud Redis n’est pas suffisant, car ce nœud peut tomber en panne
- Des compteurs répartis sur plusieurs nœuds peuvent diverger
- La génération de fencing tokens peut elle aussi nécessiter un algorithme de consensus
Redlock fonde sa sécurité sur des hypothèses temporelles
- En algorithmes distribués, un modèle pratique est le modèle asynchrone avec détecteur de pannes non fiable
- Un processus peut s’arrêter pour une durée arbitraire
- Les paquets peuvent être retardés arbitrairement par le réseau
- Les horloges peuvent être arbitrairement erronées
- Malgré cela, l’algorithme doit continuer à prendre des décisions correctes
- Les horloges ne devraient servir qu’à produire des timeouts afin de ne pas attendre éternellement lorsqu’un nœud est hors service
- Un timeout n’a pas besoin d’être exact, et le fait qu’une requête expire ne signifie pas forcément que l’autre nœud est tombé
- Cela peut être dû à une latence réseau ou à une erreur de l’horloge locale
- Redis utilise
gettimeofday, et non une horloge monotone, pour décider de l’expiration des clés- Avec
gettimeofday, l’heure système peut faire des sauts discontinus - Si NTP ajuste l’horloge ou si un administrateur modifie manuellement l’heure, l’expiration des clés Redis peut survenir bien plus tôt ou bien plus tard que prévu
- Avec
- Dans un algorithme du modèle asynchrone, la sécurité est généralement préservée sans hypothèse de timing, tandis que des mécanismes comme les timeouts n’affectent que la vivacité
- Si le timing devient chaotique, les performances peuvent se dégrader, mais l’algorithme ne devrait pas prendre de mauvaise décision
- Redlock, au contraire, dépend pour sa sécurité de plusieurs hypothèses temporelles
- Tous les nœuds Redis doivent conserver les clés pendant une durée à peu près correcte
- La latence réseau doit rester largement inférieure à la durée d’expiration
- Les pauses de processus doivent être bien plus courtes que cette durée d’expiration
Cas où Redlock casse quand le timing se dégrade
- Avec 5 nœuds Redis A, B, C, D, E et deux clients 1 et 2, si l’horloge d’un nœud saute vers l’avant, les deux clients peuvent croire qu’ils détiennent le verrou
- Le client 1 obtient le verrou sur A, B et C, mais n’atteint pas D et E à cause d’un problème réseau
- L’horloge de C saute vers l’avant et le verrou expire
- Le client 2 obtient alors le verrou sur C, D et E, mais n’atteint pas A ni B à cause d’un problème réseau
- Résultat : les clients 1 et 2 concluent tous deux qu’ils sont détenteurs du verrou
- Un problème similaire peut se produire si C plante avant de persister le verrou sur disque puis redémarre immédiatement
- La documentation de Redlock recommande de retarder le redémarrage d’un nœud planté pendant au moins le TTL maximal d’un verrou
- Ce délai de redémarrage dépend lui aussi d’une mesure du temps raisonnablement précise, et peut échouer si l’horloge saute
- La suspension d’un processus client peut également casser Redlock
- Le client 1 envoie des requêtes de verrouillage à A, B, C, D et E
- Pendant que les réponses sont en transit, le client 1 entre dans un GC stop-the-world
- Tous les verrous expirent sur les nœuds Redis
- Le client 2 obtient alors le verrou sur A, B, C, D et E
- Le client 1 termine son GC et reçoit les réponses positives restées dans les buffers réseau du noyau
- Les deux clients croient alors détenir le verrou
- Le fait que Redis soit écrit en C et n’ait pas de GC n’aide pas
- Le problème survient dans un système où les clients peuvent eux subir des pauses GC
- Il faut un mécanisme comme le fencing token pour empêcher le client 1 d’agir après que le client 2 a obtenu le verrou
- Une forte latence réseau peut produire le même effet qu’une suspension de processus
- Régler le TCP user timeout sur une valeur bien plus courte que le TTL Redis pourrait permettre d’ignorer certains paquets retardés, mais il faudrait examiner l’implémentation TCP concrète pour en être sûr
- Même dans ce cas, on retombe sur le problème de la précision des mesures de temps
Les hypothèses de système synchrone requises par Redlock
- Redlock ne fonctionne correctement que dans un modèle de système synchrone possédant les propriétés suivantes
- Une borne supérieure garantie sur la latence réseau
- Une durée maximale de suspension des processus
- Une borne sur l’erreur des horloges
- Le modèle synchrone ne signifie pas que les horloges sont parfaitement synchronisées, mais qu’il existe des bornes fixes connues pour la latence réseau, les pauses de processus et la dérive des horloges
- Redlock suppose que latence, pauses et dérive restent toutes petites par rapport au TTL du verrou
- Si les problèmes de timing deviennent du même ordre que le TTL, l’algorithme échoue
- Dans un datacenter classique, ces hypothèses peuvent être satisfaites la plupart du temps ; on parle alors de système partiellement synchrone
- Si la correction dépend du verrou, “la plupart du temps” ne suffit pas
- Dès que les hypothèses temporelles cessent d’être vraies, Redlock peut violer la sécurité, par exemple en accordant un lease à un second client avant l’expiration effective du lease du premier
- Le cas réel des paquets retardés de 90 secondes chez GitHub montre qu’il est difficile de supposer un modèle synchrone dans un environnement réel
- Raft, Viewstamped Replication, Zab et Paxos appartiennent à la catégorie des algorithmes de consensus conçus pour un modèle partiellement synchrone ou pour un modèle asynchrone avec détecteur de pannes
- Ces algorithmes obligent à abandonner les hypothèses de timing fortes et à ne pas supposer que réseau, processus et horloges d’un système distribué sont plus fiables qu’ils ne le sont réellement
Conclusion et options recommandées
- Redlock est inutilement lourd et coûteux pour un verrou destiné à l’optimisation de l’efficacité, et il n’est pas assez sûr pour un verrou où la correction est essentielle
- En particulier, il suppose en pratique un système synchrone avec des bornes sur la latence réseau et la durée d’exécution, et viole la sécurité dès que ces hypothèses tombent
- Il ne sait pas non plus générer de fencing token pour protéger le système contre une latence réseau extrême ou des processus bloqués
- Si l’on veut un verrou best effort pour optimiser l’efficacité, il vaut mieux utiliser l’algorithme de verrou mono-nœud de Redis
- Acquisition du verrou via un set-if-not-exists conditionnel
- Libération du verrou par suppression atomique seulement si la valeur correspond
- Le code doit documenter clairement que le verrou est approximatif et peut parfois échouer
- Il n’est pas nécessaire de mettre en place un cluster Redis à 5 nœuds
- Pour un verrou nécessitant de la correction, il ne faut pas utiliser Redlock mais un système de consensus comme ZooKeeper
- Si possible, on peut utiliser les recipes Curator qui implémentent déjà le verrou
- À défaut, on peut utiliser une base comme PostgreSQL qui fournit au minimum des garanties transactionnelles raisonnables
- Il faut imposer la vérification du fencing token sur tous les accès aux ressources protégées par le verrou
- Redis reste un outil utile lorsqu’il est employé conformément à sa vocation ; comme tout outil, il a des limites qu’il faut connaître et intégrer dans la conception
- Dans une mise à jour du 9 février 2016, Salvatore, l’auteur original de Redlock, a publié une réponse, mais la conclusion reste la même
1 commentaires
Avis sur Hacker News
Au travail, nous utilisons Temporal, et nous avons implémenté un verrou distribué avec un workflow dédié et des signaux.
Jusqu’ici, cela fonctionne bien, et comme nous déléguons la partie distribuée du verrouillage aux fonctionnalités de Temporal, l’implémentation reste assez simple.
Je me demande si Temporal est unique dans ce domaine, ou s’il existe des alternatives d’un niveau comparable.
Le fait qu’il ait été séparé d’Uber et qu’il soit utilisé par de grands fournisseurs donne l’impression qu’il est suffisamment éprouvé en production.
Pour les verrous distribués, j’utilise généralement les advisory locks PostgreSQL.
Même si le travail n’a aucun lien avec la base de données, on peut démarrer une transaction et prendre un advisory lock ; le verrou est alors conservé jusqu’à ce que l’application le libère explicitement ou que la transaction se termine, par exemple à cause d’un crash.
Jusqu’ici, cela m’a semblé assez sûr, mais je viens de réaliser que je n’ai jamais vérifié si la connexion à la base de données était encore valide.
Si le travail concerne la base de données, une requête échouerait et le travail échouerait aussi ; sinon, on pourrait avoir déjà perdu le verrou sans le savoir.
Si l’on veut une exactitude absolue sans jetons de fencing ni opérations atomiques, j’ai l’impression qu’il faut finalement un commit en deux phases pour toutes les opérations.
Pour faire correctement ce qui était probablement visé, il faudrait sans doute utiliser “EXCLUSIVE” ou “ACCESS EXCLUSIVE”, ou bien assurer un commit en deux phases ou l’idempotence de l’opération.
[0] https://www.postgresql.org/docs/current/explicit-locking.htm...
La plupart des bibliothèques utilisent généralement un pool de connexions ; il faut donc obtenir une connexion dédiée au verrou, et les vérifications périodiques du verrou doivent impérativement passer par cette même connexion.
Cela vaut la peine de lire le commentaire que j’avais laissé à l’époque sur ce billet de blog, ainsi que la réponse que j’ai écrite sur mon propre blog.
Dans le désordre : l’auteur a manqué un point essentiel sur le fonctionnement de l’algorithme, puis a rejeté l’algorithme sur la base d’arguments restants plus faibles.
Il n’est pas vrai non plus qu’avec les ordinateurs et les API modernes, il soit impossible d’attendre une durée approximativement correcte. Les pauses de GC sont bornées et les horloges monotones fonctionnent, donc ce sont des hypothèses acceptables.
Critiquer le mécanisme de libération automatique lui-même au motif qu’il expose une condition de concurrence potentielle, et le critiquer dans le cadre des objectifs de l’algorithme et de son modèle système, sont deux choses différentes.
Redlock a été utilisé avec succès pendant des années dans de nombreux cas d’usage ; si l’on fixe le timeout à une valeur bien supérieure au temps nécessaire pour terminer l’opération et aux pauses arbitraires qui peuvent survenir sur un système d’exploitation courant, il est très difficile de provoquer une condition de concurrence.
Bien sûr, si le timeout de libération automatique est trop court et que l’opération peut facilement prendre aussi longtemps, c’est une erreur de conception, mais ce n’est pas un problème propre à Redlock.
Utiliserais-tu RedLock dans une situation où le timeout est assez court (par exemple 1 à 2 secondes), où l’opération utilise généralement environ 90 % de ce timeout, et où ce qui est fait pendant que le verrou RedLock est détenu ne doit jamais s’exécuter en même temps que chez un autre détenteur du verrou ?
À mon avis, la bonne réponse ici est toujours “non”, car le risque que le bail expire avant que le client ait terminé son opération est très élevé.
RedLock ne peut pas garantir l’exclusion mutuelle dans toutes les situations ; il faut donc rendre l’opération idempotente, et ce type de cas est mieux implémenté avec du verrouillage optimiste.
Je suis en train de renforcer à nouveau mes connaissances bas niveau et en algorithmique ; quels bons livres recommanderiez-vous sur ce sujet ? J’ai le livre de l’auteur.
J’aimerais construire quelque chose pour le plaisir, mais les ressources sont soit au niveau jouet, soit beaucoup trop complexes.
Il suffit de choisir un sujet et de l’implémenter réellement.
J’avais écrit autrefois un billet de blog sur les verrous distribués à partir de cette ressource : https://medium.com/sahibinden-technology/an-easy-integration...
L’explication selon laquelle “un verrou a un timeout (autrement dit, c’est un bail)” me paraît étrange.
Premièrement, si le client crashe, même sans timed lease, l’OS ou le supervisor devrait libérer le verrou ; et si les deux meurent, la connexion finira de toute façon par être coupée, et le système réseau devrait le détecter via un reset, un timeout, l’absence de heartbeat, etc., invalider la connexion puis libérer le verrou.
Deuxièmement, si le problème est qu’un client bogué conserve le verrou trop longtemps sans crasher, un supervisor ne devrait-il pas le détecter et tuer le client avant de libérer le verrou pour les autres ?
Troisièmement, si l’on met en place des verrous avec timeout pour gérer ces cas limites, ne faudrait-il pas en informer le programme réel via une exception, un signal, une terminaison ou un mécanisme similaire ? Et ne faudrait-il pas attendre de vérifier que le programme a bien reçu la notification avant de libérer le verrou ?
L’idée même d’autoriser un programme à continuer son flux de contrôle normal alors que le timeout a expiré me semble être la cause profonde du problème ; je ne comprends pas pourquoi tout le monde passe dessus. Est-ce que je rate une raison évidente ?
L’entité qui invalide le verrou de son côté est le service de stockage, et sans garanties supplémentaires que Redlock ne fournit pas, le client ne peut pas détecter lui-même son propre problème.
Dans certains cas, ces paquets sont perdus, et le client sur la machine distante est déjà mort alors que le serveur conserve une connexion ouverte.
Au passage, ce n’est pas moi qui ai voté négativement.
Deno et Deno KV, hébergé par Deno Deploy, ont été utilisés pour implémenter un verrou distribué
En interne, cela utilise FoundationDB, une base de données distribuée, et des instances Deno tournant sur des appareils locaux se connectent au même Deno KV pour acquérir le verrou
Avec PostgreSQL, cela fonctionnerait aussi via
SELECT FOR UPDATE, mais la base de données elle-même n’est pas distribuéeEn 2018, nous avions évalué Redis pour notre cas d’usage, mais nous avons finalement choisi une solution moins tape-à-l’œil, qui n’a vraiment jamais échoué
Le cas d’usage consistait à distribuer un par un des billets identifiés à partir d’un ensemble fini de billets pour une campagne, un peu comme Ticketmaster attribue les sièges d’une salle de spectacle
Lorsqu’une requête arrivait, il fallait fournir un billet disponible, attacher les métadonnées de la requête au billet attribué, puis l’exclure des requêtes suivantes
Comme il y avait eu par le passé des campagnes en échec avec surallocation, sous-allocation ou allocations en double, l’exactitude était essentielle
Nous avions aussi essayé une implémentation simple avec Redis pour acquérir le verrou, vérifier le verrou, effectuer l’opération puis libérer le verrou, mais à l’époque la charge opérationnelle était trop importante pour nous, et heureusement que nous n’avons pas suivi cette voie
Le choix final a été Postgres. Notre « verrou distribué » ressemblait davantage à une instruction
UPDATEcomposite utilisant des fonctionnalités propres à Postgres : nous transformions la requête en une sorte d’opération sur des ensembles, et la base de données renvoyait soit un enregistrement de succès, soit une indication d’échec. Les transactions ACID l’ont emportéUne fois l’exactitude réglée, nous avons examiné l’échelle et les performances ; nous n’avions pas besoin de millions de requêtes par seconde, mais nous avions des critères pour les pics soudains
Nous avons optimisé les instances de base de données en lecture et en écriture dans le cluster, placé stratégiquement les campagnes plus grandes ou à forte demande sur des systèmes désignés, et poursuivi les optimisations pendant deux ans, sans jamais avoir une seule campagne de distribution de billets en échec
Je ne suis pas spécialiste des verrous distribués ; je me suis simplement concentré sur le problème à résoudre, j’ai essayé quelques approches, puis trouvé la solution adaptée
Comme une transaction
UPDATEne dure que quelques microsecondes, on peut centraliser le problème, ce qui est plus simple, plus rapide et plus sûrMais, comme l’explique l’article, ce n’est pas un problème distribué
Un verrou dans un système distribué n’est pas la même chose qu’un mutex dans une application multithread : c’est plus complexe, car plusieurs nœuds et le réseau peuvent échouer indépendamment, chacun de diverses manières
Un verrou distribué devient nécessaire lorsque les transactions peuvent durer de quelques secondes à plusieurs heures, et que les machines concernées peuvent tomber en panne alors qu’elles détiennent le verrou
Ici, la contrainte est « ne pas vendre plus de N billets », et la plupart des volumes de trafic réalistes pour ce genre de problème peuvent être gérés par le comportement transactionnel d’une base de données relationnelle traditionnelle, en laissant à la base la gestion interne des verrous
J’aimerais que les développeurs ne se précipitent pas si vite vers « je vais construire un verrou distribué ». Il existe presque toujours une meilleure réponse, mais cette réponse dépend de chaque application
Cela pourrait être un bon cas d’usage pour quelque chose comme le nouveau SQLite de Cloudflare
La première fois que j’ai lu quelque chose à ce sujet, c’était ici : https://code.flickr.net/2010/02/08/ticket-servers-distribute...
Beaucoup d’ingénieurs ne se soucient pas vraiment des problèmes d’exactitude avant qu’il ne soit trop tard. C’est un peu comme la sécurité
Même lorsqu’ils s’en soucient, ils ne vérifient souvent pas que ce qu’ils font est correct
Par exemple, dans mon domaine, des microservices, des acteurs et des processus s’échangent des messages sur le réseau, et plus de 95 % des implémentations que je vois comportent des cas limites où des messages peuvent être perdus ou traités dans le désordre
Mais les incitations ne sont pas assez alignées pour corriger ce problème. La structure de rémunération des dirigeants et des ingénieurs ne correspond pas aux résultats qui seraient les meilleurs pour les clients et les actionnaires
On veut mettre une frontière réseau entre des appels de fonction sans vraie raison, puis on se met à créer sans fin des serveurs et clients HTTP, de la sérialisation et désérialisation JSON pour ces appels de fonction, avec un peu de chance on utilise gRPC, et on essaie ensuite de réimplémenter des choses comme des transactions distribuées par-dessus cette frontière réseau
Au final, on ne fait que se créer du travail pour gérer les inévitables « interactions étranges à distance »
Les équipes produit et ingénierie doivent se mettre d’accord dessus, et si les SLO sont violés, il faut déplacer l’attention vers la stabilité du système
Comme il est difficile de convaincre tout le monde, il faut un bon leadership
Quand les bugs apparaissent, que les nouvelles fonctionnalités arrivent lentement ou presque plus du tout, et que les clients commencent à partir, l’argument pour intégrer la qualité au processus devient très facile à défendre
Les dirigeants mûrs anticipent cette étape aussi tôt que possible
[0] https://en.wikipedia.org/wiki/British_Post_Office_scandal
Mais je ne vois pas bien comment convaincre les managers d’hier de nous accorder le temps nécessaire pour bien faire les choses
Cela complique beaucoup trop les choses.
S’il existe quelque chose comme les tokens de fencing dont parle l’article, il n’y a pas besoin de verrou.
Le token n’a même pas besoin d’être monotone croissant ; il suffit que ce soit une valeur unique passive, partagée par le client et le stockage.
Si on l’appelle token de version, il peut s’agir d’une valeur monotone croissante, et un UUID, généralement plus facile à générer, fonctionne aussi. Techniquement, un hash de toutes les données du stockage serait aussi possible, mais ce ne serait pas pratique.
Le flux est le suivant : le client récupère auprès du stockage le token de version courant en même temps que les données à modifier, et le stockage lit atomiquement les données et le token, garantissant que ce token correspond bien à cette version des données.
Ensuite, le client renvoie le token de version avec ses modifications, et le stockage n’accepte la modification que si le token courant correspond au token transmis, puis génère atomiquement un nouveau token de version.
On peut introduire un verrou pour d’autres raisons, mais dans un système distribué, cela devrait être indépendant de l’intégrité du stockage.
Je n’aime pas non plus beaucoup le terme « verrou ». Comme c’est temporaire et non garanti, bail ou réservation exprime peut-être mieux le sens.
C’est une façon de pousser la complexité du côté de la base de données, mais il faut se rappeler qu’il est ici question de verrou distribué.
Avec une base de données unique, cela reste simple jusqu’au moment où la base crashe et où l’on ne sait plus quelles écritures CAS ont effectivement été appliquées.
Dans les grands systèmes qui nécessitent une haute disponibilité et une sauvegarde multi-datacenter, cette approche peut aussi se casser à cause des scénarios autour des défaillances de nœuds, et devenir assez complexe.
On utilise généralement un journal de transactions de type Paxos. Il ne faut pas supposer qu’il existe des solutions faciles dans les systèmes distribués. C’est toujours pénible.
Du point de vue de l’efficacité, prendre un verrou permet d’éviter de faire inutilement deux fois le même travail, par exemple un calcul coûteux.
Si le verrou échoue et que deux nœuds font le même travail, cela peut être mineur si le résultat n’est qu’un léger surcoût ou un doublon de la même notification par e-mail.
Mais je pense que le fait que plusieurs nœuds effectuent le même travail est bien pire que dans les exemples donnés, car cela peut entraver le traitement distribué scalable lui-même.
Supposons qu’un système de stockage ait deux nœuds, et que deux processus lecture-modification-écriture tournent. Les processus 1 et 2 obtiennent tous les deux le premier token
abc.Le processus 1 committe, le token devient
cdeet la modification est streamée vers le nœud 2, mais elle y arrive en retard à cause de la latence réseau.Entre-temps, si le processus 2 committe sur le nœud 2 avec le token
abc, le nœud 2 n’ayant pas encore reçu le message du nœud 1, il accepte la modification et le système se retrouve dans un état incohérent.Avec un token de fencing monotone croissant, cela ne se produit pas, car cette exigence force les nœuds à se mettre d’accord sur l’ordre global des opérations avant de fournir un token.