Comment empêcher les conditions de concurrence dans une application Web?

31

Prenons un site de commerce électronique, où Alice et Bob modifient tous deux les listes de produits. Alice améliore les descriptions, tandis que Bob met à jour les prix. Ils commencent à éditer le widget Acme Wonder en même temps. Bob termine premier et enregistre le produit avec le nouveau prix. Alice prend un peu plus de temps pour mettre à jour la description, et lorsqu'elle a terminé, elle enregistre le produit avec sa nouvelle description. Malheureusement, elle remplace également le prix par l'ancien prix, ce qui n'était pas prévu.

D'après mon expérience, ces problèmes sont extrêmement courants dans les applications Web. Certains logiciels (par exemple, les logiciels wiki) ont une protection contre cela - généralement la deuxième sauvegarde échoue avec "la page a été mise à jour pendant la modification". Mais la plupart des sites Web ne disposent pas de cette protection.

Il convient de noter que les méthodes du contrôleur sont en soi thread-safe. Habituellement, ils utilisent des transactions de base de données, ce qui les rend sécuritaires dans le sens où si Alice et Bob essaient de sauvegarder au même moment précis, cela ne causera pas de corruption. La condition de concurrence découle du fait qu'Alice ou Bob ont des données périmées dans leur navigateur.

Comment pouvons-nous empêcher de telles conditions de course? J'aimerais en particulier savoir:

  • Quelles techniques peuvent être utilisées? par exemple, le suivi de l'heure du dernier changement. Quels sont les avantages et les inconvénients de chacun.
  • Qu'est-ce qu'une expérience utilisateur utile?
  • Dans quels cadres cette protection est-elle intégrée?
paj28
la source
Vous avez déjà donné la réponse: en suivant la date de changement des objets et en la comparant à l'âge des données que d'autres changements tentent de mettre à jour. Voulez-vous savoir autre chose, par exemple, comment le faire efficacement?
Kilian Foth du
@KilianFoth - J'ai ajouté quelques informations sur ce que j'aimerais particulièrement savoir
paj28
1
Votre question n'est en rien particulière aux applications Web, les applications de bureau peuvent avoir exactement le même problème. Les stratégies de solution typiques sont décrites ici: stackoverflow.com/questions/129329/…
Doc Brown
2
Pour info, la forme de verrouillage que vous mentionnez dans votre question est connue sous le nom de " contrôle de concurrence optimiste "
TehShrike
Quelques discussions sur Django ici
paj28

Réponses:

17

Vous devez "lire vos écritures", ce qui signifie qu'avant de noter une modification, vous devez relire l'enregistrement et vérifier si des modifications y ont été apportées depuis la dernière lecture. Vous pouvez le faire champ par champ (à grain fin) ou basé sur un horodatage (à grain grossier). Pendant que vous effectuez cette vérification, vous avez besoin d'un verrou exclusif sur l'enregistrement. Si aucune modification n'a été apportée, vous pouvez noter vos modifications et libérer le verrou. Si l'enregistrement a changé entre-temps, vous abandonnez la transaction, libérez le verrou et prévenez l'utilisateur.

Phil
la source
Cela ressemble à l'approche la plus pratique. Connaissez-vous des cadres qui implémentent cela? Je pense que le plus gros problème avec ce schéma est qu'un simple message "modifier le conflit" frustrera les utilisateurs, mais il est difficile de fusionner les ensembles de modifications (manuellement ou automatiquement).
paj28
Malheureusement, je ne connais aucun framework qui prenne cela en charge. Je ne pense pas qu'un message d'erreur de modification d'erreurs sera perçu comme frustrant, tant qu'il n'apparaîtra pas souvent. En fin de compte, cela dépend de la charge utilisateur du système, que vous vérifiiez simplement l'horodatage ou implémentiez une fonction de fusion plus complexe.
Phil
J'ai maintenu un produit de base de données distribué sur PC qui utilisait l'approche fine (par rapport à sa copie de base de données locale): si un utilisateur modifiait le prix et l'autre changeait la description - pas de problème! Juste comme dans la vraie vie. Si deux utilisateurs ont changé le prix - le deuxième utilisateur s'excuse et réessaye leur changement. Aucun problème! Cela ne nécessite pas de verrous sauf au moment où les données sont écrites dans la base de données. Peu importe si un utilisateur va déjeuner pendant que sa modification est à l'écran et la soumet plus tard. Pour les modifications de base de données à distance, il était basé sur des horodatages d'enregistrement.
1
Dataflex avait une fonction appelée "relire ()" qui fait ce que vous décrivez. Dans les versions ultérieures, il était sûr dans un environnement multi-utilisateur. Et, en effet, c'était le seul moyen de faire fonctionner ces mises à jour entrelacées.
pouvez-vous donner un exemple de la façon de le faire avec le serveur sql? \
l --''''''---------------- '' '' '' '' '' ''
10

J'ai vu 2 façons principales:

  1. Ajoutez l'horodatage de la dernière mise à jour de la page que l'utilisation modifie dans une entrée masquée. Lors de la validation, l'horodatage est vérifié par rapport à l'actuel et s'il ne correspond pas, il a été mis à jour par quelqu'un d'autre et renvoie une erreur.

    • pro: plusieurs utilisateurs peuvent modifier différentes parties de la page. La page d'erreur peut conduire à une page diff où le deuxième utilisateur peut fusionner ses modifications dans la nouvelle page.

    • con: parfois une grande partie de l'effort est gaspillée lors de grandes modifications simultanées.

  2. Lorsqu'un utilisateur commence à modifier le verrouillage de la page pendant un temps raisonnable, lorsqu'un autre utilisateur tente ensuite de le modifier, il obtient une page d'erreur et doit attendre que le verrouillage expire ou que le premier utilisateur se soit engagé.

    • pro: les efforts d'édition ne sont pas gaspillés.

    • con: un utilisateur sans scrupules peut verrouiller une page indéfiniment. Une page avec un verrou expiré peut toujours être validée, sauf indication contraire (en utilisant la technique 1)

monstre à cliquet
la source
7

Utilisez le contrôle de concurrence optimiste .

Ajoutez une colonne versionNumber ou versionTimestamp à la table en question (l'entier est le plus sûr).

L'utilisateur 1 lit l'enregistrement:

{id:1, status:unimportant, version:5}

L'utilisateur 2 lit l'enregistrement:

{id:1, status:unimportant, version:5}

L'utilisateur 1 enregistre l'enregistrement, cela incrémente la version:

save {id:1, status:important, version:5}
new value {id:1, status:important, version:6}

L'utilisateur 2 essaie de sauvegarder l'enregistrement lu:

save {id:1, status:unimportant, version:5}
ERROR

Hibernate / JPA peut le faire automatiquement avec l' @Versionannotation

Vous devez maintenir l'état de l'enregistrement lu quelque part, généralement en session (c'est plus sûr que dans une variable de forme cachée).

Neil McGuigan
la source
Merci ... particulièrement utile de connaître @Version. Une question: pourquoi est-il sûr de stocker l'état dans la session? Dans ce cas, je craindrais que l'utilisation du bouton de retour puisse prêter à confusion.
paj28
La session est plus sûre qu'un élément de formulaire caché car l'utilisateur ne pourrait pas modifier la valeur de la version. Si ce n'est pas un problème, ignorez la partie sur la session
Neil McGuigan
Cette technique est appelée Optimiste de verrouillage hors ligne et il est en SQLAlchemy aussi bien
paj28
@ paj28 - ce lien pour SQLAlchemyne pointe rien sur les verrous hors ligne optimistes, et je ne le trouve pas dans les documents. Aviez-vous un lien plus utile ou dirigiez-vous simplement les gens vers SQLAlchemy en général?
dwanderson
@dwanderson Je voulais dire la partie compteur de version de ce lien.
paj28
1

Certains systèmes ORM (Object Relational Mapping) détectent les champs d'un objet qui ont changé depuis le chargement à partir de la base de données et construisent l'instruction de mise à jour SQL pour définir uniquement ces valeurs. ActiveRecord pour Ruby on Rails est l'un de ces ORM.

L'effet net est que les champs que l'utilisateur n'a pas modifiés ne sont pas inclus dans la commande UPDATE envoyée à la base de données. Les personnes qui mettent à jour différents champs en même temps n'écrasent pas les modifications des autres.

Selon le langage de programmation que vous utilisez, recherchez les ORM disponibles et voyez si l'un d'eux ne mettra à jour que les colonnes de la base de données marquées "sales" dans votre application.

Greg Burghardt
la source
Salut greg. Malheureusement, cela n'aide pas avec ce genre de conditions de course. Si vous considérez mon exemple d'origine, lorsque Alice enregistre, l'ORM voit la colonne de prix comme sale et la met à jour - même si la mise à jour n'est pas souhaitée.
paj28
1
@ paj28 Le point clé de la réponse de Greg est "les champs que l'utilisateur n'a pas modifiés ". Alice n'a pas changé le prix, donc l'ORM n'essaierait pas d'enregistrer la valeur "prix" dans la base de données.
Ross Patterson
@ RossPatterson - comment l'ORM connaît-il la différence entre les champs modifiés par l'utilisateur et les données périmées du navigateur? Ce n'est pas le cas, du moins sans faire un suivi supplémentaire. Si vous souhaitez modifier la réponse de Greg pour inclure un tel suivi, ou soumettre une autre réponse, ce serait utile.
paj28
@ paj28 une partie du système doit savoir ce que l'utilisateur a fait et ne stocker que les modifications apportées par l'utilisateur. Si l'utilisateur a changé le prix, puis l'a changé à nouveau, puis soumis, cela ne devrait pas compter comme «quelque chose que l'utilisateur a changé», car ils ne l'ont pas fait. Si vous avez un système qui nécessite ce niveau de contrôle d'accès simultané, vous devez le construire de cette façon. Sinon, non.
@nocomprende - Une partie, bien sûr - mais pas l'ORM comme le dit cette réponse
paj28