Retour au cours

data / redis

Listes : files et piles

Leçon 41 exercice

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.

PatternCommandesOrdre de traitement
Pile (LIFO)LPUSH + LPOPLe dernier ajouté est traité en premier
File (FIFO)RPUSH + LPOPLe 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

bash
# 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 RIGHT
python
import 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/RPOP implémentent piles et files ; BLPOP/BRPOP évitent le polling actif.
  • LMOVE dé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

1 disponible
1

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.

Résoudre l’exercice →