...

Défragmentation active de Redis : une optimisation efficace de la mémoire Redis contre la fragmentation de la mémoire

La défragmentation de Redis réduit l'empreinte mémoire réelle en fragmentation de la mémoire réduirait pendant le fonctionnement et éviterait ainsi les valeurs aberrantes lors de la RSS J'évite cela. Je maintiens ainsi les latences à un niveau constant, je réduis les coûts et j'obtiens une optimisation fiable de la mémoire Redis sans redémarrage.

Points centraux

  • Active La défragmentation s'effectue en ligne et déplace les objets progressivement.
  • INFO memory fournit des indicateurs permettant d'analyser les tendances et les seuils.
  • Configuration contrôle le budget CPU, la profondeur de balayage et les seuils de déclenchement.
  • Modèle de données et l'optimisation du cache limitent durablement la fragmentation.
  • Suivi et les alertes permettent d'éviter les mauvaises surprises coûteuses.

Pourquoi la fragmentation de la mémoire se produit-elle dans Redis ?

Je travaille avec une base de données en mémoire qui contient des objets plus varié La taille est constamment créée, modifiée et supprimée ; ce faisant, la mémoire RAM libre se fragmente progressivement en petits blocs. Ces blocs sont suffisants au total, mais ne sont pas contigus, ce qui fait grimper considérablement la mémoire RSS au-delà des données utiles et ainsi Coûts et augmente les latences. Par défaut, Redis utilise jemalloc, qui gère la mémoire par classes, « runs » et pages, ce qui peut entraîner la création de pages partiellement remplies. Lorsque de nombreuses pages partiellement remplies de ce type existent, l'écart entre used_memory et RSS augmente de manière visible. C'est précisément à ce stade que l'instance perd en efficacité, bien que je ne stocke aucun contenu supplémentaire. La défragmentation active s'attaque spécifiquement à ce schéma et nettoie le tas en douceur.

Fonctionnement interne de la défragmentation active

À partir de Redis 4.0, la défragmentation en ligne déplace les éléments candidats depuis mince déplace les runs occupés vers des zones plus densément occupées et libère les anciennes pages. J’en tire profit, car ce travail s’effectue par cycles courts, ce qui évite les pics de latence. Avant chaque étape, Redis vérifie des métriques telles que mem_fragmentation_ratio et allocator_frag_ratio par rapport aux seuils configurés. En cas de fragmentation suffisante, le processus analyse l’espace de clés par tranches et migre les objets appropriés, tout en respectant la limite spécifiée CPU- Le budget est respecté. Ce processus se répète en continu jusqu’à ce que le rapport entre le RSS et le tas se normalise. Cela permet de réduire l’empreinte sans que je doive prévoir un redémarrage.

INFO memory : bien interpréter les indicateurs clés

Avant d'intervenir, je lis le INFO Je surveille les valeurs de mémoire et m'intéresse davantage aux tendances qu'aux mesures ponctuelles. Le mem_fragmentation_ratio m'indique le rapport entre le RSS et le tas utilisé ; des valeurs comprises entre 1,0 et 1,5 semblent souvent non critiques, mais des pics persistants au-delà de cette fourchette méritent une attention particulière. La valeur `mem_fragmentation_bytes` me permet d’identifier le potentiel d’économie absolu, ce qui est essentiel pour une évaluation objective des coûts. Les valeurs `allocator_frag_ratio` et `allocator_frag_bytes` fournissent un contexte supplémentaire sur le fonctionnement de l’allocateur. Si active_defrag_running est en cours d’exécution, je vois immédiatement si la défragmentation est réellement active et si elle mobilise du CPU. C’est sur la base de ces faits que je prends des décisions, plutôt que de me fier à mon intuition, et je mets ainsi cache un réglage ciblé.

Métriques Description valeur indicative Action
mem_fragmentation_ratio RSS sur l'utilisation interne du tas ≈ 1,0–1,5 : normal ; > 1,5 : à vérifier Surveiller la tendance ; à partir de > 1,5, approfondir l'analyse
mem_fragmentation_bytes Fragmentation absolue en octets Pertinent à partir d'environ 100 Mo par instance Évaluer le potentiel, envisager une défragmentation
ratio_allocateur_fragmentation Fragmentation du tas selon l'allocateur > 1,4 indique qu'il faut agir Activer la défragmentation, affiner les paramètres
allocator_frag_bytes Overhead absolu de l'allocateur Chiffres élevés, allant de deux à trois chiffres en Mo Adapter le budget alloué au processeur en fonction de son potentiel
active_defrag_running État et activité de la défragmentation 0/1 selon l'état Vérifier les latences et le débit à 1

Configuration : valeurs initiales recommandées et effets

J'enclenche activedefrag Je le lance de manière ciblée et je définis des valeurs de départ prudentes afin que le processus démarre en douceur. Grâce à l'option `active-defrag-ignore-bytes` (par exemple 100 Mo), j'évite tout travail inutile sur les petits tas. Les seuils « active-defrag-threshold-lower » (par exemple 10) et « -upper » (par exemple 100) définissent à partir de quand la défragmentation démarre et quand elle atteint sa vitesse maximale. Je contrôle la fenêtre CPU via active-defrag-cycle-min (par exemple 1) et -max (par exemple 25), tandis que active-defrag-max-scan-fields limite la profondeur d'analyse dans les types de données structurés. Pour avoir un aperçu rapide des relations entre les différents paramètres de réglage, j’aime m’appuyer sur des connaissances de base concises telles que Gestion de la mémoire dans Redis. Après les premières mesures, j'ajuste les valeurs progressivement jusqu'à obtenir un bon équilibre entre les latences et les gains ; celles-ci Réglage Je l'enregistre ensuite de manière permanente dans le fichier redis.conf.

Garder un œil sur le budget CPU et les latences

Je suis conscient que la défragmentation sollicite le processeur, c'est pourquoi je vérifie Latence et le débit immédiatement après l'activation. Si les valeurs P99 augmentent, je réduis le paramètre « active-defrag-cycle-max » ou je déplace l'opération vers des plages horaires moins chargées. De plus, j'allège la charge de travail principale en déplaçant les libérations de manière asynchrone, ce qui réduit la durée des opérations individuelles. Des compléments utiles tels que Redis Lazy Free Je supprime la mémoire utilisée en arrière-plan, ce qui allège sensiblement la charge du thread principal. Je vérifie également si les durées d'exécution longues sont dues à des clés ou à des structures spécifiques, et j'optimise en priorité les modèles de données concernés. Je préserve ainsi l'équilibre entre les économies réalisées et Débit.

Bonnes pratiques pour une mise en production

J'évalue le niveau de fragmentation avant d'agir, et je prends en compte tous les Métriques issu du même échantillon, afin que les proportions soient correctes. Un mem_fragmentation_ratio inférieur à 1,0 signale un risque de pagination par le noyau ; dans ce cas, je vérifie la RAM et le paramètre « swappiness », plutôt que de considérer la défragmentation comme une panacée. Pour détecter une véritable fragmentation, je définis des limites inférieures et supérieures réalistes et je surveille la valeur « allocator_frag_bytes » comme indicateur d’une récupération utile. Pendant les premières minutes suivant l’activation, j’observe attentivement le nombre d’erreurs, les latences et les délais d’expiration. Si des effets secondaires apparaissent, je réduis le budget CPU ou je mets Defrag en pause jusqu’à ce que j’aie trouvé la cause. Fonctionnement stable Valeurs Je les documente et les enregistre dans le fichier redis.conf ou dans des modèles d'automatisation.

Des modèles de données structurés pour lutter contre la fragmentation

Je commence par réduire les frais généraux au niveau des Clés En effet : les identifiants plus courts permettent d'économiser des octets par entrée et réduisent la dispersion. Pour les structures d'objets, je préfère utiliser des hachages plutôt que de nombreuses clés individuelles, car Redis compresse étroitement les petits champs de hachage. Pour les valeurs sérialisées, j'opte pour des formats binaires tels que MessagePack plutôt que pour de longues chaînes JSON. Je minimise les contenus volumineux et facilement compressibles à l’aide d’algorithmes légers tels que Snappy, afin de réduire la fréquence des réallocations. De plus, je définis des TTL partout où les données deviennent obsolètes, afin que l’espace de clés ne croisse pas de manière incontrôlée. L’ensemble de ces choix réduit la charge de défragmentation ultérieure et maintient le tas compact.

Mise en place de la surveillance et des alertes

J'intègre mem_fragmentation_ratio, allocator_frag_ratio, used_memory et active_defrag_running dans mon Suivi et j'enregistre des courbes d'évolution. Je ne déclenche pas les seuils de manière rigide, mais je les associe à des tendances sur des fenêtres temporelles, afin que les pics à court terme ne dictent pas le planning. Je nomme les alertes de manière univoque et je complète les guides d'intervention qui décrivent les réactions possibles. Parmi ces réactions, on compte l’activation de la défragmentation, l’ajustement des fenêtres CPU, la vérification du modèle de données et l’optimisation du système avant l’apparition d’effets de swap. De plus, je sépare les métriques par instance afin qu’aucune valeur aberrante n’échappe à mon attention. Grâce à cette rigueur, j’identifie les risques à un stade précoce et je maintiens la Performance planifiable.

Prendre en compte de manière ciblée la persistance et le « copy-on-write »

Je prévois une défragmentation dans le cadre de BGSAVE et de la réécriture AOF, car les opérations de fork déclenchent le „ copy-on-write “ (CoW). Chaque page qui change après le fork est dupliquée : plus le tas est fragmenté et « sale », plus les besoins supplémentaires sont importants. C’est pourquoi je préfère lancer la défragmentation avant fenêtres de persistance planifiées, afin de créer des pages denses et de réduire l'amplification CoW. De plus, je prévois une marge opérationnelle : en fonction du rythme des mutations, je prévois 20 à 50 % en plus du tas utilisé, afin que les sauvegardes RDB et les réécritures AOF s’effectuent sans OOM. Les tampons de réplication, les tampons de sortie client et les tampons de réécriture AOF sont pris en compte dans cette réserve. Résultat : des fenêtres de persistance plus courtes, moins de pics RSS et des latences plus stables pendant la sauvegarde.

Réglage fin de Jemalloc et influence du système d'exploitation

Je vérifie si jemalloc s'exécute avec un thread d'arrière-plan actif qui libère des pages. La purge en arrière-plan et des paramètres de „ decay “ adaptés garantissent que la mémoire libérée parvienne bien au noyau et ne reste pas indéfiniment „ fuzzy “/« dirty ». Je désactive les « Transparent Huge Pages », car elles nuisent généralement aux charges de travail Redis et alourdissent le CoW. J’évite systématiquement le swapping ; je considère un mem_fragmentation_ratio < 1,0 comme un signal d’alerte et je vérifie les paramètres système avant d’intervenir sur Redis. Mon objectif est d’obtenir un couplage étroit entre le tas et le RSS : Defrag nettoie, jemalloc libère de la mémoire, et le système d’exploitation réintègre rapidement les pages – sans contretemps inattendus lors d’un nouvel accès.

L'optimisation spécifique aux types de données dans la pratique

J'utilise systématiquement les représentations compactes : les hachages et les ensembles triés restent longtemps compacts grâce aux formats listpack, à condition que je définisse correctement les limites. Les listes bénéficient des compressions Quicklist, et les ensembles d'intsets, à condition qu'ils ne contiennent que des entiers. Je « trim » régulièrement les flux (par exemple via XTRIM) afin d’éviter une croissance infinie et les réallocations. Pour les ZSET comportant peu d’entrées, je calcule des limites de compression plus élevées ; pour les très grands ZSET, je les réduis à nouveau afin de limiter les recompressions coûteuses. Ce réglage fin réduit le nombre et la variance des petites allocations – c’est précisément là que la fragmentation survient souvent. Ce qui reste important : je mesure d’abord les tailles réelles des objets et les taux de croissance, puis j’ajuste les seuils, au lieu de me contenter d’optimiser au feeling.

Maxmemory, éviction et marge opérationnelle

Je configure maxmemory de manière à ce qu’il puisse accueillir non seulement les données utiles, mais aussi les surcoûts, la réplication, les pics de CoW et la fragmentation. Les politiques d’éviction influencent la dynamique d’allocation : les politiques LRU/LFU procèdent à des évictions plus fréquentes, créant ainsi des espaces vides plus petits, tandis que la politique „ noeviction “ augmente le risque d’erreurs graves en cas de manque d’espace disponible. Mon approche : des seuils réalistes et une politique adaptée au profil d’accès. Je surveille également les tampons liés aux clients, les pics Pub/Sub et les pics SCRIPT/Pipeline – ces trois éléments peuvent faire gonfler la mémoire à court terme. La défragmentation elle-même fonctionne de manière plus efficace lorsqu’elle n’est pas accompagnée d’évictions ; c’est pourquoi je choisis des fenêtres où la charge est stable ou que je réduis le budget de défragmentation pendant les phases de pics de charge identifiables.

Sharding, réplication et défragmentation progressive

Je préfère évoluer horizontalement avant qu'une seule instance ne soit à bout de souffle. Plusieurs shards de taille moyenne ont généralement tendance à se fragmenter moins qu'un énorme processus contenant des objets très hétérogènes. Dans les configurations répliquées, j’effectue la défragmentation progressivement, selon une approche « rolling » : je commence par alléger la charge de la réplique et la vérifier, puis je procède au basculement et je nettoie l’ancien maître. Cela me permet de maintenir la stabilité des chemins d’accès des utilisateurs et de réduire les risques. Pour les clusters, je tiens également compte de la répartition des slots : des « hot keys » hétérogènes concentrés sur un petit nombre de shards entraînent un comportement d’allocation inégal et donc des profils de fragmentation différents. Une répartition équilibrée des slots atténue visiblement ces effets.

Stratégie de test, profils de charge et activation sécurisée

Je simule des profils de charge réalistes : écriture dominante, lecture intensive, insertions en rafale, séquences TTL… tout ce qui se produit au quotidien. En environnement de test, j’active d’abord Defrag de manière prudente et je mesure les latences P50/P95/P99, le débit, la durée des forks et l’évolution de mem_fragmentation_bytes. Ensuite, j’augmente le budget CPU par petits paliers. Je modifie les configurations en temps réel avec CONFIG SET, mais je prévois toujours des plans de secours. Je consigne quand et avec quels paramètres Defrag a été exécuté, afin que les corrélations avec les métriques soient fiables. Important : je teste également la désactivation. Lorsque Defrag est mis en pause, les latences ne doivent pas „ se stabiliser “ de manière permanente. C’est la seule façon de prouver que l’optimisation est réellement efficace et ne se contente pas de déplacer les symptômes.

Cas limites et obstacles connus

Je m'attends à des situations dans lesquelles la défragmentation n'aura que peu d'effet : des tailles d'objets très homogènes, des objets individuels gigantesques ou des charges de travail qui, en raison d'une mutation élevée et constante, annulent immédiatement tout effet de consolidation. Les modules qui gèrent leur propre mémoire en dehors de jemalloc échappent à ce mécanisme – mon optimisation n’y a qu’un effet indirect. Un autre cas classique est celui des structures „ vides “ mais gigantesques qui conservent une surcharge administrative (par exemple, de grands ensembles après une suppression massive). Dans de tels cas, la refactorisation du modèle de données s’avère plus efficace que n’importe quel budget de défragmentation. Enfin, je vérifie si je ne freine pas par inadvertance la défragmentation : profondeur de balayage trop faible, valeurs cycle-max trop basses ou seuils qui ne sont jamais atteints. Ce n’est qu’une fois ces obstacles levés que je peux espérer de réels gains de performance.

Dépannage : quand un redémarrage est-il judicieux ?

Si la défragmentation stagne alors que la valeur « allocator_frag_ratio » reste élevée, j'envisage de procéder à une Commutations ou un redémarrage rapide. Dans les configurations à haute disponibilité, un basculement planifié remplace l'instance active, et le processus nouvellement chargé démarre avec un tas (heap) dense. Je vérifie également si le serveur fonctionne bien avec jemalloc, car sans cet allocateur, la défragmentation active ne fonctionne pas. Pour mieux comprendre les mécanismes de dispersion de la mémoire, je me réfère à des articles clairs sur Fragmentation de la mémoire. Avant chaque redémarrage, je sauvegarde les dernières valeurs mesurées afin d'évaluer objectivement l'efficacité. Ce n'est que lorsque la mesure et l'effet concordent que je considère l'incident comme résolu et que je note Effets d'apprentissage pour l'avenir.

Résumé en bref

J'utilise Active Défragmentation, afin de limiter le flux RSS à un niveau raisonnable sans risquer d'interrompre le service. Des seuils clairs, des valeurs de départ prudentes et un budget CPU transparent permettent de garantir la réactivité du service. Un modèle de données adapté, avec des clés compactes, des hachages, une sérialisation binaire et des TTL cohérents, réduit les tâches de nettoyage ultérieures. Une bonne surveillance, associée à des alertes pertinentes, guide mes interventions et évite les mauvaises surprises. Si la défragmentation ne résout pas le problème, je planifie délibérément le basculement et le redémarrage, plutôt que de m'en remettre au hasard. Je réalise ainsi des économies de RAM et maintiens les latences constant et exploite Redis de manière fiable, avec des avantages tangibles en termes de coûts et d'expérience utilisateur.

Derniers articles