Introduction
Redis est rapide par conception, mais les systèmes réels ajoutent de la latence et des limites : réseau, persistance, structure des données et comportement des clients. Ce guide montre comment identifier les goulots d'étranglement, dimensionner les ressources, réduire la latence et augmenter le débit avec des commandes et du code que vous pouvez tester localement. Il s'adresse aux développeurs et opérateurs faisant tourner Redis (souvent avec Node.js et Docker) qui veulent des améliorations sûres et mesurables.
Ce que vous allez apprendre :
- Comment capturer une référence pour la latence et le débit en quelques minutes
- Comment dimensionner correctement CPU, mémoire et persistance pour des performances stables
- Comment régler les clients avec le pooling, les pipelines et un meilleur accès aux données
- Un flux de travail d'optimisation incrémentale sûr avec des étapes de retour arrière claires
Ce dont vous avez besoin :
- Redis 6+ (les exemples notent le comportement de Redis 7 quand pertinent)
- redis-cli et optionnellement redis-benchmark
- Node.js 18+ pour les exemples clients (ioredis)
- Optionnel : Docker pour un pilote local
Avant d'optimiser : prérequis et sécurité
- Utilisez une fenêtre contrôlée. Faites les changements pendant une fenêtre de maintenance ou sur un replica/cluster de staging d'abord.
- Faites un snapshot de la configuration et des données. Sauvegardez redis.conf, la sortie de CONFIG GET * et INFO, et prenez un snapshot RDB. Sachez comment restaurer.
- Fixez la référence. Exécutez la même charge de travail à chaque fois (durée, concurrence, jeu de données). Gardez vos chiffres initiaux pour la comparaison.
- Garde-fous. Mettez en place des alertes pour la latence p95/p99, evicted_keys, aof_delayed_fsync, blocked_clients. Définissez un retour arrière immédiat (restauration config/fichier ou feature flag) pour chaque changement.
---
Ce qui cause les goulots d'étranglement Redis
Vous trouverez généralement un ou plusieurs de ces motifs :
- Trop d'allers-retours : beaucoup d'appels séquentiels petits au lieu de batching ou de pipelines.
- Opérations O(N) sur de grandes collections : KEYS en production, SMEMBERS sur d'énormes sets, LRANGE sur de grandes listes, gros SORT.
- Inadéquation du modèle de données : valeurs très grandes, huge hashes, ou clés chaudes concentrant la charge sur un shard/primary.
- Surcharge de persistance : stratégie AOF fsync, réécriture AOF, et snapshots RDB en compétition avec le débit d'écriture.
- Pression mémoire et fragmentation : le copy-on-write pendant les forks a besoin de marge ; la fragmentation gonfle le RSS.
- Clients lents et backpressure : les tampons de sortie client grossissent ; les clients bloqués stoppent la progression.
- Saturation CPU : chaque primary exécute les commandes sur un seul cœur ; scripts lourds ou QPS élevé le saturant.
- Surcharge réseau et TLS : nombreux petits paquets, interactions Nagle, ou coût crypto TLS augmentent la latence de queue.
Votre travail est de confirmer lequel de ces problèmes se produit réellement dans votre environnement et de le corriger avec des changements minimaux et mesurables.
---
Vérifications rapides de santé et de latence
Exécutez ces vérifications d'abord pour établir une référence et localiser les goulots d'étranglement probables.
Snapshot de latence
redis-cli --latency -h <host> -p <port>
redis-cli --latency-history -h <host> -p <port>
redis-cli LATENCY DOCTOR
redis-cli LATENCY LATEST
Observations attendues et modes de défaillance :
- Un p95 stable sous 1–2 ms en LAN est typique pour GET/SET ; des pics p99 alignés avec RDB ou réécriture AOF suggèrent une pression fork/COW.
- LATENCY DOCTOR met souvent en évidence fork, fsync, ou pics de commande ; corréléz les timestamps avec INFO persistence.
Commandes lentes
redis-cli SLOWLOG LEN
redis-cli SLOWLOG GET 64
Ce qu'il faut chercher :
- Coupables O(N) (ex: KEYS, SMEMBERS sur gros sets, gros LRANGE), scripts Lua lourds, ou grosses payloads.
- Si SLOWLOG est clairsemé mais que vous voyez quand même une latence élevée, le réseau ou le bavardage côté client est plus probable.
Santé mémoire, clients, et persistance
redis-cli INFO memory | egrep 'used_memory:|used_memory_rss:|mem_fragmentation_ratio:'
redis-cli INFO stats | egrep 'evicted_keys:|keyspace_hits:|keyspace_misses:|instantaneous_ops_per_sec:'
redis-cli INFO persistence | egrep 'aof_enabled:|aof_last_write_status:|aof_rewrite_in_progress:|rdb_bgsave_in_progress:'
redis-cli INFO clients | egrep 'connected_clients:|blocked_clients:'
Signaux et modes de défaillance :
- mem_fragmentation_ratio > 1.5 ou RSS bien au-dessus de used_memory suggère fragmentation et risque de fork.
- evicted_keys > 0 signifie pression mémoire ; attendez des pics de latence et des cache misses.
- aof_delayed_fsync > 0 indique que le stockage ne suit pas ; la latence de queue augmente.
- blocked_clients > 0 indique souvent des opérations bloquantes lourdes (BLPOP/BRPOP) ou mauvais usage de scripts.
Sonde de débit (à utiliser avec précaution)
redis-benchmark -h <host> -p <port> -t get,set -n 50000 -c 50 -P 16
Enregistrez p50/p95/p99, ops/sec, et erreurs. Utilisez les mêmes paramètres après chaque changement pour comparer.
---
Dimensionnement CPU, mémoire, et persistance
Un bon dimensionnement évite les arrêts et surprises quand le trafic ou le travail d'arrière-plan augmente.
CPU
- Un primary occupé ≈ un cœur occupé. Quand un primary unique approche 80–90 % sur son cœur, scale-out avec sharding ou Redis Cluster.
- Gardez l'affinité CPU stable et évitez les voisins bruyants. Épinglez le processus/conteneur sur des CPUs spécifiques si possible.
- Les lourds Lua/Functions ou commandes complexes comptent dans le même budget single-threadé ; profilez-les d'abord.
Mémoire
- Règle de marge : dataset + overhead + fork COW. Gardez au moins 30 % de mémoire libre sur l'hôte ; montez à 50 % avec écritures fréquentes ou gros objets.
- Surveillez mem_fragmentation_ratio ; soutenu > 1.5 est un drapeau rouge. Envisagez un redémarrage contrôlé en fenêtre creuse si fragmentation chronique.
- Si vous activez l'éviction, choisissez une politique selon les patterns d'accès (ex: allkeys-lru pour usage cache) et un maxmemory réaliste.
- Mode de défaillance : marge insuffisante pendant le fork mène à des OOM kills ou swap sévère. Retour arrière : pausez le travail d'arrière-plan (désactivez temporairement planification réécriture AOF/snapshot RDB) et ajoutez de la mémoire ou réduisez le dataset.
Persistance
- RDB : faible surcharge continue, mais les snapshots fork le processus. Planifiez loin des pics. Ajustez les intervalles save prudemment.
- AOF : appendfsync everysec est l'équilibre latence/durabilité courant ; always maximise la durabilité mais augmente la latence. Gardez l'AOF sur SSD local rapide.
- Surveillez aof_rewrite_in_progress et aof_delayed_fsync. Envisagez no-appendfsync-on-rewrite yes pour réduire les pics au prix d'un risque de durabilité.
- Guide de retour arrière : si la régresse latence, remettez fsync à everysec, replanifiez réécritures/snapshots, ou basculez temporairement en RDB-only en environnements non critiques.
---
Optimisation du débit : connexions et pipelines
La plupart des gains viennent du comportement client.
Pool de connexions
- Réutilisez un petit pool au lieu de créer de nouvelles connexions TCP. Activez TCP keepalive côté clients. Évitez les connexions longues inactives qui accumulent des tampons de sortie.
Batching et pipelining
- Commencez avec des tailles de lot de 50–200 opérations et ajustez. Lots trop gros peuvent augmenter la latence de queue et l'usage mémoire.
- Préférez MGET/MSET/HMGET/HMSET quand la sémantique le permet ; ils réduisent les allers-retours plus que des pipelines d'appels mono-clé.
Éviter les patterns bavards
- Remplacez une boucle GET par un seul MGET et un parsing vectorisé.
- Remplacez KEYS par SCAN pour une itération sûre en production.
- Fusionnez les séquences read-modify-write quand possible (ex: utilisez HINCRBY/INCRBY au lieu de GET + calcul + SET).
Replicas de lecture et sharding
- Déléguez les lectures tolérantes aux replicas ; assurez-vous que l'app gère le lag de réplication.
- Shardez par clé pour garder chaque primary sous les limites CPU et mémoire. Pour Cluster, utilisez les hash tags pour co-localiser les clés liées quand nécessaire.
Modes de défaillance et retours arrière
- Gros pipelines peuvent heurter les limites de tampons de sortie client ou timeouts. Symptômes : logs serveur sur tampons de sortie, blocked_clients en hausse, ou timeouts client. Retour arrière : réduisez la taille de lot ou désactivez l'auto-pipelining.
- Concurrence excessive peut saturer un seul cœur. Retour arrière : baissez la concurrence client et observez le CPU.
---
Exemples Node.js : réduire la latence, augmenter le QPS
Les exemples utilisent ioredis ; les idées s'appliquent aux autres clients.
Installation
npm install ioredis
Boucle naive par commande (bavarde et lente)
const Redis = require('ioredis');
const redis = new Redis('redis://127.0.0.1:6379', {
lazyConnect: false,
keepAlive: 1,
});
async function naiveWrite(items) {
for (const [k, v] of items) {
await redis.set(k, v); // un aller-retour réseau par clé
}
}
async function naiveRead(keys) {
const out = [];
for (const k of keys) {
out.push(await redis.get(k));
}
return out;
}
Écriture et lecture en batch et pipelined (moins d'allers-retours, QPS plus haut)
async function pipelinedWrite(items, batchSize = 100) {
for (let i = 0; i < items.length; i += batchSize) {
const slice = items.slice(i, i + batchSize);
const pipe = redis.pipeline();
for (const [k, v] of slice) pipe.set(k, v);
await pipe.exec();
}
}
async function bulkRead(keys, batchSize = 100) {
const results = [];
for (let i = 0; i < keys.length; i += batchSize) {
const slice = keys.slice(i, i + batchSize);
const pipe = redis.pipeline();
for (const k of slice) pipe.get(k);
const res = await pipe.exec();
results.push(...res.map(r => r[1]));
}
return results;
}
Préférez MGET/MSET quand possible
async function msetPairs(pairs) {
const obj = {};
for (const [k, v] of pairs) obj[k] = v;
await redis.mset(obj);
}
async function mgetKeys(keys) {
return await redis.mget(keys);
}
Remplacez KEYS par SCAN en production
async function scanByPattern(pattern, count = 1000) {
let cursor = '0';
const found = [];
do {
const [next, keys] = await redis.scan(cursor, 'MATCH', pattern, 'COUNT', count);
cursor = next;
found.push(...keys);
} while (cursor !== '0');
return found;
}
Mesurez avant et après
node bench.js
redis-cli --latency-history
---
Considérations Docker et runtime
Les conteneurs conviennent à Redis si vous rendez les limites explicites.
CPU et mémoire
- Épinglez le CPU quand possible (ex: --cpuset-cpus="2\)). Gardez de la marge mémoire pour survivre aux forks. Désactivez le swap pour les conteneurs Redis.
Stockage
- Placez AOF/RDB sur volumes SSD rapides. Évitez les systèmes de fichiers distants lents pour AOF à forte écriture.
Réseau
- Privilégiez les chemins à faible latence. Minimisez les couches NAT. Le network host peut aider si mesuré pour réduire le jitter.
Exemple docker-compose.yml (pilote local)
version: '3.9'
services:
redis:
image: redis:7
command: ["redis-server", "--appendonly", "yes", "--appendfsync", "everysec" ]
ports:
- "6379:6379"
volumes:
- ./data:/data
ulimits:
nofile: 100000
---
Monitoring : métriques et garde-fous
Suivez les changements par rapport à votre référence et alertez sur les dérives de tendance, pas seulement les seuils absolus.
- Latence : p95/p99 côté client ; LATENCY LATEST pour événements serveur. Alerte sur p95 soutenu > référence + 100 %.
- Débit : instantaneous_ops_per_sec. Surveillez les baisses pendant les changements.
- Erreurs/backpressure : blocked_clients, rejets de connexion, timeouts.
- Mémoire : used_memory, used_memory_rss, mem_fragmentation_ratio. Alerte si fragmentation > 1.5 ou évictions qui démarrent.
- Santé keyspace : evicted_keys, expired_keys, taux de hit (hits vs misses).
- Persistance : aof_rewrite_in_progress, aof_delayed_fsync, rdb_bgsave_in_progress.
Récupérations rapides
redis-cli INFO stats | egrep 'instantaneous_ops_per_sec|evicted_keys|keyspace_hits|keyspace_misses'
redis-cli INFO memory | egrep 'used_memory:|used_memory_rss:|mem_fragmentation_ratio:'
redis-cli LATENCY LATEST
---
Flux de travail d'optimisation sûr (avec retour arrière)
- Référence
- Capturez p50/p95/p99 latence, ops/sec, marge mémoire, statut persistance sous charge représentative. Sauvegardez config et révision de code.
- Ciblage
- Utilisez SLOWLOG, LATENCY DOCTOR, et INFO pour choisir un goulot. Exemple : allers-retours excessifs sur un chemin de lecture chaud.
- Changez une chose
- Exemples : introduisez MGET/pipeline sur le chemin chaud ; remplacez KEYS par SCAN ; passez appendfsync de always à everysec ; ajoutez 30–50 % marge mémoire.
- Testez
- Relancez la même charge pour la même durée. Comparez latence et erreurs à la référence. Surveillez les métriques serveur pendant que le changement est actif.
- Sécurisez
- Ajoutez ou renforcez les alertes : p99 latence, evicted_keys, aof_delayed_fsync, blocked_clients.
- Retour arrière
- Définissez les retours par changement : rétablissez tailles de lot/feature flags clients, restaurez fsync antérieur, pausez ou replanifiez les sauvegardes d'arrière-plan, ou désactivez temporairement la fonctionnalité.
- Déploiement progressif
- Déployez sur une petite tranche de trafic ou un shard d'abord. Observez 30–60 minutes (ou cycles de trafic) avant élargissement.
Observations attendues
- Le batching/pipelining client réduit typiquement le p95 de 30–70 % sur les chemins bavards.
- Ajuster la persistance de always à everysec réduit souvent la latence de queue en écriture ; surveillez les compromis de durabilité.
- Ajouter de la mémoire pour éliminer les évictions améliore immédiatement le taux de hit et la latence.
Modes de défaillance
- Pipelines plus gros peuvent gonfler les tampons client, causant des timeouts sous pics.
- Événements fork sans marge peuvent causer explosions RSS et OOM kills.
- Rééquilibrage de shards sans hash-tagging soigné peut mal placer les clés liées et dégrader les opérations multi-clés.
---
Plan de pilote local
Objectif : Réduire la latence p95 GET de 40 % sur un seul chemin chaud en introduisant le pipelining et MGET, sans augmentation d'erreurs.
Plan
- Environnement : docker-compose Redis (AOF everysec), script client Node.js.
- Référence : lancez votre script avec GET/SET naive par commande pendant 2–5 minutes. Capturez p95 via redis-cli --latency-history et logs app.
- Changement : basculez la boucle chaude vers MGET ou pipeline avec taille de lot 100.
- Test : répétez l'exécution exacte. Comparez p50/p95 et ops/sec.
- Acceptez : continuez si p95 amélioré de 40 %+, taux d'erreur inchangé, et aof_delayed_fsync reste à 0.
- Documentez : config, diff de code, chiffres, et étapes de retour arrière explicites.
Jeu de commandes d'exemple
docker compose up -d
redis-cli --latency-history -h 127.0.0.1 -p 6379
node workload.js --mode=naive --seconds=180
node workload.js --mode=pipeline --seconds=180
---
Conclusion
Vous avez maintenant un chemin pratique pour optimiser Redis en toute sécurité :
- Mesurez d'abord : distribution de latence, débit, marge mémoire, statut persistance
- Corrigez vite ce que vous contrôlez : réduisez les allers-retours avec MGET et pipelines ; retirez les appels O(N) des chemins chauds
- Dimensionnez le serveur : un cœur occupé par primary, mémoire adéquate pour les forks, stockage rapide pour AOF/RDB
- Validez chaque changement contre une référence fixe, ajoutez des garde-fous, et déployez progressivement avec des options de retour arrière claires
Avec une mesure disciplinée, des changements ciblés et petits, et des déploiements sûrs, Redis reste rapide pendant que votre trafic et vos données grandissent.