Top 5 des goulots d'étranglement de performance Redis et comment les résoudre
Optimisez les performances de vos déploiements Redis avec ce guide essentiel des goulots d'étranglement courants. Apprenez à identifier et résoudre des problèmes comme les commandes O(N) lentes, les allers-retours réseau excessifs, la pression mémoire et les politiques d'éviction inefficaces, les surcharges de persistance et les opérations liées au CPU. Cet article fournit des mesures concrètes, des exemples pratiques et des bonnes pratiques, de l'utilisation du pipelining et de `SCAN` à l'optimisation des structures de données et de la persistance, garantissant que votre instance Redis reste rapide et fiable pour tous vos besoins de mise en cache, messagerie et stockage de données.
Top 5 des goulots d'étranglement de performance Redis et comment les résoudre
Les problèmes de performance Redis semblent généralement mystérieux jusqu'à ce que vous vous rappeliez une chose : Redis est rapide, mais il n'est pas exempt de travail. Une commande qui parcourt un million de clés parcourt toujours un million de clés. Un client qui envoie une commande par aller-retour réseau paie toujours pour chaque aller-retour. Un serveur qui manque de mémoire doit toujours évincer, échanger, rejeter les écritures ou tomber en panne selon la configuration.
Lorsque Redis ralentit, ne commencez pas par modifier des paramètres au hasard. Commencez par des preuves :
redis-cli INFO
redis-cli SLOWLOG GET 20
redis-cli LATENCY DOCTOR
redis-cli INFO commandstats
redis-cli INFO memory
Ces commandes pointent généralement vers l'un des cinq goulots d'étranglement : commandes lentes, allers-retours réseau, pression mémoire, surcharge de persistance ou saturation CPU.
1. Commandes lentes sur de grandes données
Redis possède de nombreuses petites opérations à temps constant, mais toutes les commandes ne sont pas petites. Les commandes telles que KEYS, les grandes LRANGE, SMEMBERS, HGETALL, ZRANGE sur de grandes plages, SORT et les longs scripts Lua peuvent bloquer d'autres clients pendant leur exécution.
L'incident classique commence par une commande de nettoyage ou de débogage :
KEYS *
Sur une petite instance de développement, elle retourne immédiatement. Sur un espace de clés de production avec des millions de clés, elle peut bloquer le serveur suffisamment longtemps pour que les requêtes des applications s'accumulent. Le même schéma se produit avec un hachage qui a commencé comme "quelques champs par utilisateur" et est devenu silencieusement un objet géant.
Trouvez les preuves :
redis-cli SLOWLOG GET 20
redis-cli INFO commandstats
redis-cli LATENCY LATEST
SLOWLOG enregistre les commandes qui ont dépassé le seuil configuré. INFO commandstats montre les compteurs d'appels par commande et le temps cumulé. Si une commande domine le temps, commencez par là.
Corrigez le modèle d'accès :
redis-cli --scan --pattern 'user:*'
Utilisez SCAN au lieu de KEYS pour l'itération de l'espace de clés. Utilisez HSCAN, SSCAN et ZSCAN pour les grands hachages, ensembles et ensembles triés. Récupérez des pages ou des plages au lieu de structures entières :
LRANGE feed:user:42 0 49
ZRANGE leaderboard 0 99 WITHSCORES
Si un objet est devenu trop grand, divisez-le en fonction de la façon dont l'application le lit. Un seul hachage user:42 avec des milliers de champs non liés peut être pratique pour les écritures mais pénible pour les lectures qui n'ont besoin que des paramètres de profil. Des clés séparées comme user:42:profile, user:42:prefs et user:42:counters peuvent réduire la quantité de données touchées par requête.
Pour la suppression, préférez UNLINK lorsque les valeurs peuvent être grandes :
UNLINK old:large:set
UNLINK supprime la clé de l'espace de clés et libère la mémoire de manière asynchrone. C'est plus sûr que DEL pour les grandes valeurs, bien que le nettoyage en masse nécessite toujours une limitation.
2. Trop d'allers-retours réseau
Redis peut traiter une commande en microsecondes pendant que votre application passe des millisecondes à attendre le réseau. Si un chemin de requête envoie 50 commandes Redis séquentielles, le réseau peut dominer le temps total même lorsque Redis lui-même est en bonne santé.
C'est courant dans du code comme :
for user_id in user_ids:
profile = redis.get(f"user:{user_id}:profile")
Chaque GET attend sa propre réponse avant que la suivante ne commence. Sur un réseau, c'est coûteux.
Utilisez le pipelining :
pipe = redis.pipeline(transaction=False)
for user_id in user_ids:
pipe.get(f"user:{user_id}:profile")
profiles = pipe.execute()
Le pipelining envoie plusieurs commandes sans attendre chaque réponse individuellement. Redis exécute toujours les commandes dans l'ordre, mais le client évite de payer un aller-retour pour chaque commande.
Utilisez des commandes multi-clés là où elles conviennent :
MGET user:1:profile user:2:profile user:3:profile
Ne transformez pas chaque requête en un pipeline énorme. Les grands pipelines peuvent augmenter l'utilisation de la mémoire et créer des rafales de réponses. Traitez suffisamment pour éliminer le gaspillage évident d'aller-retour, puis mesurez.
Pour les chemins à forte lecture, vérifiez également si votre application demande à plusieurs reprises à Redis des valeurs qui pourraient être récupérées une fois et réutilisées pour la durée d'une requête. Un petit cache de requête local peut supprimer les lectures en double accidentelles sans modifier Redis du tout.
3. Pression mémoire et éviction
Redis est centré sur la mémoire. Une fois la mémoire serrée, les performances se dégradent de plusieurs manières : l'éviction coûte du CPU, les écritures peuvent échouer sous noeviction, les forks de persistance peuvent devenir plus difficiles, les réplicas peuvent prendre du retard, et le système d'exploitation peut échanger si l'hôte est mal configuré ou surchargé.
Vérifiez la mémoire :
redis-cli INFO memory
redis-cli INFO stats | grep evicted_keys
redis-cli CONFIG GET maxmemory
redis-cli CONFIG GET maxmemory-policy
Signes importants :
used_memoryest proche demaxmemory.evicted_keysaugmente rapidement.- L'hôte échange.
- Les grandes clés consomment plus de mémoire que prévu.
- Les clés de cache expirant n'ont pas réellement de TTL.
Trouvez les grandes clés avec précaution. N'exécutez pas de commandes larges et coûteuses pendant le trafic de pointe. L'échantillonnage avec --bigkeys peut aider :
redis-cli --bigkeys
Pour les caches, définissez une limite de mémoire et une politique d'éviction qui correspond aux données :
maxmemory 4gb
maxmemory-policy allkeys-lru
allkeys-lru ou allkeys-lfu peut être approprié lorsque toutes les clés sont des entrées de cache. volatile-lru n'évince que les clés avec TTL, ce qui est utile lorsque des clés persistantes partagent l'instance avec des clés de cache. noeviction est souvent approprié pour Redis utilisé comme magasin de données principal, car évincer silencieusement des données à apparence durable serait pire que de retourner une erreur.
Définissez des TTL au moment de l'écriture :
SET cache:product:123 "$json" EX 300
Pour les magasins de sessions, soyez délibéré. Une clé de session sans expiration est généralement un bug. Pour les limiteurs de débit, les compteurs doivent expirer avec la fenêtre. Pour les Streams, taillez les anciennes entrées. Les fuites de mémoire dans Redis sont souvent des données d'application qui n'ont jamais reçu de cycle de vie.
Réduisez également la surcharge des clés et des valeurs là où cela compte. Des milliers de petites clés peuvent coûter plus de métadonnées que prévu. Parfois, un hachage compact est meilleur que de nombreuses clés individuelles ; parfois, l'inverse est vrai car les lectures n'ont besoin que d'un champ. Mesurez avec des modèles d'accès réels au lieu de supposer qu'une forme est toujours la meilleure.
4. Persistance et blocages d'E/S disque
La persistance protège les données, mais elle introduit un comportement de disque et de fork que vous devez comprendre. Les instantanés RDB et les réécritures AOF sont normalement des opérations en arrière-plan, mais elles peuvent toujours causer de la latence via le temps de fork, la pression mémoire copy-on-write et les E/S disque.
Vérifiez l'état de la persistance :
redis-cli INFO persistence
redis-cli LATENCY LATEST
iostat -xz 1
Recherchez les sauvegardes en arrière-plan échouées, les longs temps de fork, l'activité de réécriture AOF et la saturation du disque. Si les pics de latence coïncident avec BGSAVE ou BGREWRITEAOF, le réglage de la persistance est prioritaire.
Pour AOF, le principal paramètre de durabilité/performance est :
appendfsync everysec
everysec est le choix équilibré habituel. always synchronise chaque écriture et peut être très lent. no laisse la synchronisation au système d'exploitation et accepte plus de risque de perte de données en cas de crash.
Pour RDB, évitez les règles d'instantané qui se déclenchent constamment sur une charge de travail d'écriture élevée, sauf si c'est intentionnel :
save 900 1
save 300 10
save 60 10000
Ces exemples par défaut ne sont pas automatiquement adaptés à chaque charge de travail. Un Redis à haute écriture utilisé comme cache jetable peut ne pas avoir besoin de persistance du tout. Une instance Redis utilisée comme file d'attente de travaux ou magasin de sessions en a probablement besoin, mais la fenêtre de perte acceptable doit être claire.
Si la persistance entre en concurrence avec le trafic de l'application, envisagez :
- Un stockage SSD local plus rapide.
- Séparer la persistance Redis des autres services gourmands en disque.
- Exécuter la persistance sur un réplica lorsque le primaire peut tolérer cette conception.
- Maintenir la taille du jeu de données en dessous de ce que l'hôte peut forker confortablement.
- Définir Linux
vm.overcommit_memory=1là où Redis le recommande pour les sauvegardes en arrière-plan.
Ne désactivez pas la persistance aveuglément pour "corriger les performances" à moins que les données ne soient vraiment jetables. Cela pourrait améliorer le graphique tout en transformant un redémarrage en perte de données.
5. Saturation CPU et exécution de commandes monothread
L'exécution des commandes Redis est largement monothread, même si Redis moderne utilise des threads supplémentaires pour certaines E/S et travaux en arrière-plan. Si un cœur est monopolisé par Redis, ajouter plus de cœurs inactifs sur la même instance peut ne pas aider le chemin de commande chaud.
Vérifiez l'hôte et le mélange de commandes Redis :
top -H -p $(pgrep redis-server)
redis-cli INFO commandstats
redis-cli SLOWLOG GET 20
redis-cli INFO clients
Causes CPU courantes :
- Grandes opérations sur ensembles, ensembles triés, listes ou hachages.
- Scripts Lua lourds.
- Surcharge de compression ou de sérialisation dans l'application entraînant des valeurs plus grandes que prévu.
- Fan-out Pub/Sub très élevé.
- Éviction coûteuse sous pression mémoire.
- Trop de connexions qui se reconnectent constamment ou émettent de petites commandes.
Corrigez le CPU en réduisant le travail, en divisant le travail ou en distribuant le travail.
Réduisez le travail en changeant les commandes et les formes de données. Si vous n'avez besoin que de 50 éléments, ne récupérez pas 5 000. Si chaque requête analyse un blob JSON de 500 Ko pour lire un indicateur, divisez cet indicateur en une clé ou un champ plus petit.
Divisez le travail en déplaçant les longues boucles vers les clients en utilisant des analyses incrémentielles :
HSCAN big:hash 0 COUNT 100
Distribuez le travail avec des réplicas pour les lectures ou Redis Cluster pour le partitionnement. Les réplicas aident le trafic à forte lecture, mais ils ne rendent pas les écritures moins coûteuses sur le primaire. Redis Cluster distribue les clés entre les primaires, ce qui peut augmenter la capacité totale de CPU et de mémoire, mais ajoute également de la complexité opérationnelle et des contraintes de slot de clé.
Pour Pub/Sub, surveillez les tampons de sortie et le fan-out :
redis-cli PUBSUB NUMSUB events:updates
redis-cli CLIENT LIST
Un abonné lent peut se transformer en pression mémoire. Des milliers d'abonnés peuvent transformer une publication en une grande quantité de sortie réseau. Si Pub/Sub est lourd, envisagez de l'isoler sur une instance Redis séparée.
Un workflow de triage rapide
Lorsque la latence Redis augmente, exécutez ces vérifications dans l'ordre :
redis-cli --latency
redis-cli SLOWLOG GET 10
redis-cli LATENCY DOCTOR
redis-cli INFO memory
redis-cli INFO clients
redis-cli INFO persistence
redis-cli INFO commandstats
Puis demandez :
- Une commande lente est-elle apparue ?
- La mémoire a-t-elle atteint
maxmemoryou a-t-elle commencé à évincer ? - La persistance a-t-elle démarré une sauvegarde ou une réécriture ?
- Les clients connectés ou bloqués ont-ils augmenté ?
- Le nombre d'appels ou le temps d'une commande a-t-il explosé ?
- Les déploiements d'applications ont-ils modifié les modèles d'accès Redis ?
La plupart des goulots d'étranglement Redis ne sont pas résolus par un paramètre magique. Ils sont résolus en rendant la charge de travail plus petite, plus incrémentielle, plus groupée ou mieux isolée. Les meilleurs déploiements Redis sont ennuyeux : les clés expirent quand elles le devraient, les grandes opérations sont paginées, les clients utilisent judicieusement le pipelining, les paramètres de persistance correspondent à la valeur des données, et la surveillance détecte la tendance avant que les utilisateurs ne la ressentent.
Ce qu'il faut mesurer avant et après une correction
Une correction de performance n'est réelle que si le graphique change pour la charge de travail qui compte. Avant de modifier le code ou la configuration, capturez une petite référence :
redis-cli INFO stats
redis-cli INFO commandstats
redis-cli INFO memory
redis-cli INFO persistence
redis-cli SLOWLOG GET 20
Au niveau du système, capturez le CPU, le disque, la mémoire, l'échange et le débit réseau. Si Redis s'exécute dans un conteneur, vérifiez à la fois les limites du conteneur et la pression de l'hôte. Un processus Redis peut sembler correct dans sa propre vue mémoire tandis que l'hôte est sous pression disque ou CPU d'un autre service.
Après le changement, comparez :
- Latence p50, p95 et p99 de l'application pour les requêtes soutenues par Redis.
- Latence des commandes Redis, pas seulement la latence des requêtes.
- Entrées Slowlog par commande.
- Taux d'éviction et marge mémoire.
- Clients connectés et connexions rejetées.
- Temps de fork de persistance et état AOF/RDB.
- Retard du réplica si les réplicas servent les lectures ou protègent la durabilité.
Méfiez-vous des corrections qui ne font que déplacer la douleur. Par exemple, un grand pipeline peut réduire la latence des requêtes mais augmenter les pics de mémoire. Désactiver AOF peut supprimer la latence du disque mais affaiblir la récupération. Augmenter maxmemory peut retarder les évictions mais affamer l'hôte si la machine était déjà partagée.
Une pratique utile consiste à écrire un petit test de charge autour du modèle Redis exact que vous avez modifié. Si l'ancien code faisait 40 GET séquentiels, testez les GET séquentiels par rapport à MGET ou au pipelining avec des tailles de charge utiles réalistes. Si l'ancien code utilisait HGETALL, testez HGET pour les champs réellement nécessaires à la requête. Le réglage de Redis est beaucoup plus facile lorsque vous évaluez la forme que vous exécutez réellement, pas un nombre générique "d'opérations Redis par seconde".
Enfin, gardez le rollback simple. Un changement de performance Redis se situe souvent dans le code de l'application, les paramètres du client et la configuration du serveur en même temps. Changez une chose quand vous le pouvez. Si vous devez changer plusieurs choses, notez quel symptôme chaque changement est censé améliorer. Cela empêche le prochain ingénieur d'hériter d'un tas de paramètres mystérieux que personne ne veut supprimer.