# Fichier: python_cheats/cheatsheets/redis.txt
# Cheatsheet Redis - Guide Ultra-Détaillé pour Grands Débutants


[OK] CONCEPTS FONDAMENTAUX (EXPLICATIONS TRÈS DÉTAILLÉES)

# === QU'EST-CE QUE REDIS ? ===

# Imagine que tu as une application web (site e-commerce, réseau social, etc.)
# Cette app doit stocker des données :
# - Les informations des utilisateurs
# - Les articles d'un blog
# - Les produits d'un catalogue
# - Les messages d'un chat

# Solution classique : Base de données traditionnelle (MySQL, PostgreSQL)
# Problème : Ces bases de données écrivent les données sur le DISQUE DUR
# Le disque dur est LENT (même les SSD)
# Quand 1000 utilisateurs demandent la même info en même temps = LENT!

# REDIS = Solution différente!
# = Base de données qui stocke TOUT EN MÉMOIRE (RAM)
# La RAM est 100x plus rapide que le disque!

# Analogie simple:
# Base de données classique = Bibliothèque
#   - Tu dois aller chercher un livre dans les étagères (lent)
#   - Tu dois le remettre à sa place après (lent)
# Redis = Ton bureau
#   - Tous les livres dont tu as besoin sont déjà sur ton bureau (rapide!)
#   - Tu peux les attraper instantanément

# REDIS signifie: REmote DIctionary Server
# = Serveur de dictionnaire distant
# = Fonctionne comme un dictionnaire Python géant accessible via le réseau


# === POURQUOI REDIS EST INCROYABLEMENT RAPIDE? ===

# 1. STOCKAGE EN MÉMOIRE (RAM)
# La RAM est ultra-rapide comparée au disque
# Temps d'accès:
#   - RAM: 100 nanosecondes (0.0000001 seconde)
#   - SSD: 100 microseconds (0.0001 seconde)
#   - HDD: 10 milliseconds (0.01 seconde)
# Redis peut traiter 100,000 opérations par seconde!

# 2. STRUCTURE SIMPLE
# Redis utilise des structures de données simples et optimisées
# Pas de JOIN complexes, pas de requêtes SQL compliquées
# = Opérations ultra-rapides

# 3. SINGLE-THREADED
# Redis fonctionne avec un seul thread (processus)
# Pourquoi c'est rapide? Pas de temps perdu à synchroniser plusieurs threads!
# Toutes les opérations sont atomiques (pas de conflit)


# === VOCABULAIRE REDIS (TRÈS IMPORTANT!) ===

# KEY (Clé)
# = Un identifiant unique pour accéder à une valeur
# Comme une clé de dictionnaire Python
# Exemples:
#   - "user:1000" -> informations de l'utilisateur 1000
#   - "session:abc123" -> données de session
#   - "counter:visits" -> compteur de visites
# Règles:
#   - Les clés sont des chaînes de caractères
#   - Sensibles à la casse: "User" ≠ "user"
#   - Peuvent contenir n'importe quel caractère
#   - Convention: utiliser ":" pour hiérarchie

# VALUE (Valeur)
# = Les données associées à une clé
# Types de valeurs possibles:
#   - String: texte ou nombres
#   - List: liste ordonnée
#   - Set: ensemble non ordonné sans doublons
#   - Hash: dictionnaire (clé-valeur imbriqué)
#   - Sorted Set: ensemble ordonné avec score
# = Redis supporte plusieurs types de structures!

# TTL (Time To Live)
# = Durée de vie d'une clé en secondes
# Après ce délai, Redis SUPPRIME automatiquement la clé
# Très utile pour:
#   - Sessions utilisateur (expiration après 1h d'inactivité)
#   - Cache de données (expiration après 5 minutes)
#   - Codes de vérification (expiration après 10 minutes)
# Exemple:
#   SET "code:abc" "123456" EX 600
#   # Cette clé disparaît automatiquement après 600 secondes (10 min)

# PERSISTENCE (Persistance)
# = Mécanisme pour sauvegarder les données de la RAM vers le disque
# Redis propose 2 méthodes:
#   1. RDB (Redis Database Backup)
#      - Snapshot complet à intervalles réguliers
#      - Fichier binaire .rdb
#   2. AOF (Append Only File)
#      - Log de toutes les opérations d'écriture
#      - Fichier texte .aof
# Pourquoi? Si Redis redémarre, il recharge les données depuis le disque!

# EVICTION (Éviction)
# = Politique de suppression automatique quand la mémoire est pleine
# Redis a une limite de mémoire (ex: 256MB, 1GB, etc.)
# Quand la limite est atteinte, Redis doit supprimer des clés
# Politiques possibles:
#   - noeviction: refuse les nouvelles écritures (erreur)
#   - allkeys-lru: supprime les clés les moins récemment utilisées
#   - volatile-lru: supprime les clés avec TTL les moins utilisées
#   - allkeys-random: supprime des clés aléatoirement
# = Redis gère automatiquement la mémoire!

# ATOMIC OPERATIONS (Opérations atomiques)
# = Opération qui s'exécute complètement ou pas du tout
# Pas d'état intermédiaire!
# Exemple:
#   INCR "counter"
#   # Incrémente le compteur de manière atomique
#   # Même si 1000 utilisateurs font INCR en même temps,
#   # le résultat sera correct (pas de conflit)
# Pourquoi important? Évite les problèmes de concurrence!


# === CAS D'USAGE DE REDIS ===

# 1. CACHE (Le plus courant!)
# Problème: Ta base de données SQL est lente
# Solution: Mettre les données fréquentes dans Redis
# Exemple:
#   - Page d'accueil avec les articles populaires
#   - Au lieu de lire la DB SQL 10,000 fois/seconde
#   - Tu lis Redis (ultra-rapide!)
#   - Tu mets à jour le cache toutes les 5 minutes
# Résultat: Site 100x plus rapide!

# 2. SESSIONS UTILISATEUR
# Problème: Stocker les sessions dans la DB SQL = lent
# Solution: Stocker dans Redis avec TTL automatique
# Exemple:
#   - Utilisateur se connecte
#   - Redis stocke: "session:abc123" -> {"user_id": 42, "username": "alice"}
#   - TTL = 1 heure (session expire après 1h d'inactivité)
#   - Chaque page charge: super rapide!

# 3. FILE D'ATTENTE (Queue)
# Problème: Traiter des tâches en arrière-plan
# Solution: Redis comme file d'attente
# Exemple:
#   - Un utilisateur upload une vidéo
#   - L'app ajoute la tâche dans une liste Redis
#   - Un worker (processus séparé) lit la liste et traite la vidéo
#   - Pattern: LPUSH pour ajouter, BRPOP pour lire (bloquant)

# 4. PUB/SUB (Publication/Souscription)
# Problème: Notifier plusieurs services en temps réel
# Solution: Redis Pub/Sub
# Exemple:
#   - Un utilisateur poste un message dans un chat
#   - Redis PUBLISH "chat:room1" "Hello!"
#   - Tous les utilisateurs SUBSCRIBE à "chat:room1" reçoivent le message
#   - Temps réel, sans polling!

# 5. LEADERBOARD (Classement)
# Problème: Afficher le top 10 des joueurs
# Solution: Redis Sorted Set
# Exemple:
#   - Chaque joueur a un score
#   - ZADD "leaderboard" 1500 "alice"
#   - ZADD "leaderboard" 2000 "bob"
#   - ZREVRANGE "leaderboard" 0 9 -> Top 10 instantané!
#   - Ultra-rapide même avec des millions de joueurs

# 6. RATE LIMITING (Limitation de débit)
# Problème: Empêcher les abus (spam, brute force)
# Solution: Compter les requêtes avec Redis
# Exemple:
#   - Utilisateur fait une requête API
#   - INCR "rate:user123:20240115:14:30"
#   - Si compteur > 100 -> Bloquer
#   - TTL = 60 secondes (compteur se réinitialise)

# 7. DISTRIBUTED LOCKS (Verrous distribués)
# Problème: Coordonner plusieurs serveurs
# Solution: Redis comme verrou partagé
# Exemple:
#   - Plusieurs serveurs veulent traiter la même tâche
#   - SET "lock:task123" "server1" NX EX 10
#   # NX = set only if Not eXists
#   # EX 10 = expire after 10 seconds
#   - Si SET réussit -> le serveur a le verrou
#   - Autres serveurs attendent ou passent à autre chose


# === COMMENT ÇA MARCHE? (ARCHITECTURE) ===

# ARCHITECTURE CLIENT-SERVER
# Redis fonctionne comme un serveur
# Ton application Python = client
# Communication via TCP/IP (réseau)

# Flux complet:
# 1. Tu installes Redis sur un serveur (ou localement)
# 2. Redis démarre et écoute sur un port (par défaut: 6379)
# 3. Ton app Python se connecte à Redis via une bibliothèque (redis-py)
# 4. Ton app envoie des commandes: SET, GET, etc.
# 5. Redis exécute la commande et renvoie le résultat
# 6. Tout se passe en mémoire = ultra-rapide!

# Exemple simple:
# Python:
#   import redis
#   r = redis.Redis(host='localhost', port=6379)
#   r.set('name', 'Alice')  # Envoie commande SET à Redis
#   value = r.get('name')    # Envoie commande GET à Redis
#   print(value)             # b'Alice'

# Ce qui se passe:
# 1. redis.Redis() crée une connexion TCP vers localhost:6379
# 2. r.set() envoie la commande: SET name Alice
# 3. Redis stocke en RAM: {'name': 'Alice'}
# 4. r.get() envoie: GET name
# 5. Redis renvoie: 'Alice'
# 6. Python reçoit la réponse


# === REDIS VS AUTRES BASES DE DONNÉES ===

# Redis vs MySQL/PostgreSQL:
# Redis:
#   - En mémoire (RAM) = ultra-rapide
#   - Pas de schéma fixe, flexibilité totale
#   - Pas de SQL, commandes simples
#   - Limité par la RAM disponible
#   - Parfait pour cache, sessions, temps réel
# MySQL:
#   - Sur disque = plus lent mais persistent par défaut
#   - Schéma fixe (tables, colonnes)
#   - Requêtes SQL complexes possibles (JOIN, etc.)
#   - Capacité illimitée (disque dur)
#   - Parfait pour données structurées et relationnelles

# Redis vs Memcached:
# Redis:
#   - Support de plusieurs types de données (String, List, Set, etc.)
#   - Persistence possible (RDB, AOF)
#   - Pub/Sub, transactions, Lua scripts
#   - Plus de fonctionnalités
# Memcached:
#   - Seulement clé-valeur simple
#   - Pas de persistence
#   - Plus simple, légèrement plus rapide pour cache pur
#   - Moins de fonctionnalités

# Redis vs MongoDB:
# Redis:
#   - En mémoire, ultra-rapide
#   - Structures de données spécialisées
#   - Pas de requêtes complexes
# MongoDB:
#   - Sur disque, plus lent
#   - Documents JSON flexibles
#   - Requêtes complexes possibles (aggregation, etc.)


[OK] INSTALLATION SUPER DÉTAILLÉE

# === INSTALLATION SELON TON OS ===

# ========== LINUX (UBUNTU/DEBIAN) ==========

# Méthode 1: Via APT (facile, recommandé pour débutants)

# Mettre à jour les paquets:
sudo apt update

# Installer Redis:
sudo apt install redis-server -y

# Vérifier l'installation:
redis-server --version
# Affiche: Redis server v=6.0.16 sha=00000000:0 malloc=jemalloc-5.2.1 bits=64 build=...

# Démarrer Redis:
sudo systemctl start redis-server

# Activer au démarrage:
sudo systemctl enable redis-server

# Vérifier que Redis tourne:
sudo systemctl status redis-server
# Affiche: Active: active (running)

# Tester la connexion:
redis-cli ping
# Affiche: PONG
# (Si tu vois PONG, Redis fonctionne!)


# Méthode 2: Compiler depuis les sources (avancé)

# Installer les dépendances:
sudo apt install build-essential tcl -y

# Télécharger Redis:
cd /tmp
wget https://download.redis.io/releases/redis-7.2.4.tar.gz

# Extraire:
tar xzf redis-7.2.4.tar.gz
cd redis-7.2.4

# Compiler:
make
make test  # Optionnel: lance les tests (prend du temps)

# Installer:
sudo make install

# Vérifier:
redis-server --version


# ========== MACOS ==========

# Méthode 1: Avec Homebrew (recommandé)

# Si tu n'as pas Homebrew:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Installer Redis:
brew install redis

# Vérifier:
redis-server --version

# Démarrer Redis comme service:
brew services start redis
# Redis démarre automatiquement au démarrage de macOS

# Vérifier que Redis tourne:
brew services list
# Affiche: redis started

# Tester:
redis-cli ping
# Affiche: PONG


# Méthode 2: Sans Homebrew (rare)

# Télécharger depuis redis.io et compiler comme sur Linux


# ========== WINDOWS ==========

# Redis n'est PAS officiellement supporté sur Windows!
# Solutions:

# Solution 1: WSL2 (Windows Subsystem for Linux) - Recommandé

# Activer WSL2:
# 1. Ouvre PowerShell en administrateur
# 2. Tape: wsl --install
# 3. Redémarre l'ordinateur
# 4. Ouvre "Ubuntu" depuis le menu Démarrer

# Dans Ubuntu WSL:
sudo apt update
sudo apt install redis-server -y
redis-server --version

# Démarrer Redis:
redis-server &
# & = lance en arrière-plan

# Tester:
redis-cli ping


# Solution 2: Docker (facile, multi-OS)

# Installer Docker Desktop pour Windows:
# https://www.docker.com/products/docker-desktop

# Une fois Docker installé:
docker run -d --name redis-server -p 6379:6379 redis:latest

# Explications:
# -d = détaché (arrière-plan)
# --name redis-server = nom du container
# -p 6379:6379 = mappe le port 6379 du container vers ton ordinateur
# redis:latest = image officielle Redis

# Tester:
docker exec -it redis-server redis-cli ping
# Affiche: PONG


# Solution 3: Redis Installer pour Windows (ancien, non recommandé)

# Télécharger depuis:
# https://github.com/microsoftarchive/redis/releases

# ATTENTION: Version très ancienne (3.x), pas maintenue!
# Utilise WSL2 ou Docker à la place


# ========== DOCKER (MULTI-OS) ==========

# Docker est la méthode UNIVERSELLE pour tous les OS!

# Installer Docker:
# - Linux: https://docs.docker.com/engine/install/ubuntu/
# - macOS: https://docs.docker.com/desktop/install/mac-install/
# - Windows: https://docs.docker.com/desktop/install/windows-install/

# Lancer Redis:
docker run -d --name my-redis -p 6379:6379 redis:latest

# Vérifier que le container tourne:
docker ps
# Affiche: CONTAINER ID   IMAGE   ...   my-redis

# Se connecter à Redis:
docker exec -it my-redis redis-cli

# Tu es maintenant dans le CLI Redis!
# Tape: PING
# Affiche: PONG

# Quitter: Ctrl+D ou exit


# Avec persistance (sauvegarder les données):
docker run -d --name my-redis \
  -p 6379:6379 \
  -v redis-data:/data \
  redis:latest redis-server --appendonly yes

# Explications:
# -v redis-data:/data = volume Docker pour persister les données
# --appendonly yes = active AOF (persistence)


[OK] CONFIGURATION DE BASE

# === FICHIER DE CONFIGURATION REDIS ===

# Redis utilise un fichier redis.conf pour sa configuration
# Localisation par défaut:
# - Linux: /etc/redis/redis.conf
# - macOS (Homebrew): /opt/homebrew/etc/redis.conf
# - Compilation manuelle: /etc/redis/redis.conf ou /usr/local/etc/redis.conf

# Voir le fichier:
cat /etc/redis/redis.conf

# Le fichier est TRÈS LONG (1000+ lignes)
# Voici les paramètres les plus importants:


# === PARAMÈTRES ESSENTIELS ===

# 1. BIND (Adresse d'écoute)
# Par défaut: bind 127.0.0.1 ::1
# Signifie: Redis écoute seulement sur localhost
# Pour accepter les connexions externes:
bind 0.0.0.0
# ATTENTION: Dangereux sans mot de passe!

# 2. PORT (Port d'écoute)
# Par défaut: port 6379
# Changer si nécessaire:
port 6379

# 3. REQUIREPASS (Mot de passe)
# Par défaut: pas de mot de passe (dangereux!)
# Ajouter un mot de passe:
requirepass my_super_secret_password_change_this

# Pour se connecter avec mot de passe:
redis-cli -a my_super_secret_password_change_this
# Ou dans le CLI:
AUTH my_super_secret_password_change_this

# 4. MAXMEMORY (Limite de mémoire)
# Par défaut: pas de limite (utilise toute la RAM!)
# Définir une limite (ex: 256MB):
maxmemory 256mb

# Politique quand la limite est atteinte:
maxmemory-policy allkeys-lru
# Options:
# - noeviction: refuse les écritures (erreur)
# - allkeys-lru: supprime les clés les moins récemment utilisées
# - volatile-lru: supprime les clés avec TTL les moins utilisées
# - allkeys-random: supprime aléatoirement
# - volatile-random: supprime aléatoirement parmi les clés avec TTL
# - volatile-ttl: supprime les clés avec TTL le plus court

# 5. PERSISTENCE (Sauvegarde sur disque)

# RDB (Snapshot):
# Par défaut: save 900 1 (après 900 sec si 1 changement)
#            save 300 10 (après 300 sec si 10 changements)
#            save 60 10000 (après 60 sec si 10000 changements)
# Pour désactiver complètement:
save ""

# Fichier RDB:
dbfilename dump.rdb
dir /var/lib/redis

# AOF (Append Only File):
# Par défaut: appendonly no
# Activer:
appendonly yes
appendfilename "appendonly.aof"

# Politique de synchronisation:
# appendfsync always     # Chaque écriture (lent mais sûr)
# appendfsync everysec   # Toutes les secondes (compromis)
# appendfsync no         # Laisse l'OS décider (rapide mais risqué)
appendfsync everysec

# 6. DAEMONIZE (Lancer en arrière-plan)
# Par défaut: daemonize no (reste en foreground)
# Pour lancer en daemon:
daemonize yes

# Fichier PID:
pidfile /var/run/redis/redis-server.pid

# 7. LOGLEVEL (Niveau de log)
# Options: debug, verbose, notice, warning
loglevel notice

# Fichier de log:
logfile /var/log/redis/redis-server.log

# 8. DATABASES (Nombre de bases de données)
# Par défaut: 16 databases (numérotées 0-15)
databases 16

# Sélectionner une database:
# Dans CLI: SELECT 1
# En Python: r = redis.Redis(db=1)


# === MODIFIER LA CONFIGURATION ===

# Méthode 1: Éditer redis.conf

# Ouvrir l'éditeur:
sudo nano /etc/redis/redis.conf

# Faire les modifications (ex: ajouter requirepass)

# Sauvegarder: Ctrl+O, Enter
# Quitter: Ctrl+X

# Redémarrer Redis:
sudo systemctl restart redis-server

# Vérifier:
redis-cli -a my_super_secret_password_change_this ping


# Méthode 2: Commande CONFIG SET (temporaire)

# Se connecter:
redis-cli

# Modifier une option:
CONFIG SET maxmemory 512mb
CONFIG SET requirepass new_password

# Voir la configuration actuelle:
CONFIG GET maxmemory
# Affiche:
# 1) "maxmemory"
# 2) "536870912"  # 512MB en bytes

# ATTENTION: Ces changements sont perdus au redémarrage!
# Pour les rendre permanents:
CONFIG REWRITE
# Cela écrit les changements dans redis.conf


# === SÉCURITÉ ESSENTIELLE ===

# 1. Toujours définir un mot de passe!
requirepass strong_password_here

# 2. Limiter les adresses autorisées:
bind 127.0.0.1  # Seulement localhost
# Ou:
bind 192.168.1.100  # Seulement cette IP

# 3. Désactiver les commandes dangereuses:
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command CONFIG ""
# Cela DÉSACTIVE ces commandes complètement

# 4. Utiliser un firewall:
# Bloquer le port 6379 sauf pour les IPs de confiance

# 5. Chiffrement SSL/TLS (Redis 6+):
# Nécessite un certificat SSL
tls-port 6380
tls-cert-file /path/to/redis.crt
tls-key-file /path/to/redis.key
tls-ca-cert-file /path/to/ca.crt


[OK] REDIS-CLI (INTERFACE LIGNE DE COMMANDE)

# === QU'EST-CE QUE REDIS-CLI? ===

# redis-cli = Redis Command Line Interface
# = Interface en ligne de commande pour interagir avec Redis
# Installé automatiquement avec Redis
# Permet de:
# - Tester Redis
# - Déboguer
# - Gérer les données manuellement
# - Monitorer les performances


# === LANCER REDIS-CLI ===

# Se connecter à Redis local (par défaut):
redis-cli

# Se connecter avec un mot de passe:
redis-cli -a my_password

# Se connecter à un serveur distant:
redis-cli -h 192.168.1.100 -p 6379 -a password

# Se connecter à une database spécifique:
redis-cli -n 1  # Database 1


# === COMMANDES DE BASE DANS REDIS-CLI ===

# Tester la connexion:
127.0.0.1:6379> PING
PONG

# S'authentifier (si requirepass est défini):
127.0.0.1:6379> AUTH my_password
OK

# Changer de database:
127.0.0.1:6379> SELECT 1
OK
127.0.0.1:6379[1]>  # [1] indique database 1

# Voir les informations du serveur:
127.0.0.1:6379> INFO
# Affiche plein d'infos (version, uptime, mémoire, etc.)

# Voir les infos de mémoire:
127.0.0.1:6379> INFO MEMORY
# Affiche: used_memory, maxmemory, etc.

# Voir toutes les clés:
127.0.0.1:6379> KEYS *
# ATTENTION: Commande lente avec beaucoup de clés!
# Ne PAS utiliser en production!

# Voir les clés avec un pattern:
127.0.0.1:6379> KEYS user:*
1) "user:1000"
2) "user:1001"
3) "user:1002"

# Compter le nombre de clés:
127.0.0.1:6379> DBSIZE
(integer) 42

# Voir le type d'une clé:
127.0.0.1:6379> TYPE user:1000
string

# Voir le TTL d'une clé:
127.0.0.1:6379> TTL session:abc123
(integer) 3600  # 3600 secondes restantes

# Supprimer une clé:
127.0.0.1:6379> DEL user:1000
(integer) 1  # 1 = clé supprimée

# Vider TOUTE la database actuelle:
127.0.0.1:6379> FLUSHDB
OK
# DANGER: Supprime tout!

# Vider TOUTES les databases:
127.0.0.1:6379> FLUSHALL
OK
# DANGER EXTRÊME: Supprime absolument tout!

# Quitter redis-cli:
127.0.0.1:6379> EXIT
# Ou: Ctrl+D


# === COMMANDES AVANCÉES ===

# Monitorer en temps réel:
127.0.0.1:6379> MONITOR
# Affiche toutes les commandes exécutées en temps réel
# Utile pour déboguer
# Pour arrêter: Ctrl+C

# Statistiques des commandes:
127.0.0.1:6379> INFO COMMANDSTATS
# Affiche: combien de fois chaque commande a été exécutée

# Clients connectés:
127.0.0.1:6379> CLIENT LIST
# Affiche: tous les clients connectés

# Lenteur des commandes:
127.0.0.1:6379> SLOWLOG GET 10
# Affiche: les 10 commandes les plus lentes

# Sauvegarder immédiatement (snapshot RDB):
127.0.0.1:6379> SAVE
OK
# BLOQUE Redis pendant la sauvegarde (peut être long!)

# Sauvegarder en arrière-plan:
127.0.0.1:6379> BGSAVE
Background saving started
# Ne bloque pas Redis

# Réécrire le fichier AOF:
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started


# === MODE INTERACTIF VS MODE COMMAND ===

# Mode interactif (défaut):
redis-cli
127.0.0.1:6379> PING
PONG
127.0.0.1:6379> SET name Alice
OK

# Mode commande (pour scripts):
redis-cli PING
# Affiche: PONG

redis-cli SET name Alice
# Affiche: OK

redis-cli GET name
# Affiche: "Alice"

# Utile dans des scripts bash:
#!/bin/bash
result=$(redis-cli GET counter)
echo "Counter value: $result"


# === OPTIONS UTILES ===

# Exécuter une commande et quitter:
redis-cli PING

# Exécuter plusieurs commandes:
redis-cli --eval script.lua , arg1 arg2

# Exporter toutes les données:
redis-cli --rdb dump.rdb

# Importer des données:
redis-cli --pipe < data.txt

# Mode CSV (pour exporter):
redis-cli --csv KEYS '*'

# Mode raw (sans formatage):
redis-cli --raw GET name


[OK] PYTHON ET REDIS (redis-py)

# === INSTALLATION DE LA BIBLIOTHÈQUE ===

# redis-py = Bibliothèque Python officielle pour Redis
# Compatible Python 3.7+

# Installer avec pip:
pip install redis

# Vérifier l'installation:
python -c "import redis; print(redis.__version__)"
# Affiche: 5.0.1 (ou version actuelle)

# Alternative (avec support async):
pip install redis[hiredis]
# hiredis = parser C ultra-rapide


# === CONNEXION À REDIS ===

# Méthode 1: Connexion simple

import redis

# Se connecter à Redis local:
r = redis.Redis(host='localhost', port=6379, db=0)

# Tester la connexion:
response = r.ping()
print(response)  # True

# Avec mot de passe:
r = redis.Redis(
    host='localhost',
    port=6379,
    password='my_password',
    db=0
)


# Méthode 2: Connection pool (recommandé pour production)

# Un pool réutilise les connexions = plus efficace
pool = redis.ConnectionPool(
    host='localhost',
    port=6379,
    password='my_password',
    db=0,
    max_connections=10
)

r = redis.Redis(connection_pool=pool)


# Méthode 3: URL de connexion

# Format: redis://[password@]host[:port][/database]
r = redis.from_url('redis://localhost:6379/0')

# Avec mot de passe:
r = redis.from_url('redis://:my_password@localhost:6379/0')

# Redis sur serveur distant:
r = redis.from_url('redis://192.168.1.100:6379/0')


# Méthode 4: SSL/TLS (Redis sécurisé)

r = redis.Redis(
    host='redis.example.com',
    port=6380,
    ssl=True,
    ssl_cert_reqs='required',
    ssl_ca_certs='/path/to/ca.crt'
)


# === OPÉRATIONS DE BASE (STRINGS) ===

import redis

r = redis.Redis(host='localhost', port=6379, db=0)

# === SET: Définir une valeur ===
r.set('name', 'Alice')
# Retourne: True si succès

# Avec expiration (TTL):
r.set('session:abc123', 'user_data', ex=3600)
# ex=3600 -> expire après 3600 secondes (1 heure)

# Ou avec setex:
r.setex('session:abc123', 3600, 'user_data')

# Set seulement si la clé n'existe pas:
r.setnx('counter', 0)
# Retourne: True si créée, False si existe déjà

# Set avec options:
r.set('key', 'value', nx=True, ex=60)
# nx=True -> seulement si n'existe pas
# ex=60 -> expire après 60 secondes


# === GET: Récupérer une valeur ===
value = r.get('name')
print(value)  # b'Alice'

# ATTENTION: get() retourne des BYTES!
# Pour avoir une string:
value = r.get('name').decode('utf-8')
print(value)  # 'Alice'

# Ou configurer le décodage automatique:
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
value = r.get('name')
print(value)  # 'Alice' (string directement)


# === AUTRES OPÉRATIONS STRINGS ===

# GETSET: Get et set en une opération atomique
old_value = r.getset('name', 'Bob')
print(old_value)  # b'Alice'
print(r.get('name'))  # b'Bob'

# APPEND: Ajouter à la fin
r.set('message', 'Hello')
r.append('message', ' World')
print(r.get('message'))  # b'Hello World'

# STRLEN: Longueur de la string
length = r.strlen('message')
print(length)  # 11

# INCR: Incrémenter (atomique!)
r.set('counter', 0)
r.incr('counter')  # counter = 1
r.incr('counter')  # counter = 2
r.incr('counter', 5)  # counter = 7

# DECR: Décrémenter
r.decr('counter')  # counter = 6
r.decr('counter', 3)  # counter = 3

# MSET: Set multiple
r.mset({'key1': 'value1', 'key2': 'value2', 'key3': 'value3'})

# MGET: Get multiple
values = r.mget(['key1', 'key2', 'key3'])
print(values)  # [b'value1', b'value2', b'value3']


# === GESTION DES CLÉS ===

# EXISTS: Vérifier si une clé existe
exists = r.exists('name')
print(exists)  # 1 si existe, 0 sinon

# DEL: Supprimer une clé
r.delete('name')
# Ou supprimer plusieurs:
r.delete('key1', 'key2', 'key3')

# KEYS: Lister les clés (ATTENTION: lent!)
keys = r.keys('user:*')
print(keys)  # [b'user:1000', b'user:1001', ...]

# SCAN: Alternative plus sûre à KEYS
cursor = 0
while True:
    cursor, keys = r.scan(cursor, match='user:*', count=100)
    for key in keys:
        print(key)
    if cursor == 0:
        break

# EXPIRE: Définir un TTL
r.expire('session:abc', 3600)  # Expire après 1 heure

# TTL: Voir le temps restant
ttl = r.ttl('session:abc')
print(ttl)  # 3600 (ou -1 si pas de TTL, -2 si clé n'existe pas)

# PERSIST: Supprimer le TTL
r.persist('session:abc')

# RENAME: Renommer une clé
r.rename('old_key', 'new_key')

# TYPE: Voir le type de la clé
key_type = r.type('name')
print(key_type)  # b'string'


# === LISTES (LISTS) ===

# Redis List = Liste ordonnée
# Peut contenir des doublons
# Opérations aux deux extrémités (gauche/droite)

# LPUSH: Ajouter à gauche
r.lpush('tasks', 'task1')
r.lpush('tasks', 'task2', 'task3')
# Liste: ['task3', 'task2', 'task1']

# RPUSH: Ajouter à droite
r.rpush('tasks', 'task4')
# Liste: ['task3', 'task2', 'task1', 'task4']

# LRANGE: Lire une plage
tasks = r.lrange('tasks', 0, -1)
print(tasks)  # [b'task3', b'task2', b'task1', b'task4']
# 0 = début, -1 = fin

# LPOP: Retirer de gauche
task = r.lpop('tasks')
print(task)  # b'task3'

# RPOP: Retirer de droite
task = r.rpop('tasks')
print(task)  # b'task4'

# LLEN: Longueur de la liste
length = r.llen('tasks')
print(length)  # 2

# LINDEX: Accéder par index
task = r.lindex('tasks', 0)
print(task)  # b'task2'

# LSET: Modifier un élément
r.lset('tasks', 0, 'new_task')

# LTRIM: Garder seulement une plage
r.ltrim('tasks', 0, 99)  # Garder les 100 premiers éléments

# BLPOP: Pop bloquant (attend si liste vide)
task = r.blpop('tasks', timeout=5)
# Attend max 5 secondes
# Retourne: (b'tasks', b'task2') ou None si timeout


# === ENSEMBLES (SETS) ===

# Redis Set = Ensemble non ordonné sans doublons
# Opérations ensemblistes (union, intersection, différence)

# SADD: Ajouter des membres
r.sadd('tags', 'python', 'redis', 'tutorial')
r.sadd('tags', 'python')  # Ignoré (doublon)

# SMEMBERS: Lire tous les membres
tags = r.smembers('tags')
print(tags)  # {b'python', b'redis', b'tutorial'}

# SISMEMBER: Vérifier si un membre existe
exists = r.sismember('tags', 'python')
print(exists)  # True

# SCARD: Nombre de membres
count = r.scard('tags')
print(count)  # 3

# SREM: Supprimer un membre
r.srem('tags', 'tutorial')

# SPOP: Retirer un membre aléatoire
tag = r.spop('tags')
print(tag)  # b'redis' (ou autre, aléatoire)

# SRANDMEMBER: Lire un membre aléatoire (sans le retirer)
tag = r.srandmember('tags')

# Opérations ensemblistes:
r.sadd('set1', 'a', 'b', 'c')
r.sadd('set2', 'b', 'c', 'd')

# SUNION: Union
union = r.sunion('set1', 'set2')
print(union)  # {b'a', b'b', b'c', b'd'}

# SINTER: Intersection
inter = r.sinter('set1', 'set2')
print(inter)  # {b'b', b'c'}

# SDIFF: Différence
diff = r.sdiff('set1', 'set2')
print(diff)  # {b'a'}


# === HASHES (DICTIONNAIRES) ===

# Redis Hash = Dictionnaire (clé-valeur imbriqué)
# Parfait pour objets structurés

# HSET: Définir un champ
r.hset('user:1000', 'name', 'Alice')
r.hset('user:1000', 'age', 30)
r.hset('user:1000', 'email', 'alice@example.com')

# Ou définir plusieurs champs d'un coup:
r.hset('user:1000', mapping={
    'name': 'Alice',
    'age': 30,
    'email': 'alice@example.com'
})

# HGET: Lire un champ
name = r.hget('user:1000', 'name')
print(name)  # b'Alice'

# HGETALL: Lire tous les champs
user = r.hgetall('user:1000')
print(user)
# {b'name': b'Alice', b'age': b'30', b'email': b'alice@example.com'}

# HMGET: Lire plusieurs champs
fields = r.hmget('user:1000', ['name', 'email'])
print(fields)  # [b'Alice', b'alice@example.com']

# HEXISTS: Vérifier si un champ existe
exists = r.hexists('user:1000', 'name')
print(exists)  # True

# HDEL: Supprimer un champ
r.hdel('user:1000', 'age')

# HKEYS: Lister les champs
keys = r.hkeys('user:1000')
print(keys)  # [b'name', b'email']

# HVALS: Lister les valeurs
vals = r.hvals('user:1000')
print(vals)  # [b'Alice', b'alice@example.com']

# HLEN: Nombre de champs
count = r.hlen('user:1000')
print(count)  # 2

# HINCRBY: Incrémenter un champ numérique
r.hset('stats', 'views', 100)
r.hincrby('stats', 'views', 1)  # views = 101
r.hincrby('stats', 'views', 10)  # views = 111


# === SORTED SETS (ENSEMBLES TRIÉS) ===

# Redis Sorted Set = Ensemble ordonné par score
# Chaque membre a un score (float)
# Parfait pour leaderboards, classements

# ZADD: Ajouter des membres avec scores
r.zadd('leaderboard', {'alice': 1500, 'bob': 2000, 'charlie': 1800})

# ZRANGE: Lire par ordre croissant
players = r.zrange('leaderboard', 0, -1)
print(players)  # [b'alice', b'charlie', b'bob']

# ZREVRANGE: Lire par ordre décroissant (top!)
top_players = r.zrevrange('leaderboard', 0, 2)
print(top_players)  # [b'bob', b'charlie', b'alice']

# Avec scores:
top_players = r.zrevrange('leaderboard', 0, 2, withscores=True)
print(top_players)
# [(b'bob', 2000.0), (b'charlie', 1800.0), (b'alice', 1500.0)]

# ZSCORE: Lire le score d'un membre
score = r.zscore('leaderboard', 'alice')
print(score)  # 1500.0

# ZRANK: Rang d'un membre (ordre croissant)
rank = r.zrank('leaderboard', 'bob')
print(rank)  # 2 (3ème position, index 0-based)

# ZREVRANK: Rang décroissant
rank = r.zrevrank('leaderboard', 'bob')
print(rank)  # 0 (1ère position)

# ZINCRBY: Incrémenter le score
r.zincrby('leaderboard', 100, 'alice')
# alice passe à 1600

# ZCARD: Nombre de membres
count = r.zcard('leaderboard')
print(count)  # 3

# ZREM: Supprimer un membre
r.zrem('leaderboard', 'charlie')

# ZCOUNT: Compter les membres dans une plage de scores
count = r.zcount('leaderboard', 1000, 2000)
print(count)  # Nombre de joueurs entre 1000 et 2000 points


[OK] PATTERNS ET CAS D'USAGE AVANCÉS

# === PATTERN 1: CACHE SIMPLE ===

import redis
import time

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

def get_data_from_db(user_id):
    """Simule une requête DB lente"""
    print(f"Fetching user {user_id} from database (slow)...")
    time.sleep(2)  # Simule lenteur
    return {'id': user_id, 'name': 'Alice', 'email': 'alice@example.com'}

def get_user(user_id):
    """Get user avec cache Redis"""
    cache_key = f'user:{user_id}'
    
    # Essayer le cache d'abord
    cached = r.get(cache_key)
    if cached:
        print(f"Cache HIT for user {user_id}")
        import json
        return json.loads(cached)
    
    # Pas dans le cache -> requête DB
    print(f"Cache MISS for user {user_id}")
    data = get_data_from_db(user_id)
    
    # Stocker dans le cache (expire après 5 minutes)
    import json
    r.setex(cache_key, 300, json.dumps(data))
    
    return data

# Utilisation:
user = get_user(1000)  # Cache MISS -> lent (2 secondes)
user = get_user(1000)  # Cache HIT -> rapide (<1ms)


# === PATTERN 2: SESSIONS UTILISATEUR ===

import redis
import json
import secrets

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

class SessionManager:
    def __init__(self):
        self.r = r
        self.session_ttl = 3600  # 1 heure
    
    def create_session(self, user_id):
        """Créer une nouvelle session"""
        session_id = secrets.token_urlsafe(32)
        session_key = f'session:{session_id}'
        
        session_data = {
            'user_id': user_id,
            'created_at': time.time()
        }
        
        self.r.setex(session_key, self.session_ttl, json.dumps(session_data))
        return session_id
    
    def get_session(self, session_id):
        """Récupérer une session"""
        session_key = f'session:{session_id}'
        data = self.r.get(session_key)
        
        if not data:
            return None
        
        # Prolonger la session (refresh TTL)
        self.r.expire(session_key, self.session_ttl)
        
        return json.loads(data)
    
    def delete_session(self, session_id):
        """Supprimer une session (logout)"""
        session_key = f'session:{session_id}'
        self.r.delete(session_key)

# Utilisation:
sm = SessionManager()

# Login
session_id = sm.create_session(user_id=1000)
print(f"Session created: {session_id}")

# Accès protégé
session = sm.get_session(session_id)
if session:
    print(f"Welcome user {session['user_id']}")
else:
    print("Session expired or invalid")

# Logout
sm.delete_session(session_id)


# === PATTERN 3: RATE LIMITING ===

import redis
import time

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

class RateLimiter:
    def __init__(self, max_requests=100, window=60):
        self.r = r
        self.max_requests = max_requests
        self.window = window  # en secondes
    
    def is_allowed(self, user_id):
        """Vérifier si l'utilisateur peut faire une requête"""
        key = f'rate:{user_id}:{int(time.time() // self.window)}'
        
        # Incrémenter le compteur
        count = self.r.incr(key)
        
        # Définir TTL si première requête
        if count == 1:
            self.r.expire(key, self.window)
        
        return count <= self.max_requests

# Utilisation:
limiter = RateLimiter(max_requests=5, window=60)

for i in range(10):
    if limiter.is_allowed('user_123'):
        print(f"Request {i+1}: Allowed")
    else:
        print(f"Request {i+1}: Rate limit exceeded!")
        break


# === PATTERN 4: FILE D'ATTENTE (QUEUE) ===

import redis
import time
import threading

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

class TaskQueue:
    def __init__(self, queue_name='tasks'):
        self.r = r
        self.queue_name = queue_name
    
    def enqueue(self, task):
        """Ajouter une tâche"""
        import json
        self.r.lpush(self.queue_name, json.dumps(task))
        print(f"Enqueued task: {task}")
    
    def dequeue(self, timeout=0):
        """Récupérer une tâche (bloquant)"""
        import json
        result = self.r.brpop(self.queue_name, timeout=timeout)
        if result:
            _, task_data = result
            return json.loads(task_data)
        return None
    
    def size(self):
        """Nombre de tâches en attente"""
        return self.r.llen(self.queue_name)

# Producer (ajoute des tâches)
def producer():
    queue = TaskQueue()
    for i in range(5):
        queue.enqueue({'id': i, 'action': 'process_video'})
        time.sleep(1)

# Consumer (traite les tâches)
def consumer():
    queue = TaskQueue()
    while True:
        task = queue.dequeue(timeout=5)
        if task:
            print(f"Processing task: {task}")
            time.sleep(2)  # Simule traitement
        else:
            print("No more tasks, exiting")
            break

# Lancer producer et consumer dans des threads séparés
threading.Thread(target=producer).start()
threading.Thread(target=consumer).start()


# === PATTERN 5: PUB/SUB (CHAT EN TEMPS RÉEL) ===

import redis
import threading

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# Publisher (envoie des messages)
def publish_messages(channel):
    for i in range(5):
        message = f"Message {i+1}"
        r.publish(channel, message)
        print(f"Published: {message}")
        time.sleep(1)

# Subscriber (reçoit des messages)
def subscribe_messages(channel):
    pubsub = r.pubsub()
    pubsub.subscribe(channel)
    
    print(f"Subscribed to {channel}")
    
    for message in pubsub.listen():
        if message['type'] == 'message':
            print(f"Received: {message['data']}")

# Lancer dans des threads
channel_name = 'chat:room1'

threading.Thread(target=subscribe_messages, args=(channel_name,)).start()
time.sleep(1)  # Attendre que le subscriber soit prêt
threading.Thread(target=publish_messages, args=(channel_name,)).start()


# === PATTERN 6: LEADERBOARD ===

import redis

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

class Leaderboard:
    def __init__(self, name='leaderboard'):
        self.r = r
        self.name = name
    
    def add_score(self, player, score):
        """Ajouter/mettre à jour le score d'un joueur"""
        self.r.zadd(self.name, {player: score})
    
    def increment_score(self, player, points):
        """Incrémenter le score d'un joueur"""
        self.r.zincrby(self.name, points, player)
    
    def get_top(self, n=10):
        """Top N joueurs"""
        return self.r.zrevrange(self.name, 0, n-1, withscores=True)
    
    def get_rank(self, player):
        """Rang d'un joueur (1-based)"""
        rank = self.r.zrevrank(self.name, player)
        return rank + 1 if rank is not None else None
    
    def get_score(self, player):
        """Score d'un joueur"""
        return self.r.zscore(self.name, player)
    
    def get_around(self, player, count=5):
        """Joueurs autour d'un joueur donné"""
        rank = self.r.zrevrank(self.name, player)
        if rank is None:
            return []
        
        start = max(0, rank - count)
        end = rank + count
        
        return self.r.zrevrange(self.name, start, end, withscores=True)

# Utilisation:
lb = Leaderboard('game:scores')

# Ajouter des scores
lb.add_score('alice', 1500)
lb.add_score('bob', 2000)
lb.add_score('charlie', 1800)
lb.add_score('diana', 2200)
lb.add_score('eve', 1600)

# Top 3
print("Top 3:")
for player, score in lb.get_top(3):
    print(f"  {player}: {int(score)}")

# Rang de bob
rank = lb.get_rank('bob')
print(f"\nbob's rank: #{rank}")

# Incrémenter le score d'alice
lb.increment_score('alice', 1000)
print(f"\nalice's new score: {lb.get_score('alice')}")


# === PATTERN 7: DISTRIBUTED LOCK ===

import redis
import time
import uuid

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

class DistributedLock:
    def __init__(self, key, ttl=10):
        self.r = r
        self.key = f'lock:{key}'
        self.ttl = ttl
        self.identifier = str(uuid.uuid4())
    
    def acquire(self, timeout=10):
        """Acquérir le verrou"""
        end_time = time.time() + timeout
        
        while time.time() < end_time:
            # Essayer d'obtenir le verrou
            if self.r.set(self.key, self.identifier, nx=True, ex=self.ttl):
                return True
            
            time.sleep(0.1)  # Attendre un peu avant de réessayer
        
        return False
    
    def release(self):
        """Libérer le verrou"""
        # Script Lua pour garantir l'atomicité
        lua_script = """
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
        """
        
        self.r.eval(lua_script, 1, self.key, self.identifier)

# Utilisation:
def process_task(task_id):
    lock = DistributedLock(f'task:{task_id}', ttl=30)
    
    if lock.acquire(timeout=5):
        try:
            print(f"Processing task {task_id}")
            time.sleep(2)  # Simule traitement
            print(f"Task {task_id} completed")
        finally:
            lock.release()
    else:
        print(f"Could not acquire lock for task {task_id}")


# === PATTERN 8: COMPTEUR DISTRIBUÉ ===

import redis

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

class DistributedCounter:
    def __init__(self, key):
        self.r = r
        self.key = f'counter:{key}'
    
    def increment(self, amount=1):
        """Incrémenter de manière atomique"""
        return self.r.incr(self.key, amount)
    
    def decrement(self, amount=1):
        """Décrémenter de manière atomique"""
        return self.r.decr(self.key, amount)
    
    def get(self):
        """Lire la valeur actuelle"""
        value = self.r.get(self.key)
        return int(value) if value else 0
    
    def reset(self):
        """Réinitialiser à 0"""
        self.r.delete(self.key)

# Utilisation:
counter = DistributedCounter('page_views')

# Plusieurs serveurs peuvent incrémenter simultanément
counter.increment()
counter.increment()
counter.increment(5)

print(f"Total views: {counter.get()}")


[OK] TRANSACTIONS ET PIPELINES

# === TRANSACTIONS (MULTI/EXEC) ===

# Redis Transactions = Exécution atomique de plusieurs commandes
# Toutes les commandes réussissent ou toutes échouent

import redis

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# Exemple: Transfert d'argent entre comptes

def transfer_money(from_account, to_account, amount):
    """Transférer de l'argent de manière atomique"""
    pipe = r.pipeline()
    
    try:
        # Lire les soldes actuels
        pipe.watch(f'account:{from_account}', f'account:{to_account}')
        
        from_balance = float(r.get(f'account:{from_account}') or 0)
        to_balance = float(r.get(f'account:{to_account}') or 0)
        
        # Vérifier si suffisant
        if from_balance < amount:
            pipe.unwatch()
            return False, "Insufficient funds"
        
        # Commencer la transaction
        pipe.multi()
        pipe.decrby(f'account:{from_account}', amount)
        pipe.incrby(f'account:{to_account}', amount)
        
        # Exécuter
        pipe.execute()
        
        return True, "Transfer successful"
    
    except redis.WatchError:
        return False, "Transaction conflict, retry"

# Initialiser les comptes
r.set('account:alice', 1000)
r.set('account:bob', 500)

# Faire un transfert
success, message = transfer_money('alice', 'bob', 100)
print(message)

print(f"Alice: {r.get('account:alice')}")  # 900
print(f"Bob: {r.get('account:bob')}")      # 600


# === PIPELINES (BATCH OPERATIONS) ===

# Pipeline = Regrouper plusieurs commandes en une seule requête
# Réduit la latence réseau

import redis
import time

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# Sans pipeline (lent):
start = time.time()
for i in range(1000):
    r.set(f'key:{i}', f'value:{i}')
print(f"Without pipeline: {time.time() - start:.2f}s")

# Avec pipeline (rapide!):
start = time.time()
pipe = r.pipeline()
for i in range(1000):
    pipe.set(f'key:{i}', f'value:{i}')
pipe.execute()
print(f"With pipeline: {time.time() - start:.2f}s")

# Différence: 10-100x plus rapide!


# Pipeline pour lire plusieurs clés:
pipe = r.pipeline()
for i in range(100):
    pipe.get(f'key:{i}')

values = pipe.execute()
# values contient les 100 valeurs


[OK] LUA SCRIPTING

# === POURQUOI LUA DANS REDIS? ===

# Redis supporte l'exécution de scripts Lua
# Avantages:
# 1. Atomicité: Tout le script s'exécute atomiquement
# 2. Performance: Pas de round-trips réseau
# 3. Logique complexe: Opérations conditionnelles, boucles, etc.

import redis

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# === EXEMPLE 1: INCREMENT IF GREATER ===

# Problème: Incrémenter seulement si la valeur est > seuil
# En Python normal: 2 requêtes (GET + INCR) = pas atomique!

lua_script = """
local current = tonumber(redis.call('GET', KEYS[1]) or '0')
if current > tonumber(ARGV[1]) then
    return redis.call('INCR', KEYS[1])
else
    return current
end
"""

# Enregistrer le script (retourne un SHA)
script_sha = r.script_load(lua_script)

# Exécuter
r.set('counter', 10)
result = r.evalsha(script_sha, 1, 'counter', 5)
print(result)  # 11 (car 10 > 5)


# === EXEMPLE 2: RATE LIMITER AVEC LUA ===

lua_rate_limit = """
local key = KEYS[1]
local max = tonumber(ARGV[1])
local ttl = tonumber(ARGV[2])

local current = redis.call('INCR', key)

if current == 1 then
    redis.call('EXPIRE', key, ttl)
end

if current > max then
    return 0
else
    return 1
end
"""

def is_rate_limited(user_id, max_requests=10, window=60):
    key = f'rate:{user_id}:{int(time.time() // window)}'
    result = r.eval(lua_rate_limit, 1, key, max_requests, window)
    return result == 0

# Test
for i in range(15):
    if is_rate_limited('user_123', max_requests=10):
        print(f"Request {i+1}: Rate limited!")
    else:
        print(f"Request {i+1}: OK")


# === EXEMPLE 3: ATOMICALLY MOVE ITEM BETWEEN LISTS ===

lua_move = """
local item = redis.call('RPOP', KEYS[1])
if item then
    redis.call('LPUSH', KEYS[2], item)
    return item
else
    return nil
end
"""

r.lpush('queue:pending', 'task1', 'task2', 'task3')

# Déplacer atomiquement de pending -> processing
item = r.eval(lua_move, 2, 'queue:pending', 'queue:processing')
print(f"Moved: {item}")


[OK] REDIS EN PRODUCTION

# === PERSISTENCE (SAUVEGARDER LES DONNÉES) ===

# Redis stocke tout en RAM = risque de perte si crash!
# Solutions: RDB et AOF

# === RDB (Redis Database Backup) ===

# Snapshot complet de la mémoire à intervalles réguliers
# Fichier binaire: dump.rdb

# Configuration dans redis.conf:
# save 900 1      # Sauvegarde après 900s si ≥1 changement
# save 300 10     # Sauvegarde après 300s si ≥10 changements
# save 60 10000   # Sauvegarde après 60s si ≥10000 changements

# Avantages:
# - Fichier compact
# - Restauration rapide
# - Moins d'impact sur les performances

# Inconvénients:
# - Peut perdre les données depuis le dernier snapshot
# - Sauvegarde bloque Redis temporairement (BGSAVE en background)

# Commandes manuelles:
# SAVE -> bloque Redis (dangereux!)
# BGSAVE -> en arrière-plan (fork)

# Python:
r.bgsave()


# === AOF (Append Only File) ===

# Log de toutes les commandes d'écriture
# Fichier texte: appendonly.aof

# Configuration dans redis.conf:
# appendonly yes
# appendfilename "appendonly.aof"
# appendfsync everysec  # ou always ou no

# Avantages:
# - Perte minimale de données (max 1 seconde)
# - Fichier lisible (texte)
# - Plus durable

# Inconvénients:
# - Fichier plus gros
# - Légèrement plus lent
# - Restauration plus lente

# Réécriture AOF (compacter):
# BGREWRITEAOF -> réécrit le fichier en version compacte

# Python:
r.bgrewriteaof()


# === RECOMMANDATIONS ===

# Pour maximum durabilité:
# appendonly yes
# appendfsync everysec
# save ""  # Désactiver RDB

# Pour maximum performance:
# appendonly no
# save 900 1
# save 300 10


# === MONITORING ===

import redis

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# Informations générales:
info = r.info()
print(f"Redis version: {info['redis_version']}")
print(f"Uptime: {info['uptime_in_seconds']} seconds")
print(f"Connected clients: {info['connected_clients']}")
print(f"Used memory: {info['used_memory_human']}")

# Statistiques de mémoire:
memory_info = r.info('memory')
print(f"Used memory: {memory_info['used_memory_human']}")
print(f"Peak memory: {memory_info['used_memory_peak_human']}")
print(f"Memory fragmentation: {memory_info['mem_fragmentation_ratio']}")

# Statistiques des commandes:
stats = r.info('commandstats')
for cmd, data in stats.items():
    if cmd.startswith('cmdstat_'):
        print(f"{cmd}: {data}")

# Clients connectés:
clients = r.client_list()
for client in clients:
    print(f"Client: {client['addr']}, DB: {client['db']}")

# Slowlog (commandes lentes):
slowlog = r.slowlog_get(10)
for entry in slowlog:
    print(f"Command: {entry['command']}, Duration: {entry['duration']}µs")


# === SCALING REDIS ===

# === REPLICATION (MASTER-SLAVE) ===

# Setup:
# 1 Master (lecture + écriture)
# N Slaves (lecture seulement)

# Configuration du slave (redis.conf):
# replicaof master_ip master_port
# masterauth master_password

# Python (connexion à un slave):
slave = redis.Redis(host='slave_ip', port=6379)
value = slave.get('key')  # Lecture OK
# slave.set('key', 'value')  # ERREUR: slave en lecture seule

# Avantages:
# - Haute disponibilité
# - Distribution des lectures
# - Backup en temps réel

# Inconvénients:
# - Master = point de défaillance unique
# - Écritures limitées au master


# === SENTINEL (HAUTE DISPONIBILITÉ) ===

# Redis Sentinel = Monitoring + Failover automatique
# Si le master tombe, un slave devient master automatiquement

# Python avec Sentinel:
from redis.sentinel import Sentinel

sentinel = Sentinel([
    ('sentinel1_ip', 26379),
    ('sentinel2_ip', 26379),
    ('sentinel3_ip', 26379)
], socket_timeout=0.1)

# Obtenir le master actuel:
master = sentinel.master_for('mymaster', socket_timeout=0.1)
master.set('key', 'value')

# Obtenir un slave:
slave = sentinel.slave_for('mymaster', socket_timeout=0.1)
value = slave.get('key')


# === CLUSTER (SHARDING) ===

# Redis Cluster = Distribution des données sur plusieurs nœuds
# Partitionnement automatique par hash slots (16384 slots)

# Python avec Cluster:
from redis.cluster import RedisCluster

rc = RedisCluster(
    host='cluster_node_ip',
    port=6379,
    decode_responses=True
)

# Utilisation identique:
rc.set('key', 'value')
value = rc.get('key')

# Avantages:
# - Scalabilité horizontale
# - Pas de point de défaillance unique
# - Performance linéaire

# Inconvénients:
# - Plus complexe à configurer
# - Certaines opérations limitées (multi-key operations)


[OK] SÉCURITÉ

# === BEST PRACTICES ===

# 1. TOUJOURS définir un mot de passe:
# requirepass strong_password_here

# 2. Limiter l'accès réseau:
# bind 127.0.0.1  # Seulement localhost
# Ou utiliser un firewall

# 3. Désactiver les commandes dangereuses:
# rename-command FLUSHDB ""
# rename-command FLUSHALL ""
# rename-command CONFIG ""
# rename-command SHUTDOWN ""

# 4. Utiliser SSL/TLS (Redis 6+):
# tls-port 6380
# tls-cert-file /path/to/cert.pem
# tls-key-file /path/to/key.pem

# Python avec SSL:
r = redis.Redis(
    host='redis.example.com',
    port=6380,
    password='password',
    ssl=True,
    ssl_cert_reqs='required',
    ssl_ca_certs='/path/to/ca.crt'
)

# 5. Mode protégé (Redis 3.2+):
# protected-mode yes
# Refuse les connexions externes si pas de mot de passe

# 6. Utiliser un utilisateur non-root:
# Ne JAMAIS lancer Redis en tant que root!

# 7. ACL (Access Control Lists - Redis 6+):
# Contrôle granulaire des permissions

# Créer un utilisateur:
# ACL SETUSER alice on >password ~cached:* +get

# Explications:
# - on: utilisateur actif
# - >password: définir le mot de passe
# - ~cached:*: accès seulement aux clés commençant par "cached:"
# - +get: permission GET seulement

# Python avec ACL:
r = redis.Redis(
    host='localhost',
    port=6379,
    username='alice',
    password='password'
)


[OK] DEBUGGING ET DÉPANNAGE

# === PROBLÈME: REDIS EST LENT ===

# 1. Vérifier les commandes lentes:
slowlog = r.slowlog_get(10)
for entry in slowlog:
    print(f"{entry['command']} took {entry['duration']}µs")

# 2. Éviter les commandes bloquantes:
# - KEYS * -> Utiliser SCAN à la place
# - SMEMBERS (avec gros set) -> Utiliser SSCAN
# - HGETALL (avec gros hash) -> Utiliser HSCAN

# 3. Vérifier la mémoire:
info = r.info('memory')
if info['used_memory'] > info['maxmemory'] * 0.8:
    print("Warning: Memory usage > 80%")

# 4. Activer les pipelines pour les batch operations


# === PROBLÈME: MÉMOIRE PLEINE ===

# 1. Vérifier l'usage:
print(r.info('memory')['used_memory_human'])

# 2. Voir les grosses clés:
# redis-cli --bigkeys

# 3. Augmenter maxmemory ou nettoyer:
# CONFIG SET maxmemory 1gb

# 4. Configurer eviction policy:
# CONFIG SET maxmemory-policy allkeys-lru


# === PROBLÈME: CONNEXION REFUSÉE ===

# 1. Vérifier que Redis tourne:
# sudo systemctl status redis-server

# 2. Vérifier le bind address:
# redis-cli CONFIG GET bind

# 3. Vérifier le firewall:
# sudo ufw allow 6379

# 4. Tester avec telnet:
# telnet localhost 6379


# === PROBLÈME: PERFORMANCES RÉSEAU ===

# 1. Utiliser un pool de connexions:
pool = redis.ConnectionPool(
    host='localhost',
    port=6379,
    max_connections=50
)
r = redis.Redis(connection_pool=pool)

# 2. Utiliser des pipelines

# 3. Activer keepalive:
r = redis.Redis(
    host='localhost',
    socket_keepalive=True,
    socket_keepalive_options={
        socket.TCP_KEEPIDLE: 1,
        socket.TCP_KEEPINTVL: 1,
        socket.TCP_KEEPCNT: 5
    }
)


[OK] EXEMPLES COMPLETS D'APPLICATIONS

# === APPLICATION COMPLÈTE: SYSTÈME DE CACHE ===

import redis
import json
import time
from functools import wraps

class RedisCache:
    def __init__(self, host='localhost', port=6379, db=0):
        self.r = redis.Redis(
            host=host,
            port=port,
            db=db,
            decode_responses=True
        )
    
    def cache(self, ttl=300):
        """Décorateur pour mettre en cache le résultat d'une fonction"""
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                # Générer une clé unique basée sur la fonction et ses arguments
                cache_key = f"cache:{func.__name__}:{str(args)}:{str(kwargs)}"
                
                # Essayer le cache
                cached = self.r.get(cache_key)
                if cached:
                    print(f"Cache HIT: {cache_key}")
                    return json.loads(cached)
                
                # Appeler la fonction
                print(f"Cache MISS: {cache_key}")
                result = func(*args, **kwargs)
                
                # Stocker dans le cache
                self.r.setex(cache_key, ttl, json.dumps(result))
                
                return result
            
            return wrapper
        return decorator
    
    def invalidate(self, pattern):
        """Invalider le cache par pattern"""
        keys = self.r.keys(f"cache:{pattern}*")
        if keys:
            self.r.delete(*keys)
            print(f"Invalidated {len(keys)} cache entries")

# Utilisation:
cache = RedisCache()

@cache.cache(ttl=60)
def get_user_profile(user_id):
    """Simule une requête DB lente"""
    print(f"Fetching user {user_id} from database...")
    time.sleep(2)
    return {
        'id': user_id,
        'name': 'Alice',
        'email': 'alice@example.com'
    }

# Premier appel: lent (cache MISS)
profile = get_user_profile(1000)

# Deuxième appel: rapide (cache HIT)
profile = get_user_profile(1000)

# Invalider le cache
cache.invalidate('get_user_profile')


# === APPLICATION COMPLÈTE: CHAT EN TEMPS RÉEL ===

import redis
import threading
import time

class ChatRoom:
    def __init__(self, room_id, host='localhost', port=6379):
        self.room_id = room_id
        self.r = redis.Redis(host=host, port=port, decode_responses=True)
        self.pubsub = self.r.pubsub()
        self.channel = f'chat:{room_id}'
    
    def send_message(self, username, message):
        """Envoyer un message dans le chat"""
        data = {
            'username': username,
            'message': message,
            'timestamp': time.time()
        }
        
        import json
        self.r.publish(self.channel, json.dumps(data))
        
        # Stocker dans l'historique (limité aux 100 derniers)
        history_key = f'chat:history:{self.room_id}'
        self.r.lpush(history_key, json.dumps(data))
        self.r.ltrim(history_key, 0, 99)
    
    def get_history(self, limit=50):
        """Récupérer l'historique des messages"""
        history_key = f'chat:history:{self.room_id}'
        messages = self.r.lrange(history_key, 0, limit-1)
        
        import json
        return [json.loads(msg) for msg in messages]
    
    def listen(self, callback):
        """Écouter les messages (bloquant)"""
        self.pubsub.subscribe(self.channel)
        
        for message in self.pubsub.listen():
            if message['type'] == 'message':
                import json
                data = json.loads(message['data'])
                callback(data)

# Utilisation:
chat = ChatRoom('room1')

# Callback pour afficher les messages
def on_message(data):
    print(f"[{data['username']}]: {data['message']}")

# Lancer le listener dans un thread
listener_thread = threading.Thread(
    target=chat.listen,
    args=(on_message,)
)
listener_thread.daemon = True
listener_thread.start()

# Envoyer des messages
time.sleep(1)
chat.send_message('Alice', 'Hello everyone!')
chat.send_message('Bob', 'Hi Alice!')

# Voir l'historique
history = chat.get_history(10)
for msg in reversed(history):
    print(f"[{msg['username']}]: {msg['message']}")


[OK] RESSOURCES & LIENS

# Documentation officielle Redis:
# https://redis.io/documentation

# Commandes Redis:
# https://redis.io/commands

# redis-py (bibliothèque Python):
# https://redis-py.readthedocs.io/

# Tutoriels interactifs:
# https://try.redis.io/

# Redis University (cours gratuits):
# https://university.redis.com/

# Redis sur Docker Hub:
# https://hub.docker.com/_/redis

# Redis Cloud (managed Redis):
# https://redis.com/redis-enterprise-cloud/

# Forum et communauté:
# https://reddit.com/r/redis
# https://stackoverflow.com/questions/tagged/redis

# Redis Best Practices:
# https://redis.io/docs/manual/patterns/
```