Retour au cours

data / redis

Expiration et TTL

Leçon 61 exercice

Explication

Ce que vous allez apprendre

  • Définir une durée de vie sur une clé avec EX/PX, et la lire avec TTL
  • Comprendre la différence entre une expiration relative et une expiration à un instant précis
  • Utiliser SET ... NX EX comme base d'un verrou distribué simple et auto-libérable
  • Lire et modifier le TTL et la valeur d'une clé en une seule opération atomique avec GETEX
  • Sécuriser la libération d'un verrou avec un script Lua qui vérifie son propriétaire

Dans quel contexte ?

Une application e-commerce stocke la session d'un utilisateur connecté dans Redis, avec une expiration automatique après une heure d'inactivité. Sans TTL, il faudrait un processus de nettoyage périodique séparé pour supprimer les vieilles sessions ; avec un TTL natif, Redis s'en charge lui-même, sans code applicatif supplémentaire.

D'abord, l'idée de base : une clé qui s'auto-détruit

SET session:abc123 "user_data" EX 3600 stocke une valeur ET programme sa suppression automatique dans 3600 secondes (1 heure), en une seule commande. Passé ce délai, la clé disparaît toute seule, sans qu'aucun processus externe n'ait besoin de la supprimer explicitement.

Une fois cette mécanique comprise, deux façons d'exprimer une expiration existent

Une expiration relative (EX, PX, ou EXPIRE appliqué après coup) compte un nombre de secondes ou de millisecondes à partir de maintenant. Une expiration absolue (EXPIREAT, PEXPIREAT) fixe un instant précis dans le temps, sous forme de timestamp Unix — utile par exemple pour synchroniser l'expiration d'une donnée avec un événement métier précis (la fin d'une offre promotionnelle à minuit).

CommandeType d'expirationExemple d'usage
EX / EXPIRERelative, en secondesSession utilisateur (1h après la dernière activité)
PX / PEXPIRERelative, en millisecondesVerrou de courte durée, précision fine
EXPIREAT / PEXPIREATAbsolue, timestamp UnixExpiration synchronisée à un instant métier précis

Il reste un usage particulièrement intéressant de ce mécanisme : le verrou distribué

SET lock:job:42 "worker-1" NX EX 30 combine deux garanties en une seule opération atomique : NX garantit que la clé n'est créée que si elle n'existe pas déjà (donc qu'un seul client peut "gagner" le verrou), et EX 30 garantit que ce verrou se libère automatiquement après 30 secondes, même si le processus qui l'a posé crashe avant de le libérer explicitement.

Prérequis

Cette leçon suppose que tu es à l'aise avec SET (leçon 2) : les options NX, XX, EX, PX sont des extensions de cette même commande de base.

Ensuite, un problème de sécurité se pose : qui a le droit de libérer un verrou ?

Un processus A pose un verrou, mais si son traitement dure plus longtemps que prévu, le TTL peut expirer et un processus B pose alors le même verrou. Si A termine enfin et supprime "son" verrou sans vérifier, il supprimerait en réalité le verrou légitime de B. La solution consiste à associer un jeton unique à chaque verrou et à vérifier ce jeton avant toute suppression — une opération qui doit elle-même être atomique, d'où le recours à un script Lua (détaillé dans une leçon ultérieure).

Piège fréquent

Poser un verrou sans jamais lui donner de TTL, en pensant le libérer "toujours proprement" à la fin du traitement, est une source classique de blocages définitifs en production : si le processus crashe entre la pose du verrou et sa libération explicite, plus aucun autre processus ne peut jamais l'obtenir. Un verrou sans expiration automatique est un verrou dangereux.

Bonne pratique

Utilise GETEX quand tu as besoin de lire une valeur ET de rafraîchir son TTL en même temps (par exemple prolonger une session à chaque activité de l'utilisateur) : cette opération atomique évite une course entre un GET et un EXPIRE séparés, où un autre client pourrait intervenir entre les deux.

Maintenant que tu maîtrises l'expiration des clés, la prochaine leçon change de registre : le Pub/Sub, un mécanisme de messagerie en temps réel entre plusieurs clients Redis.

Commandes & code

Expiration et TTL

bash
# Définir une expiration (en secondes ou en millisecondes)
SET session:abc123 "user_data" EX 3600         # expire dans 1h (3600s)
SET session:abc123 "user_data" PX 3600000      # équivalent en millisecondes
EXPIRE session:abc123 1800                     # modifie le TTL d'une clé existante (30 min)
PEXPIRE session:abc123 1800000                 # équivalent en ms

# Expiration à un instant précis (timestamp Unix)
EXPIREAT session:abc123 1735689600
PEXPIREAT session:abc123 1735689600000

# Consulter le TTL restant
TTL session:abc123                             # secondes restantes, -1 si pas de TTL, -2 si absente
PTTL session:abc123                            # en millisecondes

# Retirer l'expiration (rendre la clé permanente)
PERSIST session:abc123

# SET conditionnel + TTL en une commande (pattern courant : cache ou lock)
SET lock:job:42 "worker-1" NX EX 30            # NX = seulement si absente ; expire après 30s (lock auto-libéré)
SET cache:page:home "<html>...</html>" XX EX 60   # XX = seulement si déjà existante

# GETEX : lit une valeur ET modifie son TTL en une seule commande atomique
GETEX session:abc123 EX 3600
GETEX session:abc123 PERSIST                   # lit et retire le TTL
python
# Pattern verrou distribué simple (à ne pas utiliser tel quel en prod critique, voir Redlock)
import redis, uuid

r = redis.Redis(decode_responses=True)

def acquire_lock(name: str, ttl: int = 30) -> str | None:
    token = str(uuid.uuid4())
    ok = r.set(f"lock:{name}", token, nx=True, ex=ttl)  # NX+EX atomique
    return token if ok else None

def release_lock(name: str, token: str) -> None:
    # Script Lua pour vérifier le token AVANT de supprimer (évite de supprimer le lock d'un autre)
    script = '''
    if redis.call("GET", KEYS[1]) == ARGV[1] then
        return redis.call("DEL", KEYS[1])
    else
        return 0
    end
    '''
    r.eval(script, 1, f"lock:{name}", token)

Résumé

  • EX/PX définissent un TTL relatif, EXPIREAT/PEXPIREAT un TTL absolu ; TTL renvoie -1 (permanente) ou -2 (absente).
  • SET ... NX EX est la base des locks distribués simples : atomique, auto-expirant en cas de crash du client.
  • Toujours valider un token propriétaire avant DEL d'un lock (via script Lua) pour éviter de libérer le lock d'un autre processus.

Exercices pratiques

1 disponible
1

Mission : empêcher un job de supprimer le verrou d'un autre worker

Objectif : Poser un verrou distribué auto-expirant, diagnostiquer un TTL manquant, et sécuriser sa libération avec un jeton propriétaire.

Contexte

Un système de jobs utilise des verrous Redis pour qu'un seul worker traite chaque tâche à la fois. Un premier incident a bloqué toute la file pendant des heures à cause d'un verrou sans expiration ; un second incident a fait perdre le travail d'un worker légitime à cause d'une libération de verrou mal sécurisée. Tu dois corriger les deux problèmes.

Résoudre l’exercice →