web-dev-qa-db-fra.com

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

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 met un peu plus de temps à 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. suivre 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?
33
paj28

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.

19
Phil

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 peu scrupuleux 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)

10
ratchet freak

Utilisez 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 le @Version annotation

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 formulaire cachée).

8
Neil McGuigan

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.

1
Greg Burghardt