Compression des données dans Apache Ignite. Expérience de Sber

Compression des données dans Apache Ignite. Expérience de SberLorsque vous travaillez avec de grands volumes de données, le problème du manque d'espace sur les disques peut parfois devenir aigu. L'une des solutions à ce problème est la compression, qui permet d'augmenter la capacité de stockage sur le même équipement. Dans cet article, nous allons examiner comment fonctionne la compression des données dans Apache Ignite. Nous aborderons uniquement les méthodes de compression mises en œuvre dans le produit. D'autres méthodes de compression des données (sur le réseau, en mémoire), qu'elles soient réalisées ou non, resteront en dehors du cadre de cet article.

Ainsi, en mode persistance activé, lorsqu'il y a des modifications des données dans les caches, Ignite commence à écrire sur disque :

  1. Le contenu des caches
  2. Le Journal d'écriture anticipée (Write Ahead Log, ci-après WAL)

Un mécanisme existe depuis assez longtemps pour compresser le WAL, appelé WAL compaction. Dans la récente version d'Apache Ignite 2.8, deux nouveaux mécanismes ont été ajoutés pour permettre la compression des données sur le disque : la compression des pages disque pour compresser le contenu des caches et la compression des instantanés de pages WAL pour compresser certaines entrées du WAL. Plus de détails sur ces trois mécanismes ci-dessous.

Compression des pages disque

Comment cela fonctionne

Pour commencer, faisons un bref rappel sur la manière dont Ignite stocke les données. La mémoire est utilisée sous forme de pages. La taille de la page est définie au démarrage du nœud et ne peut pas être modifiée ultérieurement, de plus, la taille de la page doit être une puissance de deux et multiple de la taille du bloc du système de fichiers. Les pages sont chargées dans la RAM à partir du disque au besoin, la taille des données sur le disque peut dépasser la quantité de RAM allouée. En cas de manque d'espace dans la RAM pour charger une page à partir du disque, de vieilles pages déjà inutilisées seront évincées de la RAM.

Sur le disque, les données sont stockées de la manière suivante : pour chaque partition de chaque groupe de caches, un fichier distinct est créé ; dans ce fichier, les pages suivent l'ordre croissant des indices. L'identifiant complet d'une page contient l'identifiant du groupe de caches, le numéro de la partition et l'indice de la page dans le fichier. Ainsi, grâce à cet identifiant complet, nous pouvons précisément identifier le fichier et l'offset du fichier pour chaque page. Vous pouvez lire davantage sur l'architecture de la mémoire paginée dans l'article sur le Wiki d'Apache Ignite : Ignite Persistent Store — sous le capot.

Le mécanisme de compression des pages disque, comme on peut le deviner par son nom, fonctionne à un niveau de page. Lors de l'activation de ce mécanisme, les opérations sur les données en RAM se font telles quelles, sans aucune compression, mais au moment de la sauvegarde des pages de la RAM sur le disque, leur compression est effectuée.

Cependant, compresser chaque page individuellement n'est pas encore une solution au problème, il faut de quelque manière réduire la taille des fichiers de données finaux. Si la taille de la page cesse d'être fixe, nous ne pouvons plus écrire les pages dans le fichier les unes après les autres, car cela peut engendrer toute une série de problèmes :

  • Nous ne serons pas en mesure de calculer l'offset de la page dans le fichier à l'aide de l'index de la page.
  • Il n'est pas clair comment gérer les pages qui ne se trouvent pas à la fin du fichier et dont la taille change. Si la taille de la page diminue, l'espace qu'elle a libéré est perdu. Si la taille de la page augmente, il faut déjà chercher un nouvel emplacement pour elle dans le fichier.
  • Si la page se déplace d'un nombre de bytes non multiple de la taille du bloc du système de fichiers, alors pour sa lecture ou son écriture, il faudra toucher à un bloc de système de fichiers de plus, ce qui peut entraîner une dégradation des performances.

Pour éviter de résoudre ces problèmes à son propre niveau, la compression des pages disque dans Apache Ignite utilise un mécanisme de système de fichiers appelé fichiers sparse. Un fichier sparse est un fichier dans lequel certaines régions remplies de zéros peuvent être marquées comme « trous ». Dans ce cas, aucun bloc de système de fichiers ne sera alloué pour stocker ces trous, ce qui permet d'économiser de l'espace sur le disque.

Il est logique que pour libérer un bloc de système de fichiers, la taille du trou doit être supérieure ou égale à celle du bloc de système de fichiers, ce qui impose une limitation supplémentaire sur la taille de la page dans Apache Ignite : pour que la compression ait un effet, il est nécessaire que la taille de la page soit strictement supérieure à la taille du bloc de système de fichiers. Si la taille de la page est égale à celle du bloc, nous ne pourrons jamais libérer aucun bloc, car pour libérer un unique bloc, il faudrait que la page compressée occupe 0 byte. Si la taille de la page est égale à celle de 2 ou 4 blocs, nous pourrons déjà libérer au moins un bloc si notre page se compresse d'au moins 50 % ou 75 % respectivement.

Ainsi, la description finale du fonctionnement du mécanisme est la suivante : lors de l'enregistrement d'une page sur le disque, une tentative de compression de la page est effectuée. Si la taille de la page compressée permet de libérer un ou plusieurs blocs du système de fichiers, la page est enregistrée sous forme compressée, et un « trou » est créé à la place des blocs libérés (un appel système est effectué avec le drapeau « punch hole »). fallocate() Si la taille de la page compressée ne permet pas de libérer des blocs, la page est conservée telle quelle, sous forme non compressée. Tous les offsets des pages sont calculés de la même manière que sans compression, en multipliant l'index de la page par la taille de celle-ci. Aucun déplacement des pages par ses propres moyens n'est nécessaire. Les offsets des pages, comme sans compression, tombent sur les frontières des blocs du système de fichiers.

Compression des données dans Apache Ignite. Expérience de Sber

Dans l'implémentation actuelle, Ignite peut travailler avec des fichiers sparse uniquement sous OS Linux, par conséquent, la compression des pages disque ne peut être activée que lors de l'utilisation d'Ignite sur ce système d'exploitation.

Les algorithmes de compression qui peuvent être utilisés pour la compression des pages disque sont : ZSTD, LZ4, Snappy. De plus, il existe un mode de fonctionnement (SKIP_GARBAGE), dans lequel seule la mémoire inutilisée de la page est supprimée sans appliquer de compression sur les données restantes, ce qui permet de réduire la charge sur le CPU par rapport aux algorithmes mentionnés précédemment.

Impact sur la performance

Malheureusement, je n'ai pas effectué de mesure de performance sur des environnements réels, car nous ne prévoyons pas d'utiliser ce mécanisme en production, mais nous pouvons théoriquement discuter des pertes et des gains potentiels.

Pour cela, rappelons comment la lecture et l'écriture des pages sont effectuées lors de leurs accès :

  • Lors de l'opération de lecture, la recherche est d'abord effectuée dans la RAM, et si la recherche échoue, la page est chargée dans la RAM depuis le disque par le même thread qui effectue la lecture.
  • Lors de l'opération d'écriture, la page dans la RAM est marquée comme sale, et l'enregistrement physique de la page sur le disque n'est pas immédiatement effectué dans le thread qui effectue l'écriture. Toutes les pages sales sont enregistrées sur le disque plus tard dans le processus de checkpoint par des threads séparés.

Ainsi, l'impact sur les opérations de lecture est :

  • Positif (IO disque), grâce à la réduction du nombre de blocs du système de fichiers lus.
  • Négatif (CPU) en raison de la charge supplémentaire nécessaire au système d'exploitation pour travailler avec des fichiers sparse. Il se peut également qu'il y ait des opérations d'E/S supplémentaires pour sauvegarder une structure de fichier sparse plus complexe (malheureusement, je ne connais pas tous les détails concernant le fonctionnement des fichiers sparse).
  • Négatif (CPU) en raison de la nécessité de décompresser les pages.
  • Aucun impact sur les opérations d'écriture.
  • Impact sur le processus de checkpoint (similaire aux opérations de lecture) :
  • Positif (disk IO) grâce à la réduction du nombre de blocs écrits dans le système de fichiers.
  • Négatif (CPU, peut-être disk IO) à cause du travail avec des fichiers sparse.
  • Négatif (CPU) en raison de la nécessité de compresser les pages.

Quelle balance l'emportera ? Tout cela dépend beaucoup de l'environnement, mais je penche plutôt vers le fait que la compression des pages disque entraînera plutôt une dégradation des performances dans la plupart des systèmes. D'autant plus que les tests effectués sur d'autres SGBD utilisant cette approche avec des fichiers sparse montrent une baisse des performances lorsque la compression est activée.

Comment activer et configurer

Comme déjà mentionné ci-dessus, la version minimale d'Apache Ignite supportant la compression des pages disque est 2.8 et elle est uniquement supportée par le système d'exploitation Linux. L'activation et la configuration se font comme suit :

  • Le class-path doit inclure le module ignite-compression. Par défaut, il se trouve dans la distribution d'Apache Ignite dans le répertoire libs/optional et n'est pas inclus dans le class-path. Vous pouvez simplement déplacer le répertoire d'un niveau vers le haut dans libs, et alors lors du lancement via ignite.sh, il sera automatiquement inclus.
  • La persistance doit être activée (S'active via DataRegionConfiguration.setPersistenceEnabled(true)).
  • La taille de la page doit être supérieure à la taille du bloc du système de fichiers (peut être définie avec DataStorageConfiguration.setPageSize() ).
  • Pour chaque cache dont les données doivent être compressées, il est nécessaire de configurer la méthode de compression et (facultativement) le niveau de compression dans la configuration (méthodes CacheConfiguration.setDiskPageCompression(), CacheConfiguration.setDiskPageCompressionLevel()).

Compactage WAL

Comment cela fonctionne

Qu'est-ce que le WAL et à quoi sert-il ? En résumé : c'est un journal où se trouvent tous les événements modifiant finalement le stockage page. Il est nécessaire principalement pour permettre la récupération en cas de panne. Toute opération, avant de restituer le contrôle à l'utilisateur, doit d'abord enregistrer un événement dans le WAL pour, en cas de panne, pouvoir rejouer les journaux et récupérer toutes les opérations pour lesquelles l'utilisateur a reçu une réponse réussie, même si ces opérations n'ont pas eu le temps de se refléter dans le stockage page sur le disque (comme déjà mentionné, l'enregistrement effectif dans le stockage page se fait dans un processus appelé « point de contrôle » avec un certain retard par des threads distincts).

Les enregistrements dans le WAL se divisent en enregistrements logiques et physiques. Les logiques sont les clés et les valeurs elles-mêmes. Les physiques reflètent les changements des pages dans le stockage page. Alors que les enregistrements logiques peuvent également être utiles dans d'autres cas, les enregistrements physiques sont nécessaires uniquement pour la récupération en cas de panne et ne concernent que les enregistrements depuis le dernier point de contrôle réussi. Nous n'allons pas entrer ici dans les détails et expliquer pourquoi cela fonctionne de cette manière, mais ceux qui sont intéressés peuvent se référer à l'article déjà mentionné sur le Wiki d'Apache Ignite : Ignite Persistent Store — sous le capot.

Souvent, un enregistrement logique correspond à plusieurs enregistrements physiques. Par exemple, une opération put dans le cache touche plusieurs pages dans la mémoire page (la page avec les données elles-mêmes, les pages avec les index, les pages avec les listes libres). Dans certains tests synthétiques, j'ai constaté que les enregistrements physiques occupaient jusqu'à 90 % du volume du fichier WAL. Cependant, ils ne sont nécessaires que pendant une période très courte (par défaut, l'intervalle entre les points de contrôle est de 3 minutes). Il serait logique de se débarrasser de ces données après qu'elles aient perdu leur pertinence. C'est précisément ce que fait le mécanisme de compaction du WAL, en se débarrassant des enregistrements physiques et en compressant avec zip les enregistrements logiques restants, ce qui réduit considérablement la taille du fichier (parfois plusieurs fois).

Le WAL physique est composé de plusieurs segments (par défaut 10) de taille fixe (par défaut 64 Mo) qui sont écrasés en boucle. Une fois que le segment actuel est rempli, le segment suivant lui est attribué, et le segment rempli est copié dans l'archive par un flux séparé. La compaction du WAL fonctionne déjà avec les segments archivé. De plus, par un flux distinct, elle suit l'exécution des points de contrôle et commence la compression des segments archivés, les enregistrements physiques pour lesquels ne sont plus nécessaires.

Compression des données dans Apache Ignite. Expérience de Sber

Impact sur la performance

Étant donné que la compaction du WAL fonctionne par un flux séparé, il ne devrait pas y avoir d'impact direct sur les opérations en cours. Cependant, cela engendre une charge supplémentaire en arrière-plan sur le CPU (compression) et sur le disque (lecture de chaque segment de WAL depuis l'archive et écriture des segments compressés). Par conséquent, si le système fonctionne à pleine capacité, cela entraînera également une dégradation des performances.

Comment activer et configurer

La compaction du WAL peut être activée avec la propriété WalCompactionEnabled dans DataStorageConfiguration (DataStorageConfiguration.setWalCompactionEnabled(true)). De plus, avec la méthode DataStorageConfiguration.setWalCompactionLevel(), vous pouvez définir le niveau de compression si la valeur par défaut (BEST_SPEED) ne vous convient pas.

Compression des instantanés de page WAL

Comment cela fonctionne

Nous avons déjà établi que les enregistrements WAL se divisent en enregistrements logiques et physiques. Pour chaque modification d'une page dans la mémoire des pages, un enregistrement physique WAL est créé. Les enregistrements physiques, à leur tour, se divisent en deux sous-types : l'enregistrement de snapshot de page et l'enregistrement delta. Chaque fois que nous modifions quelque chose sur la page et que nous la transformons d'un état propre à un état sale, une copie complète de cette page est conservée dans le WAL (l'enregistrement de snapshot de page — le snapshot de la page). Même si nous ne changeons qu'un seul octet, le WAL sauvegardera un enregistrement légèrement supérieur à la taille de la page. Si nous modifions quelque chose sur une page déjà sale, un enregistrement delta est créé dans le WAL, où seules les modifications par rapport à l'état précédent de la page sont reflétées, mais pas la page entière. Étant donné que le rétablissement des pages de l'état sale à l'état propre se produit lors du processus de checkpoint, presque tous les enregistrements physiques consisteront uniquement en snapshots de pages immédiatement après le début du checkpoint (puisque toutes les pages sont immédiatement propres après le début du checkpoint). Ensuite, à mesure que nous nous rapprochons du prochain checkpoint, la proportion d'enregistrements delta commence à augmenter et revient à zéro au début du prochain checkpoint. Des mesures sur certains tests synthétiques ont montré que la proportion de snapshots de pages dans le volume total des enregistrements physiques atteint 90%.

L'idée de la compression des snapshots de page WAL consiste à compresser les snapshots de pages en utilisant un outil de compression de pages déjà disponible (voir compression des pages disque). Dans ce cas, les enregistrements WAL sont conservés de manière séquentielle en mode append-only et il n'y a pas besoin d'ancrer les enregistrements aux limites des blocs du système de fichiers. Ainsi, contrairement au mécanisme de compression des pages disque, nous n'avons pas besoin de fichiers épars, ce qui signifie que ce mécanisme fonctionnera non seulement sur les systèmes d'exploitation Linux. De plus, il n'est plus important de savoir dans quelle mesure nous avons pu compresser une page. Même si nous avons libéré 1 octet, c'est déjà un résultat positif et nous pouvons sauvegarder des données compressées dans le WAL, contrairement à la compression des pages disque, où nous ne sauvegardons une page compressée que si nous avons libéré plus d'un bloc du système de fichiers.

Les pages sont des données hautement compressibles, et leur part dans le volume total du WAL est très élevée. En ne modifiant pas le format du fichier WAL, nous pouvons obtenir une réduction significative de sa taille. La compression des enregistrements logiques nécessiterait un changement de format et entraînerait une perte de compatibilité, notamment pour les consommateurs externes qui pourraient être intéressés par les enregistrements logiques, sans apporter de réduction importante de la taille du fichier.

Comme pour la compression des pages disque, des algorithmes de compression tels que ZSTD, LZ4, Snappy, ainsi que le mode SKIP_GARBAGE, peuvent être utilisés pour la compression des instantanés de page WAL.

Impact sur la performance

Il est facile de remarquer que l'activation directe de la compression des instantanés de page WAL n'affecte que les flux qui écrivent des données en mémoire page, c'est-à-dire les flux qui modifient des données dans les caches. La lecture des enregistrements physiques du WAL se fait uniquement une fois, lors du redémarrage du nœud après un crash (et uniquement en cas de crash pendant le processus de checkpoint).

L'impact sur les flux qui modifient les données est le suivant : nous avons un effet négatif (CPU) en raison de la nécessité de compresser chaque page avant de l'écrire sur le disque, et un effet positif (disque IO) grâce à la réduction de la quantité de données écrites. Par conséquent, ici c'est simple : si les performances du système sont limitées par le CPU, nous avons une légère dégradation, si elles sont limitées par l'IO disque, nous avons un gain.

Indirectement, la réduction de la taille du WAL affecte également (positivement) les flux qui archivent les segments WAL et ceux de la compactage du WAL.

Des tests de performance réels dans notre environnement avec des données synthétiques ont montré un léger gain (le débit a augmenté de 10 à 15 %, la latence a diminué de 10 à 15 %).

Comment activer et configurer

Version minimale d'Apache Ignite : 2.8. L'activation et la configuration se font de la manière suivante :

  • Le class-path doit inclure le module ignite-compression. Par défaut, il se trouve dans la distribution d'Apache Ignite dans le répertoire libs/optional et n'est pas inclus dans le class-path. Vous pouvez simplement déplacer le répertoire d'un niveau vers le haut dans libs, et alors lors du lancement via ignite.sh, il sera automatiquement inclus.
  • La persistance doit être activée (S'active via DataRegionConfiguration.setPersistenceEnabled(true)).
  • Le mode de compression doit être défini à l'aide de la méthode DataStorageConfiguration.setWalPageCompression(), par défaut, la compression est désactivée (mode DISABLED).
  • Il est également possible de définir le degré de compression à l'aide de la méthode DataStorageConfiguration.setWalPageCompression(), les valeurs autorisées pour chacun des modes sont à consulter dans la javadoc de la méthode.

Conclusion

Les mécanismes de compression de données examinés dans Apache Ignite peuvent être utilisés indépendamment les uns des autres, mais toutes leurs combinaisons sont également acceptables. Comprendre les principes de leur fonctionnement permettra de déterminer à quel point ils conviennent à vos tâches dans votre environnement et ce avec quoi il faudra faire des sacrifices lors de leur utilisation. La compression des pages disque est destinée à compresser le stockage principal et peut offrir un taux de compression moyen. La compression des instantanés des pages WAL offrira un taux de compression moyen des fichiers WAL, tout en augmentant probablement les performances. La compaction WAL n'aura pas d'effet positif sur les performances, mais réduira au maximum la taille des fichiers WAL en supprimant les enregistrements physiques.

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster