Styles

samedi 26 juillet 2008

Les mises à jour perdues sont trop souvent perdues

Il est assez honteux de voir encore aujourd'hui des applications Web ne pas traiter les problèmes de mises à jour perdues ("lost updates"). Ce problème est vieux comme le monde, des solutions plus ou moins complexes existent, et pourtant, cela n'empêche pas de le retrouver dans beaucoup d'applications de type concurrente et même dans les frameworks récents comme Grails par exemple.

J'ai rarement vu des dossiers de conception parler explicitement du problème et des solutions choisies. C'est pourtant une des rares choses à étudier.

Pour rappel donc, voici le scénario de ce problème :

Sur une application concurrente (comme le sont toutes les applications Web) :
- une session 1, lit l'information A
- une session 2, lit l'information A
- la session 1, met à jour l'information A
- la session 2, met à jour l'information A
La session 2 va écraser les mises à jour de la session 1 sans s'en apercevoir !

Ce problème arrive dès qu'une information est gérée concurremment (par plusieurs personnes en même temps).

Le problème est très bien explicité dans Expert Oracle Database Architecture de Tom Kyte.

La première chose à faire quand on construit une application Web, c'est de déterminer quelles sont les informations qui risquent d'être mises à jour par plusieurs personnes en même temps.

Ce ne sont pas toutes les informations qui sont accédées en même temps. Même si plusieurs utilisateurs accèdent à la même table en même temps, il se peut que par conception, il soit impossible aux uns d'écraser les données des autres.

Ensuite, on peut utiliser plusieurs méthodes pour empêcher le problème des mises à jour perdues:

- sérialiser tous les accès à l'information (on empêche d'accéder et mettre à jour une information tant qu'elle est en train d'être prise en charge par quelqu'un) : cela revient à mettre un verrou sur l'information, on perd alors tout l'intérêt d'une application concurrente.

- mettre un marqueur sur chaque information pour signifier à quelle date l'information a été mise à jour pour la dernière fois. Concrètement, si l'information est stockée dans une base de données, il s'agit de rajouter par exemple une colonne LAST_MOD contenant la date de dernière modification.

Avant chaque mise à jour, il doit y avoir une vérification que la date de dernière mise à jour LAST_MOD n'est pas plus récente que la date LAST_MOD existante lors de la lecture de l'information. Le scénario se déroule alors comme suit :
- la session 1, lit l'information A et le date LAST_MOD (last_mod1)

- la session 2, lit l'information A et le date LAST_MOD (last_mod1)

- la session 1, vérifie que LAST_MOD n'est pas plus récent que last_mod1. Ce n'est pas le cas donc l'information A est mise à jour, la date LAST_MOD contient maintenant la date à laquelle session 1 a mis à jour l'information (disons last_mod2),

- la session 2, vérifie que LAST_MOD n'est pas plus récent que last_mod1. C'est le cas puisque LAST_MOD en base vaut last_mod2 qui est plus récent que last_mod1. Le problème doit être alors remonté à l'utilisateur (par exemple, un message l'avertis que l'information A a été mise à jour depuis qu'on lui a présenté.)
- Le problème de LAST_MOD c'est qu'on n'est pas sûr à 100% qu'il n'y ai pas de mise à jour exactement au même moment par rapport au système. Si la précision de LAST_MOD est la seconde, il peut arriver le cas où les deux mises à jour se passent dans la même seconde. Le cas est rare il est vrai mais peut exister. Pour pallier à ce problème, il faut recourir à une valeur unique, calculée en fonction de la valeur de l'information. On calcule donc une chaîne de hachage et on la gère comme LAST_MOD (dans une colonne HASH par exemple)

- On peut ne pas vouloir bloquer les mises à jour quand ce n'est pas la même colonne qui est mise à jour. Pour une ligne dans une table de base de données, il y a plusieurs informations. Parfois deux utilisateurs veulent mettre à jour en même temps la ligne mais pas la même colonne de cette ligne. Ainsi, avec LAST_MOD ou HASH, on s'assure qu'il n'y a pas de mise à jour de toute la ligne en même temps. Mais il peut être intéressant de stocker les valeurs de chaque colonne de la ligne dans la session de l'utilisateur et de les comparer à celle de la base avant de faire la mise à jour.

On met à jour seulement les informations qui ont changées, le risque de mise à jour perdu diminue puisque la probabilité de mettre à jour la même colonne de la même ligne en même temps est plus faible. Cela permet d'être moins sensible aux mises à jour perdues et de remonter moins de problème à l'utilisateur.

Quelque soit la méthode employée, il est important d'en choisir une et de s'y tenir pour l'ensemble de l'application, pour qu'il soit plus facile de la maintenir. La méthode doit bien sur être explicité dans le dossier de conception.

Il faut bien garder à l'esprit que le problème des mises à jour perdues peut se produire pour n'importe quelle information, pas seulement en base de données. Dès que les données sont partagées (dans un fichier, en mémoire...), il faut faire attention aux utilisateurs qui vont les mettre à jour.

Parfois même un seul utilisateur peut perdre ses mises à jour s'il ne fait pas attention à ce qu'il fait sur chacun de ses écrans :)

Aucun commentaire: