Retour au cours

data / redis

Structures avancées : HyperLogLog, bitmaps, Streams

Leçon 141 exercice

Explication

Ce que vous allez apprendre

  • Compter des éléments uniques à très grande échelle avec HyperLogLog
  • Stocker des millions de flags booléens de façon extrêmement compacte avec les bitmaps
  • Modéliser un flux d'événements durable et rejouable avec les Streams
  • Faire coopérer plusieurs workers sur un même Stream via les consumer groups
  • Choisir la bonne structure selon le besoin métier réel

Dans quel contexte ?

Une équipe analytics veut savoir combien de visiteurs uniques ont consulté un site chaque jour, sur un trafic de plusieurs dizaines de millions de visites quotidiennes. Stocker chaque identifiant de visiteur dans un SET classique fonctionnerait, mais consommerait des centaines de mégaoctets par jour. HyperLogLog résout précisément ce problème en offrant une estimation du nombre d'éléments uniques avec une mémoire quasi constante, quelle que soit l'échelle.

D'abord, HyperLogLog : compter sans stocker

PFADD ajoute des éléments à une structure HyperLogLog, et PFCOUNT renvoie une estimation du nombre d'éléments uniques ajoutés — un doublon n'augmente jamais le compte. La contrepartie de cette efficacité mémoire est une petite marge d'erreur, d'environ 0,81 % dans le cas de Redis, largement acceptable pour de l'analytics.

Prérequis

Cette leçon suppose que tu connais déjà les sets Redis classiques (SADD, SCARD), pour bien saisir en quoi HyperLogLog s'en distingue par son compromis mémoire/précision.

StructureCas d'usageCoût mémoire
HyperLogLogCompter des éléments uniques approximativementQuasi constant (~12 Ko max)
BitmapFlags booléens massifs indexés par ID1 bit par élément
StreamJournal d'événements ordonné et rejouableProportionnel au nombre d'événements conservés

Une fois ce principe posé, un autre besoin très différent : les flags booléens massifs

Les bitmaps stockent un bit par position, adressable directement par un identifiant numérique. SETBIT utilisateurs:actifs:2026-09-04 1000 1 marque l'utilisateur d'ID 1000 comme actif ce jour-là, et BITCOUNT compte instantanément le nombre total de bits à 1 — soit le nombre d'utilisateurs actifs. BITOP AND permet même de croiser deux bitmaps, par exemple pour trouver les utilisateurs actifs deux jours de suite.

Piège courant

Une bitmap est indexée par position, pas par valeur : si les identifiants utilisateurs sont très dispersés (par exemple des UUID), une bitmap directe sur ces IDs bruts serait absurdement volumineuse. Les bitmaps ne sont efficaces que sur des identifiants numériques denses et bornés.

Il reste un dernier besoin, très différent des deux précédents : la messagerie durable

Contrairement au Pub/Sub classique de Redis qui perd instantanément tout message si aucun abonné n'écoute au moment de l'envoi, un Stream conserve les événements de façon persistante et ordonnée, avec un identifiant unique par entrée. Un consommateur peut ainsi relire l'historique, reprendre après une déconnexion, ou même retraiter un message.

Les consumer groups poussent ce concept plus loin : plusieurs workers peuvent se répartir la charge d'un même Stream sans jamais retraiter deux fois le même message, grâce à XREADGROUP et à l'accusé de réception XACK. Si un worker tombe en panne avant d'acquitter un message, XPENDING révèle ce message en attente et XCLAIM permet à un autre worker de le récupérer.

Bonne pratique

Utilise systématiquement XACK après avoir traité un message avec succès dans un consumer group. Sans cet accusé de réception, le message reste indéfiniment marqué comme "en cours de traitement" (pending), ce qui empêche de savoir s'il a réellement été traité ou perdu.

Après ces structures de données spécialisées, la prochaine leçon change de registre en abordant un sujet tout aussi critique en production : sécuriser un déploiement Redis avec les ACL et le chiffrement TLS.

Commandes & code

Structures avancées : HyperLogLog, bitmaps, Streams

bash
# --- HyperLogLog : compter des éléments UNIQUES avec une mémoire quasi constante ---
# Erreur ~0.81%, mais une clé HLL ne dépasse jamais ~12 Ko, même pour des milliards d'éléments
PFADD visiteurs:2026-09-04 "user1" "user2" "user3"
PFCOUNT visiteurs:2026-09-04                       # estimation du nombre d'éléments uniques
PFADD visiteurs:2026-09-04 "user1"                  # doublon, n'augmente pas le compte
PFMERGE visiteurs:semaine visiteurs:2026-09-01 visiteurs:2026-09-02   # fusionne plusieurs HLL

# --- Bitmaps : opérations bit à bit sur une chaîne, indexées par position ---
SETBIT utilisateurs:actifs:2026-09-04 1000 1        # marque l'utilisateur d'ID 1000 comme actif ce jour
GETBIT utilisateurs:actifs:2026-09-04 1000          # -> 1
BITCOUNT utilisateurs:actifs:2026-09-04             # nombre total de bits à 1 = nb d'utilisateurs actifs
BITOP AND resultat utilisateurs:actifs:2026-09-03 utilisateurs:actifs:2026-09-04   # actifs les 2 jours
BITPOS utilisateurs:actifs:2026-09-04 1             # position du premier bit à 1

# --- Streams : log append-only, avec IDs ordonnés, consumer groups ---
XADD stream:evenements '*' type "login" user_id "42"    # '*' = ID auto-généré (timestamp-séquence)
XLEN stream:evenements
XRANGE stream:evenements - +                              # tous les événements, du plus vieux au plus récent
XREVRANGE stream:evenements + - COUNT 10                  # les 10 plus récents

# Lecture simple depuis un point donné
XREAD COUNT 10 STREAMS stream:evenements 0               # depuis le début
XREAD BLOCK 5000 STREAMS stream:evenements $              # bloque 5s, attend un nouvel événement

# Consumer groups : plusieurs workers se partagent le stream SANS retraiter le même message
XGROUP CREATE stream:evenements groupe_workers 0
XREADGROUP GROUP groupe_workers worker-1 COUNT 10 STREAMS stream:evenements >
XACK stream:evenements groupe_workers 1725000000000-0     # accuse réception d'un message traité
XPENDING stream:evenements groupe_workers                 # messages livrés mais pas encore ACK
XCLAIM stream:evenements groupe_workers worker-2 60000 1725000000000-0   # récupère un message bloqué chez worker-1
python
import redis

r = redis.Redis(decode_responses=True)

# Worker consommant un stream via consumer group (pattern queue durable et rejouable)
def worker(nom_worker: str):
    r.xgroup_create("stream:evenements", "groupe_workers", id="0", mkstream=True)
    while True:
        messages = r.xreadgroup(
            "groupe_workers", nom_worker, {"stream:evenements": ">"}, count=10, block=5000
        )
        for stream_name, entries in messages or []:
            for msg_id, fields in entries:
                traiter(fields)
                r.xack("stream:evenements", "groupe_workers", msg_id)   # confirme le traitement

def traiter(fields: dict) -> None:
    print("événement:", fields)

Résumé

  • HyperLogLog compte des éléments uniques avec une mémoire quasi constante, au prix d'une petite erreur d'estimation.
  • Les bitmaps sont extrêmement compacts pour des flags booléens massifs (ex : utilisateurs actifs par jour).
  • Les Streams offrent une messagerie durable et rejouable avec consumer groups, contrairement au Pub/Sub classique.

Exercices pratiques

1 disponible
1

Mission : compter des millions de visiteurs uniques sans faire exploser la mémoire

Objectif : Choisir HyperLogLog pour un comptage d'uniques à grande échelle, croiser des bitmaps d'activité, et récupérer un message de Stream resté bloqué après le crash d'un worker.

Contexte

L'équipe analytics veut suivre les visiteurs uniques quotidiens sur un site à très fort trafic, l'équipe sécurité veut croiser des bitmaps d'utilisateurs actifs, et l'équipe ops fait tourner une file de traitement via un Stream dont un worker vient de crasher sans accuser réception de son dernier message. Tu dois répondre aux trois besoins.

Résoudre l’exercice →