Maîtrise de la Gestion Mémoire Redis pour des Performances Optimales

Débloquez des performances Redis optimales en maîtrisant les techniques de gestion mémoire. Ce guide complet couvre des aspects cruciaux comme la compréhension de l'empreinte mémoire de Redis, la surveillance avec `INFO memory` et `MEMORY USAGE`, et l'optimisation des structures de données. Apprenez à lutter contre la fragmentation avec la défragmentation active, à configurer des politiques d'éviction efficaces (`maxmemory`, `allkeys-lru`), et à tirer parti de la libération paresseuse pour des opérations plus fluides. Mettez en œuvre ces stratégies actionnables pour améliorer le débit de Redis, réduire la latence et assurer un cache et un stockage de données stables et performants.

Maîtrise de la Gestion Mémoire Redis pour des Performances Optimales

Redis est rapide car il conserve les données en mémoire, mais cette même conception fait que les erreurs de mémoire se manifestent rapidement. Un cache sans expiration croît jusqu'à ce que les écritures échouent. Quelques clés énormes créent des pics de latence lorsqu'elles sont supprimées. Une sauvegarde en arrière-plan peut nécessiter plus de mémoire que prévu en raison du copy-on-write. Un client lent peut construire un tampon de sortie suffisamment grand pour faire partie du problème.

Une bonne gestion mémoire Redis ne consiste pas seulement à "acheter plus de RAM". Il s'agit de savoir ce qui est stocké, combien de temps cela doit vivre, ce qui se passe à la limite de mémoire, et quelles opérations peuvent bloquer le serveur lorsque les clés sont grandes.

Comprendre l'Utilisation Mémoire de Redis

Redis utilise la mémoire système pour stocker toutes ses données. Lorsque vous SET une paire clé-valeur, Redis alloue de la mémoire pour la chaîne de la clé et la valeur, ainsi qu'un certain surcoût pour les structures de données internes. Comprendre les différentes composantes de l'utilisation mémoire est la première étape vers une gestion efficace :

  • Mémoire de Données : C'est la mémoire consommée par vos données réelles (clés, valeurs et structures de données internes comme les dictionnaires pour mapper les clés aux valeurs). La taille dépend du nombre et de la taille de vos clés et valeurs, et des structures de données que vous choisissez (chaînes, hachages, listes, ensembles, ensembles triés).
  • Mémoire de Surcoût : Redis ajoute un surcoût pour chaque clé, y compris les pointeurs, les métadonnées, les informations d'expiration et les données liées à l'éviction. Les petites structures agrégées peuvent utiliser des encodages compacts comme listpack ou intset selon la version de Redis et le type de données, tandis que les structures plus grandes utilisent des représentations plus générales.
  • Mémoire Tampon : Redis utilise des tampons de sortie client, des tampons de backlog de réplication et des tampons AOF. Des clients grands ou lents, ou une configuration de réplication chargée, peuvent consommer une mémoire tampon significative.
  • Mémoire Fork : Lorsque Redis effectue des opérations en arrière-plan comme la sauvegarde de snapshots RDB ou la réécriture de fichiers AOF, il fork un processus enfant. Ce processus enfant partage initialement la mémoire avec le parent via copy-on-write (CoW). Cependant, toute écriture dans l'ensemble de données par le processus parent après le fork entraînera la duplication des pages, augmentant ainsi l'empreinte mémoire totale.

Surveillance de la Mémoire Redis

Surveiller régulièrement la mémoire Redis est crucial pour identifier les problèmes potentiels avant qu'ils ne s'aggravent. L'outil principal pour cela est la commande INFO memory, ainsi que MEMORY USAGE.

La Commande INFO memory

redis-cli INFO memory

Métriques clés de INFO memory :

  • used_memory : Le nombre total d'octets alloués par Redis via son allocateur (jemalloc, glibc, etc.). C'est la somme de la mémoire utilisée par vos données, les structures de données internes et les tampons temporaires.
  • used_memory_human : used_memory au format lisible par l'humain.
  • used_memory_rss : Resident Set Size (RSS), la quantité de mémoire consommée par le processus Redis telle que rapportée par le système d'exploitation. Cela inclut les allocations propres de Redis, plus la mémoire utilisée par la gestion mémoire du système d'exploitation, les bibliothèques partagées, et potentiellement la mémoire fragmentée non encore libérée au système d'exploitation.
  • mem_fragmentation_ratio : C'est approximativement used_memory_rss / used_memory. Une valeur supérieure à 1.0 est normale. Une valeur beaucoup plus élevée peut signifier une fragmentation, un comportement de l'allocateur, ou une RSS qui n'a pas été retournée au système d'exploitation. Une valeur inférieure à 1.0 est un signe d'avertissement qui mérite d'être investigué car elle peut indiquer une mémoire paginée ou des effets de timing de mesure.
  • allocator_frag_bytes : Octets de fragmentation rapportés par l'allocateur mémoire.
  • lazyfree_pending_objects : Nombre d'objets en attente d'être libérés de manière asynchrone.

La Commande MEMORY USAGE

Pour inspecter l'utilisation mémoire de clés individuelles :

redis-cli MEMORY USAGE mykey
redis-cli MEMORY USAGE myhashkey SAMPLES 0 # Estimation pour les agrégats

Cette commande fournit une estimation de l'utilisation mémoire pour une clé donnée, vous aidant à identifier les points de données volumineux ou stockés de manière inefficace.

Stratégies Clés d'Optimisation Mémoire

Optimiser la mémoire dans Redis implique plusieurs étapes proactives, du choix des bons types de données à la gestion de la fragmentation.

1. Optimisation des Structures de Données

Redis propose plusieurs structures de données, chacune avec son propre comportement mémoire. La bonne structure dépend de la façon dont l'application lit et écrit les données.

  • Chaînes : Les plus simples, mais attention aux grandes chaînes. Utiliser SET ou GET sur de très grandes chaînes (Mo) peut impacter les performances en raison du surcoût de transfert réseau et mémoire.
  • Hachages, Listes, Ensembles, Ensembles Triés (Agrégats) : Redis peut encoder de manière compacte les petits types de données agrégés. Les noms d'encodage exacts et les seuils varient selon la version de Redis, donc vérifiez votre redis.conf et la sortie OBJECT ENCODING au lieu de supposer que l'ancienne terminologie ziplist s'applique partout.
    • Astuce : Gardez les membres individuels des agrégats petits. Pour les hachages, préférez de nombreux petits champs plutôt que quelques grands.
    • Configuration : Les versions de Redis diffèrent ici. Les versions plus anciennes utilisaient les paramètres *-ziplist-* ; les versions plus récentes utilisent couramment les paramètres *-listpack-* pour certaines structures. Ajustez-les soigneusement et testez avec des données réelles, car les encodages compacts économisent de la mémoire mais peuvent coûter du CPU pour certains modèles d'accès.

2. Bonnes Pratiques de Conception des Clés

Bien que les valeurs consomment généralement plus de mémoire, optimiser les noms de clés est également important :

  • Clés Courtes et Descriptives : Des clés plus courtes économisent de la mémoire, surtout lorsque vous en avez des millions. Cependant, ne sacrifiez pas la clarté pour une brièveté extrême. Visez des noms de clés descriptifs mais concis.
    • Mauvais : user:1000:profile:details:email
    • Bon : user:1000:email (si vous ne stockez que l'email)
  • Préfixage : Utilisez des préfixes cohérents (par exemple, user:, product:) à des fins d'organisation. Cela a un impact mémoire minimal mais facilite la gestion.

3. Minimisation du Surcoût

Chaque clé et valeur a un certain surcoût interne. Réduire le nombre de clés, en particulier les petites, peut être efficace.

  • Hachage au Lieu de Multiples Chaînes : Si vous avez de nombreux champs liés pour une entité, stockez-les dans un seul HASH plutôt que dans plusieurs clés STRING. Cela réduit le nombre de clés de premier niveau et leur surcoût associé.
    • Exemple : Au lieu de user:1:name, user:1:email, user:1:age, utilisez une clé HASH user:1 avec les champs name, email, age.

4. Gestion de la Fragmentation Mémoire

La fragmentation mémoire se produit lorsque l'allocateur mémoire est incapable de trouver des blocs de mémoire contigus de la taille exacte nécessaire, entraînant des lacunes inutilisées. Cela peut faire en sorte que used_memory_rss soit significativement plus élevé que used_memory.

  • Causes : Insertions et suppressions fréquentes de clés de tailles variables, surtout après que l'allocateur mémoire a fonctionné pendant longtemps.
  • Détection : Un mem_fragmentation_ratio significativement supérieur à 1.0 (par exemple, 1.5-2.0) indique une fragmentation élevée.
  • Solutions :
    • Défragmentation Active Redis 4.0+ : Redis peut défragmenter activement la mémoire sans redémarrer. Activez-la avec activedefrag yes dans redis.conf et configurez active-defrag-max-scan-time et active-defrag-cycle-min/max. Cela permet à Redis de déplacer les données, compactant ainsi la mémoire.
    • Redémarrer Redis : La manière la plus simple, bien que perturbatrice, de défragmenter la mémoire est de redémarrer le serveur Redis. Cela libère toute la mémoire au système d'exploitation, et l'allocateur repart à zéro. Pour les instances persistantes, assurez-vous qu'un snapshot RDB ou un fichier AOF est sauvegardé avant de redémarrer.
# Paramètres redis.conf pour la défragmentation active
activedefrag yes
active-defrag-ignore-bytes 100mb  # Ne pas défragmenter si la fragmentation est inférieure à 100 Mo
active-defrag-threshold-lower 10  # Commencer la défragmentation si le ratio de fragmentation est > 10%
active-defrag-threshold-upper 100 # Arrêter la défragmentation si le ratio de fragmentation est > 100%
active-defrag-cycle-min 1         # Effort CPU minimum pour la défragmentation (1-100%)
active-defrag-cycle-max 20        # Effort CPU maximum pour la défragmentation (1-100%)

Politiques d'Éviction : Gérer maxmemory

Lorsque Redis est utilisé comme cache, il est crucial de définir ce qui se passe lorsque la mémoire atteint une limite prédéfinie. La directive maxmemory dans redis.conf définit cette limite, et maxmemory-policy dicte la stratégie d'éviction.

maxmemory 2gb # Définir la mémoire maximale à 2 gigaoctets
maxmemory-policy allkeys-lru # Évincer les clés les moins récemment utilisées parmi toutes les clés

Options courantes de maxmemory-policy :

  • noeviction : (Par défaut) Les nouvelles écritures sont bloquées lorsque maxmemory est atteint. Les lectures fonctionnent toujours. C'est bon pour le débogage mais généralement pas pour les caches de production.
  • allkeys-lru : Évince les clés les moins récemment utilisées (LRU) de tous les espaces de clés (clés avec ou sans expiration).
  • volatile-lru : Évince les clés LRU uniquement parmi celles qui ont une expiration définie.
  • allkeys-lfu : Évince les clés les moins fréquemment utilisées (LFU) de tous les espaces de clés.
  • volatile-lfu : Évince les clés LFU uniquement parmi celles qui ont une expiration définie.
  • allkeys-random : Évince aléatoirement les clés de tous les espaces de clés.
  • volatile-random : Évince aléatoirement les clés uniquement parmi celles qui ont une expiration définie.
  • volatile-ttl : Évince les clés avec le temps de vie (TTL) le plus court uniquement parmi celles qui ont une expiration définie.

Choisir la Bonne Politique :

  • Pour un cache général, allkeys-lru ou allkeys-lfu sont souvent de bons choix, selon que la récence ou la fréquence est un meilleur indicateur d'utilité pour vos données.
  • Si vous utilisez principalement Redis pour la gestion de sessions ou des objets avec des expirations explicites, volatile-lru ou volatile-ttl pourraient être plus appropriés.

Avertissement : Si maxmemory-policy est défini sur noeviction et que maxmemory est atteint, les opérations d'écriture échoueront, entraînant des erreurs d'application.

Choisir une Valeur maxmemory

Ne définissez pas maxmemory égal à la RAM totale du serveur. Laissez de la place pour le système d'exploitation, le surcoût du processus Redis, les tampons clients, le backlog de réplication, le copy-on-write de persistance, les agents de surveillance et l'accès SSH d'urgence.

Pour une instance Redis uniquement cache, un point de départ simple est de définir maxmemory en dessous de la RAM physique avec une marge confortable, puis de surveiller les métriques réelles pendant les pics de charge et la persistance en arrière-plan. Pour une instance avec beaucoup de persistance, laissez plus de marge car les snapshots RDB et les réécritures AOF peuvent temporairement augmenter la pression mémoire.

La configuration dangereuse est l'absence de limite sur un hôte partagé. Redis peut alors entrer en compétition avec le système d'exploitation et d'autres services jusqu'à ce que le tueur OOM du noyau décide ce qui meurt. Un maxmemory clair plus une politique d'éviction délibérée est plus facile à raisonner.

Les Grandes Clés Sont Aussi un Problème de Latence

La gestion mémoire ne concerne pas seulement le total d'octets. Une seule clé énorme peut nuire à des opérations qui semblent inoffensives.

Exemples :

redis-cli MEMORY USAGE huge:hash
redis-cli HLEN huge:hash
redis-cli LLEN queue:events

Supprimer une très grande clé avec DEL peut bloquer la boucle d'événements pendant que Redis libère la mémoire. Préférez UNLINK pour les grandes clés lorsque votre version de Redis le supporte :

redis-cli UNLINK huge:hash

UNLINK détache la clé et libère la mémoire de manière asynchrone. La clé disparaît rapidement de l'espace de clés, tandis que le travail coûteux de libération se fait en arrière-plan.

Pour les grandes collections, concevez autour de tailles limitées. Tronquez les flux et les listes. Divisez les grands hachages par locataire, intervalle de temps ou type d'objet lorsque cela correspond au modèle d'accès. Un hachage d'un million de champs peut économiser un certain surcoût de clé de premier niveau, mais il peut devenir difficile à migrer, expirer, inspecter ou supprimer.

Persistance et Surcoût Mémoire

Les mécanismes de persistance de Redis (RDB et AOF) interagissent également avec la mémoire :

  • Snapshots RDB : Lorsque Redis sauvegarde un fichier RDB, il fork un processus enfant. Pendant le processus de snapshot, toute écriture dans l'ensemble de données Redis par le parent entraînera la duplication des pages mémoire en raison du copy-on-write (CoW). Cela peut temporairement doubler l'empreinte mémoire, en particulier sur les instances chargées avec des sauvegardes RDB fréquentes.
  • Réécriture AOF : De même, lorsque le fichier AOF est réécrit (par exemple, BGREWRITEAOF), un fork se produit, entraînant une duplication mémoire temporaire. Le tampon AOF lui-même consomme également de la mémoire.

Astuce : Planifiez les sauvegardes RDB et les réécritures AOF pendant les heures creuses si possible, ou assurez-vous que votre serveur dispose de suffisamment de RAM libre pour gérer le surcoût CoW.

Libération Paresseuse

Redis 4.0 a introduit la libération paresseuse (suppression non bloquante) pour empêcher le blocage du serveur lors de la suppression de grandes clés ou du vidage de bases de données. Au lieu de récupérer la mémoire de manière synchrone, Redis peut placer la tâche de libération de la mémoire dans un thread d'arrière-plan.

  • lazyfree-lazy-eviction yes : Libère la mémoire de manière asynchrone lors de l'éviction.
  • lazyfree-lazy-expire yes : Libère la mémoire de manière asynchrone lorsque les clés expirent.
  • lazyfree-lazy-server-del yes : Libère la mémoire de manière asynchrone pour les suppressions côté serveur dans les chemins supportés.
  • lazyfree-lazy-user-del yes : Fait en sorte que DEL émis par l'utilisateur se comporte davantage comme UNLINK sur les versions de Redis qui le supportent.

Activez la libération paresseuse avec précaution sur les instances chargées pour réduire les pics de latence dus à la récupération mémoire synchrone. Ensuite, surveillez lazyfree_pending_objects ; s'il reste élevé, le travail de libération en arrière-plan ne suit pas.

Tampons Clients et Pipelining

Le pipelining, bien qu'étant principalement une technique d'optimisation réseau, peut influencer indirectement les performances mémoire en rendant le traitement des commandes plus efficace. En envoyant plusieurs commandes à Redis en un seul aller-retour, il réduit la latence réseau et le surcoût CPU par commande côté client et serveur. Cela permet à Redis de traiter plus d'opérations par seconde sans accumuler de grandes files d'attente de commandes, ce qui pourrait autrement entraîner une utilisation mémoire plus élevée dans les tampons clients ou un traitement plus lent qui stresse l'allocateur mémoire au fil du temps.

Le pipelining peut améliorer le débit, mais des pipelines illimités peuvent augmenter les tampons de sortie clients. Un client qui envoie un énorme pipeline et lit les réponses lentement peut amener Redis à conserver une grande quantité de sortie en attente.

Surveillez les métriques des tampons clients dans INFO clients et configurez des limites raisonnables pour les clients normaux, réplicas et pub/sub :

client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60

Ce sont des exemples de valeurs par défaut, pas des recommandations universelles. L'essentiel est d'éviter une croissance illimitée pour les clients qui peuvent prendre du retard.

Une Routine Utile de Révision Mémoire

Lorsqu'une instance Redis commence à utiliser plus de mémoire que prévu, allez du général au spécifique :

  1. Vérifiez INFO memory pour used_memory, RSS, fragmentation, statistiques d'allocateur et libérations paresseuses en attente.
  2. Vérifiez INFO keyspace pour voir si une base de données ou une famille de clés croît.
  3. Échantillonnez les grandes clés avec MEMORY USAGE, SCAN et des commandes de longueur spécifiques au type.
  4. Confirmez que les clés de type cache ont des TTL.
  5. Examinez les déploiements récents pour de nouveaux noms de clés, des valeurs plus longues ou des expirations manquantes.
  6. Vérifiez le timing de persistance et la pression mémoire liée au fork.
  7. Examinez les tampons clients si la mémoire augmente lors des pics de trafic.

Par exemple, si la croissance mémoire suit une nouvelle version de fonctionnalité, recherchez de nouvelles clés sans expiration :

redis-cli --scan --pattern 'feature-x:*' | head
redis-cli TTL feature-x:example

Un TTL de -1 signifie que la clé n'a pas d'expiration. Cela peut être correct pour des données durables. C'est généralement faux pour des données de cache.

Les problèmes mémoire de Redis sont plus faciles à résoudre avant que l'instance ne soit pleine. Définissez un maxmemory délibéré, choisissez une politique d'éviction qui correspond au rôle de l'instance, gardez les clés de cache avec des TTL, évitez les clés surdimensionnées et laissez de la marge pour les forks et les tampons. Ensuite, examinez les métriques mémoire réelles après chaque changement majeur de forme de données. Redis restera rapide lorsque son comportement mémoire sera ennuyeux.