data / redis
Expiration et TTL
Explication
Ce que vous allez apprendre
- Définir une durée de vie sur une clé avec
EX/PX, et la lire avecTTL - Comprendre la différence entre une expiration relative et une expiration à un instant précis
- Utiliser
SET ... NX EXcomme 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).
| Commande | Type d'expiration | Exemple d'usage |
|---|---|---|
EX / EXPIRE | Relative, en secondes | Session utilisateur (1h après la dernière activité) |
PX / PEXPIRE | Relative, en millisecondes | Verrou de courte durée, précision fine |
EXPIREAT / PEXPIREAT | Absolue, timestamp Unix | Expiration 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
# 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# 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/PXdéfinissent un TTL relatif,EXPIREAT/PEXPIREATun TTL absolu ;TTLrenvoie -1 (permanente) ou -2 (absente).SET ... NX EXest la base des locks distribués simples : atomique, auto-expirant en cas de crash du client.- Toujours valider un token propriétaire avant
DELd'un lock (via script Lua) pour éviter de libérer le lock d'un autre processus.
Exercices pratiques
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.