Que dois-je faire lorsque le verrouillage optimiste ne fonctionne pas?


11

J'ai ce scénario suivant:

  1. Un utilisateur fait une demande GET à /projects/1et reçoit un ETag .
  2. L'utilisateur fait une demande PUT/projects/1 avec l'ETag à partir de l'étape # 1.
  3. L'utilisateur fait une autre demande PUT /projects/1avec l'ETag à partir de l'étape # 1.

Normalement, la deuxième demande PUT recevrait une réponse 412, car l'ETag est désormais périmé - la première demande PUT a modifié la ressource, de sorte que l'ETag ne correspond plus.

Mais que se passe-t-il si les deux demandes PUT sont envoyées en même temps (ou exactement l'une après l'autre)? La première demande PUT n'a pas le temps de traiter et de mettre à jour la ressource avant l'arrivée de PUT # 2, ce qui fait que PUT # 2 écrase PUT # 1. Le but du verrouillage optimiste est que cela ne se produise pas ...


3
Atomisez vos opérations dans des transactions au niveau de l'entreprise, comme Esben l'explique ci-dessous.
Robert Harvey

Que se passerait-il si j'atomisais mes opérations à l'aide de transactions? PUT # 2 ne serait pas traité tant que PUT # 1 ne serait pas entièrement traité?
maximedupre

7
Devenez pessimiste?
jpmc26

Eh bien, c'est à ça que sert le verrouillage.
Fattie

Correct, bien sûr, le Put # 2 ne devrait pas être traité - ils sont censés être uniques.
Fattie

Réponses:


21

Le mécanisme ETag spécifie uniquement le protocole de communication pour un verrouillage optimiste. Il incombe au service d'application de mettre en œuvre le mécanisme de détection des mises à jour simultanées pour appliquer le verrouillage optimiste.

Dans une application typique qui utilise une base de données, vous le feriez généralement en ouvrant une transaction lors du traitement d'une demande PUT. Vous devriez normalement lire l'état existant de la base de données à l'intérieur de cette transaction (pour obtenir un verrou de lecture), vérifier votre validité Etag et écraser les données (d'une manière qui provoquera un conflit d'écriture en cas de transaction simultanée incompatible), puis engagez-vous. Si vous configurez la transaction correctement, l'une des validations devrait échouer car elles essaieront toutes les deux de mettre à jour les mêmes données simultanément. Vous pourrez ensuite utiliser cet échec de transaction pour renvoyer 412 ou réessayer la demande, si cela a du sens pour l'application.


La façon dont le serveur implémente actuellement le mécanisme pour détecter les mises à jour simultanées consiste à comparer les hachages de la ressource. Le serveur utilise également des transactions pour toutes les opérations, mais je n'acquiert aucun verrou, ce qui pourrait être à l'origine du problème. Cependant, dans votre exemple, comment peut-il y avoir une erreur dans l'un des validations si les transactions utilisent des verrous? La deuxième transaction doit être en attente lors de la lecture de l'état, jusqu'à ce que la première transaction soit résolue.
maximedupre

1
@maximedupre: si vous utilisez une transaction, vous avez une sorte de verrous, bien qu'il puisse s'agir de verrous implicites (les verrous sont acquis automatiquement lorsque vous lisez / mettez à jour les champs plutôt que explicitement demandé). Le mécanisme que j'ai décrit ci-dessus peut être implémenté en utilisant uniquement ces verrouillages implicites. Comme votre autre question, cela dépend de la base de données que vous utilisez, mais de nombreuses bases de données modernes utilisent MVCC (contrôle de concurrence multi-version) pour permettre à plusieurs lecteurs et écrivains de travailler sur les mêmes champs sans se bloquer inutilement.
Lie Ryan

1
Avertissement: dans de nombreux SGBD (PostgreSQL, Oracle, SQL Server, etc.), le niveau d'isolement des transactions par défaut est "lu engagé", où votre approche n'est pas suffisante pour empêcher la condition de concurrence critique de l'OP. Dans de tels DMBS, vous pouvez le corriger en incluant AND ETag = ...dans UPDATEla WHEREclause de votre instruction et en vérifiant le nombre de lignes mis à jour par la suite. (Ou en utilisant un niveau d'isolement de transaction plus strict, mais je ne le recommande pas vraiment.)
ruakh

1
@ruakh: cela dépend de la façon dont vous écrivez votre requête, oui le niveau d'isolement par défaut ne fournit pas un tel comportement automatiquement pour toutes les requêtes, mais il est souvent possible de structurer votre transaction d'une manière qui sera suffisante pour implémenter un verrouillage optimiste. Dans la plupart des cas, si la cohérence des transactions est importante dans l'application, je recommanderais quand même la lecture répétable comme niveau d'isolement par défaut; dans les bases de données qui utilisent MVCC, la surcharge de lecture répétable est assez minime et simplifie considérablement l'application.
Lie Ryan

1
@ruakh: le principal inconvénient de la lecture répétable est que vous devrez être prêt à réessayer ou à échouer en cas de transaction simultanée. C'est normalement un problème, mais les applications qui fournissent le verrouillage optimiste en tant que stratégie de concurrence nécessiteront déjà cette gestion de toute façon, donc les échecs de lecture reproductibles mappent naturellement aux échecs de verrouillage optimistes et cela n'ajoutera pas réellement de nouveaux inconvénients.
Lie Ryan

13

Vous devez exécuter la paire suivante de manière atomique:

  • vérification de la validité de la balise (c'est-à-dire à jour)
  • la mise à jour de la ressource (ce qui inclut la mise à jour de sa balise)

D'autres appellent cela une transaction - mais fondamentalement, l'exécution atomique de ces deux opérations est ce qui empêche l'une d'écraser l'autre par accident de timing; sans cela, vous avez une condition de course, comme vous le constatez.

Ceci est toujours considéré comme un verrouillage optimiste, si vous regardez la situation dans son ensemble: que la ressource elle-même n'est pas verrouillée par la lecture initiale (GET) par un utilisateur ou des utilisateurs qui consultent les données, que ce soit avec l'intention de les mettre à jour ou non.

Un certain comportement atomique est nécessaire, mais cela se produit dans une seule demande (le PUT) plutôt que d'essayer de maintenir un verrou sur plusieurs interactions réseau; c'est un verrouillage optimiste: l'objet n'est pas verrouillé par le GET mais peut toujours être mis à jour en toute sécurité par PUT.

Il existe également de nombreuses façons de réaliser l'exécution atomique de ces deux opérations - le verrouillage de la ressource n'est pas la seule option; par exemple, un thread léger ou un verrou d'objet peut suffire et dépend de l'architecture et du contexte d'exécution de votre application.


4
+1 pour avoir noté que c'est l'atome qui compte. Selon la ressource sous-jacente mise à jour, cela peut être accompli sans transactions ni verrouillage. Par exemple, la comparaison et l'échange atomique d'une ressource en mémoire ou la recherche d'événements de données persistantes.
Aaron M. Eshbach

@ AaronM.Eshbach, d'accord, et merci d'avoir appelé ces personnes.
Erik Eidt

1

C'est au développeur de l'application de vérifier le E-Tag et de fournir cette logique. Ce n'est pas magique que le serveur Web fasse pour vous car il ne sait que calculer les en- E-Tagtêtes pour le contenu statique. Prenons donc votre scénario ci-dessus et décomposons comment l'interaction doit se produire.

GET /projects/1

Le serveur reçoit la demande, détermine le E-Tag pour cette version de l'enregistrement, le renvoyant avec le contenu réel.

200 - OK
E-Tag: "412"
Content-Type: application/json
{modified: false}

Étant donné que le client a maintenant la valeur E-Tag, il peut inclure cela avec la PUTdemande:

PUT /projects/1
If-Match: "412"
Content-Type: application/json
{modified: true}

À ce stade, votre application doit effectuer les opérations suivantes:

  • Vérifiez que l'E-Tag est toujours correct: "412" == "412"?
  • Si oui, effectuez la mise à jour et calculez un nouveau E-Tag

Envoyez la réponse de réussite.

204 No Content
E-Tag: "543"

Si une autre demande vient et tente d'effectuer une opération PUTsimilaire à la demande ci-dessus, la deuxième fois que votre code serveur l'évalue, vous êtes responsable de fournir le message d'erreur.

  • Vérifiez que l'E-Tag est toujours correct: "412"! = "543"

En cas d'échec, envoyez la réponse d'échec.

412 Precondition Failed

C'est le code que vous devez réellement écrire. Le E-Tag peut en fait être n'importe quel texte (dans les limites définies dans la spécification HTTP). Il n'est pas nécessaire que ce soit un nombre. Il peut également s'agir d'une valeur de hachage.


Ce n'est pas une notation HTTP standard que vous utilisez ici. Dans HTTP conforme standard, vous utilisez uniquement ETag dans un en-tête de réponse. Vous n'envoyez jamais ETag dans un en-tête de demande, mais utilisez plutôt la valeur ETag précédemment acquise dans un en-tête If-Match ou If-None-Match dans les en-têtes de demande.
Lie Ryan

-2

En complément des autres réponses, je posterai l'une des meilleures citations de la documentation ZeroMQ qui décrit fidèlement le problème sous-jacent:

Pour créer des programmes MT parfaitement parfaits (et je veux dire littéralement), nous n'avons pas besoin de mutex, de verrous ou de toute autre forme de communication inter-thread, à l'exception des messages envoyés via les sockets ZeroMQ.

Par «programmes MT parfaits», j'entends un code facile à écrire et à comprendre, qui fonctionne avec la même approche de conception dans n'importe quel langage de programmation et sur n'importe quel système d'exploitation, et qui évolue sur n'importe quel nombre de CPU avec zéro état d'attente et aucun point des rendements décroissants.

Si vous avez passé des années à apprendre des astuces pour faire fonctionner votre code MT, et encore moins rapidement, avec des verrous et des sémaphores et des sections critiques, vous serez dégoûté lorsque vous réaliserez que tout cela est pour rien. S'il y a une leçon que nous avons apprise de plus de 30 ans de programmation simultanée, c'est: ne partagez pas l'état. C'est comme deux ivrognes essayant de partager une bière. Peu importe qu'ils soient de bons copains. Tôt ou tard, ils vont se battre. Et plus vous ajoutez d'ivrognes à la table, plus ils se disputent la bière. La majorité tragique des applications MT ressemblent à des combats de bars ivres.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.