data / redis
Listes : files et piles
Explication
Ce que vous allez apprendre
- Comprendre pourquoi une liste Redis permet des push/pop en temps constant aux extrémités
- Implémenter une pile (LIFO) et une file (FIFO) avec les bonnes combinaisons de commandes
- Utiliser les opérations bloquantes (
BLPOP,BRPOP) pour éviter le polling actif - Déplacer un élément atomiquement entre deux listes avec
LMOVE - Écrire un worker Python simple qui consomme une file Redis en boucle
Dans quel contexte ?
Une application de traitement d'images reçoit des demandes de redimensionnement de la part des utilisateurs, mais ce traitement est trop lent pour être fait en direct pendant la requête HTTP. La solution classique consiste à empiler chaque demande dans une file d'attente Redis, puis à laisser un ou plusieurs workers en arrière-plan la vider au fur et à mesure, indépendamment du rythme des requêtes entrantes.
D'abord, une liste Redis n'est pas un simple tableau
Techniquement, une liste Redis est implémentée comme une liste doublement chaînée : ajouter ou retirer un élément à une extrémité (début ou fin) se fait en temps constant, peu importe la taille de la liste. C'est cette propriété qui rend les listes adaptées aux patterns de file d'attente, où l'on ajoute constamment d'un côté et retire constamment de l'autre.
Une fois cette structure comprise, deux patterns classiques en découlent naturellement
Une pile (LIFO, "dernier entré, premier sorti") s'obtient en poussant et en retirant du même côté : LPUSH puis LPOP. Une file (FIFO, "premier entré, premier sorti"), le pattern de loin le plus courant pour une queue de jobs, s'obtient en poussant d'un côté et en retirant de l'autre : RPUSH pour ajouter, LPOP pour retirer dans l'ordre d'arrivée.
| Pattern | Commandes | Ordre de traitement |
|---|---|---|
| Pile (LIFO) | LPUSH + LPOP | Le dernier ajouté est traité en premier |
| File (FIFO) | RPUSH + LPOP | Le premier ajouté est traité en premier |
Il reste un problème pratique : comment un worker sait-il qu'un nouveau job est arrivé ?
Sans mécanisme dédié, un worker devrait interroger la liste en boucle (LPOP répété), ce qu'on appelle le polling actif : ça gaspille du CPU même quand la file est vide. BLPOP queue:jobs 5 résout ce problème en bloquant la connexion jusqu'à ce qu'un élément arrive (ou jusqu'au timeout de 5 secondes), sans jamais consommer de ressources pendant l'attente.
Prérequis
Cette leçon suppose que tu es à l'aise avec les strings et les hashes (leçons précédentes) : les listes suivent la même logique de commandes préfixées (L pour liste), mais appliquées à une collection ordonnée.
Ensuite, un besoin de fiabilité se pose dès qu'un traitement peut échouer en cours de route
Si un worker retire un job avec LPOP puis crashe avant de le traiter, le job est définitivement perdu. LMOVE queue:jobs queue:processing LEFT RIGHT déplace atomiquement un élément d'une liste à une autre : le job reste visible dans queue:processing pendant son traitement, permettant de le retrouver et de le rejouer en cas d'échec du worker.
Piège fréquent
Utiliser une simple liste Redis pour un système de jobs qui a besoin de garanties fortes (traçabilité, plusieurs consommateurs qui ne retraitent jamais le même message, relecture après incident) atteint vite ses limites. Pour ces besoins plus avancés, les Redis Streams — abordés dans une leçon ultérieure de ce cours — offrent des garanties bien plus solides.
Bonne pratique
Utilise toujours un timeout fini sur BLPOP/BRPOP (pas 0, une attente infinie) dans un worker de production, et reboucle après chaque timeout : cela permet au worker de rester réactif à un signal d'arrêt propre (comme un SIGTERM lors d'un déploiement), plutôt que de rester bloqué indéfiniment sur une attente réseau.
Maintenant que tu maîtrises les files et les piles, la prochaine leçon explore deux structures complémentaires : les sets pour l'unicité et les opérations ensemblistes, et les sorted sets pour les classements ordonnés par score.
Commandes & code
Listes : files et piles
# Une liste Redis est une liste doublement chaînée : push/pop en O(1) aux extrémités
RPUSH taches "envoyer email" "generer rapport" # ajoute à droite (fin)
LPUSH taches "tache urgente" # ajoute à gauche (début)
LRANGE taches 0 -1 # affiche toute la liste
# Pile (LIFO) avec LPUSH/LPOP
LPUSH pile a b c
LPOP pile # -> "c" (dernier entré)
# File (FIFO) avec RPUSH/LPOP : pattern de base pour une queue de jobs
RPUSH queue:jobs "job1" "job2" "job3"
LPOP queue:jobs # -> "job1" (premier entré, premier sorti)
# Pop bloquant : attend qu'un élément arrive (pattern worker de queue)
BLPOP queue:jobs 5 # attend jusqu'à 5s, ou renvoie nil si timeout
BRPOP queue:jobs 0 # 0 = attente infinie
# Taille, accès par index, découpe
LLEN queue:jobs
LINDEX taches 0
LTRIM taches 0 99 # ne garde que les 100 premiers éléments
# Déplacer un élément d'une liste à une autre de façon atomique (pattern "reliable queue")
LMOVE queue:jobs queue:processing LEFT RIGHTimport redis
r = redis.Redis(host="localhost", port=6379, decode_responses=True)
# Worker simple consommant une file Redis en boucle bloquante
def worker():
while True:
item = r.blpop("queue:jobs", timeout=5) # bloque jusqu'à 5s
if item is None:
continue # timeout, on reboucle
_, payload = item
traiter(payload) # traitement métier
def traiter(payload: str) -> None:
print(f"traitement de {payload}")Résumé
LPUSH/RPUSH+LPOP/RPOPimplémentent piles et files ;BLPOP/BRPOPévitent le polling actif.LMOVEdéplace un élément atomiquement entre deux listes : utile pour des files "processing" fiables.- Les listes sont adaptées aux petites/moyennes files ; pour des volumes massifs, préférer les Streams (voir plus loin).
Exercices pratiques
Mission : fiabiliser une file de traitement d'images qui perd des jobs
Objectif : Remplacer un worker qui interroge la file en boucle par un worker bloquant fiable, capable de survivre à ses propres crashs sans perdre de job.
Contexte
Le worker de redimensionnement d'images consomme queue:jobs avec un LPOP répété en boucle serrée, saturant le CPU même quand la file est vide. Pire, si le worker crashe juste après avoir retiré un job, ce job est perdu définitivement. Tu dois rendre ce worker efficace et traçable pendant le traitement.