data / redis
Transactions : MULTI, EXEC, WATCH
Explication
Ce que vous allez apprendre
- Regrouper plusieurs commandes en une transaction atomique avec
MULTI/EXEC - Comprendre que Redis ne fait AUCUN rollback en cas d'erreur applicative dans une transaction
- Utiliser
WATCHpour implémenter un verrouillage optimiste sur une ou plusieurs clés - Écrire un pattern watch/retry en boucle pour des mises à jour concurrentes sûres
- Distinguer isolation (garantie par Redis) et rollback (jamais garanti)
Dans quel contexte ?
Une application bancaire simplifiée doit transférer un montant du compte A vers le compte B : décrémenter A et incrémenter B doivent se produire ensemble, sans qu'aucun autre client ne puisse lire un état intermédiaire incohérent (A déjà décrémenté, B pas encore crédité) ni intervenir entre les deux opérations.
D'abord, le mécanisme de base : mettre en file puis exécuter d'un bloc
MULTI démarre une transaction : toutes les commandes tapées ensuite ne s'exécutent pas immédiatement, elles sont simplement mises en file d'attente côté client. EXEC déclenche l'exécution de toutes ces commandes d'un coup, de façon séquentielle et sans qu'aucune commande d'un autre client ne puisse s'intercaler entre elles.
Une fois ce mécanisme compris, une confusion fréquente doit être dissipée
MULTI/EXEC garantit l'isolation (aucune interférence d'un autre client pendant l'exécution), mais PAS un rollback en cas d'erreur : si une commande échoue au moment de l'exécution (par exemple une erreur de type, comme incrémenter une valeur qui n'est pas numérique), les autres commandes de la transaction s'exécutent quand même. Ce n'est donc pas une transaction "tout ou rien" au sens des bases relationnelles classiques.
| Garantie | MULTI/EXEC la fournit-elle ? |
|---|---|
| Isolation (pas d'interférence d'un autre client) | Oui, garantie totale |
| Rollback automatique en cas d'erreur applicative | Non, les autres commandes s'exécutent quand même |
| Atomicité au sens strict (tout ou rien) | Partielle : l'exécution est ininterrompue, mais pas annulable |
Il reste un problème que MULTI/EXEC seul ne résout pas : lire avant d'écrire, en toute sécurité
Le transfert bancaire doit d'abord LIRE le solde du compte source pour vérifier qu'il est suffisant, avant de décider s'il faut ou non décrémenter. Or, entre cette lecture et l'écriture qui suit, un autre client pourrait modifier le solde — c'est exactement le problème que WATCH résout, en surveillant une clé et en annulant automatiquement la transaction si elle a changé entre-temps.
Prérequis
Cette leçon suppose que tu es à l'aise avec les opérations atomiques simples comme INCR/DECRBY (leçon 2) : MULTI/EXEC/WATCH s'appuient sur les mêmes commandes de base, combinées différemment.
Ensuite, le pattern watch/retry devient le standard pour ce genre de situation
Le client surveille une clé (WATCH), lit sa valeur, décide de la logique à appliquer, puis tente la transaction (MULTI...EXEC). Si EXEC échoue (renvoie nil) parce que la clé surveillée a changé entre-temps, le client reboucle simplement et retente depuis le début — c'est du verrouillage optimiste, qui suppose que les conflits restent rares et évite le coût d'un verrou explicite dans le cas courant.
Piège fréquent
Oublier de gérer la boucle de retry après un échec d'EXEC cause des transferts silencieusement ignorés : si EXEC renvoie nil et que le code applicatif ne vérifie pas ce cas, l'opération semble avoir "réussi" côté client alors qu'elle n'a jamais été appliquée côté serveur.
Bonne pratique
Pour une logique conditionnelle plus complexe qu'un simple lire-puis-écrire (comme un plafond d'incrément, une validation métier avant écriture), un script Lua atomique — sujet de la prochaine leçon — est souvent plus simple et plus robuste qu'un pattern WATCH/retry, car il s'exécute entièrement côté serveur en une seule opération.
Maintenant que tu maîtrises les transactions classiques, la prochaine leçon va plus loin avec le scripting Lua, qui permet une logique conditionnelle atomique impossible à exprimer avec MULTI/EXEC seul.
Commandes & code
Transactions : MULTI, EXEC, WATCH
# MULTI/EXEC : met en file les commandes, puis les exécute toutes d'un coup, atomiquement
MULTI
SET compte:A 100
DECRBY compte:A 30
INCRBY compte:B 30
EXEC # exécute tout séquentiellement, sans interruption possible par un autre client
# Annuler une transaction avant EXEC
MULTI
SET temp "valeur"
DISCARD # annule tout, rien n'est exécuté
# WATCH : verrou optimiste sur une ou plusieurs clés
WATCH compte:A # surveille compte:A
MULTI
DECRBY compte:A 50
EXEC # échoue (renvoie nil) si compte:A a été modifié par un AUTRE client entre WATCH et EXECimport redis
r = redis.Redis(decode_responses=True)
def transfert_atomique(source: str, dest: str, montant: int) -> bool:
with r.pipeline() as pipe:
while True:
try:
pipe.watch(source) # verrou optimiste
solde = int(pipe.get(source) or 0)
if solde < montant:
pipe.unwatch()
return False # solde insuffisant, on annule
pipe.multi() # bascule en mode transaction
pipe.decrby(source, montant)
pipe.incrby(dest, montant)
pipe.execute() # lève WatchError si "source" a changé entre-temps
return True
except redis.WatchError:
continue # quelqu'un a modifié la clé : on retente (optimistic locking)# Ce que MULTI/EXEC N'EST PAS :
# - Pas de rollback : si une commande échoue à l'exécution (erreur de type par ex.),
# les AUTRES commandes de la transaction s'exécutent quand même.
# - Isolation par rapport aux autres clients : oui, aucune commande ne s'intercale entre MULTI et EXEC.Résumé
MULTI/EXECgarantit l'exécution atomique et isolée d'un lot de commandes, sans rollback en cas d'erreur applicative.WATCHimplémente du verrouillage optimiste :EXECéchoue si une clé surveillée a changé entre-temps.- Le pattern watch/retry en boucle est standard pour des mises à jour concurrentes sûres sans lock explicite.
Exercices pratiques
Mission : corriger un transfert bancaire qui ignore silencieusement les échecs
Objectif : Sécuriser un transfert entre deux comptes avec MULTI/EXEC et WATCH, et corriger un bug où un échec de transaction passe inaperçu.
Contexte
Un service de transfert entre comptes utilise WATCH/MULTI/EXEC, mais ne vérifie jamais la valeur renvoyée par EXEC. Sous forte concurrence, des virements semblent avoir réussi côté application alors qu'aucun montant n'a réellement bougé côté Redis. Tu dois comprendre puis corriger ce bug, et clarifier une confusion fréquente sur ce que MULTI/EXEC garantit réellement.