Retour au cours

data / redis

Transactions : MULTI, EXEC, WATCH

Leçon 81 exercice

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 WATCH pour 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.

GarantieMULTI/EXEC la fournit-elle ?
Isolation (pas d'interférence d'un autre client)Oui, garantie totale
Rollback automatique en cas d'erreur applicativeNon, 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

bash
# 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 EXEC
python
import 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)
bash
# 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/EXEC garantit l'exécution atomique et isolée d'un lot de commandes, sans rollback en cas d'erreur applicative.
  • WATCH implé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

1 disponible
1

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.

Résoudre l’exercice →