HBase
 sql >> Base de données >  >> NoSQL >> HBase

HBase BlockCache 101

Cet article de blog a été publié sur Hortonworks.com avant la fusion avec Cloudera. Certains liens, ressources ou références peuvent ne plus être exacts.

Ce billet de blog a paru à l'origine ici et est reproduit dans son intégralité ici.

HBase est une base de données distribuée construite autour des concepts de base d'un journal d'écriture ordonné et d'une arborescence de fusion structurée en journal. Comme pour toute base de données, l'optimisation des E/S est une préoccupation essentielle pour HBase. Lorsque cela est possible, la priorité est de ne pas effectuer d'E/S du tout. Cela signifie que l'utilisation de la mémoire et les structures de mise en cache sont de la plus haute importance. A cette fin, HBase maintient deux structures de cache :le "memory store" et le "block cache". Magasin de mémoire, implémenté en tant que MemStore , accumule les modifications de données au fur et à mesure qu'elles sont reçues, les mettant en mémoire tampon (1). Le cache de blocs, une implémentation du BlockCache interface, conserve les blocs de données en mémoire après leur lecture.

Le MemStore est important pour accéder aux modifications récentes. Sans le MemStore , l'accès à ces données telles qu'elles ont été écrites dans le journal d'écriture nécessiterait la lecture et la désérialisation des entrées de ce fichier, au moins un O(n) opération. À la place, MemStore maintient une structure de liste de sauts, qui bénéficie d'un O(log n) coût d'accès et ne nécessite aucune E/S de disque. Le MemStore ne contient cependant qu'une infime partie des données stockées dans HBase.

Traitement des lectures à partir de BlockCache est le principal mécanisme par lequel HBase est capable de servir des lectures aléatoires avec une latence d'une milliseconde. Lorsqu'un bloc de données est lu à partir de HDFS, il est mis en cache dans le BlockCache . Les lectures ultérieures de données voisines - données du même bloc - ne subissent pas la pénalité d'E / S de récupérer à nouveau ces données du disque (2). C'est le BlockCache ce sera le reste de cet article.

Blocs à mettre en cache

Avant de comprendre le BlockCache , cela aide à comprendre ce qu'est exactement un "bloc" HBase. Dans le contexte HBase, un bloc est une seule unité d'E/S. Lors de l'écriture de données dans un HFile, le bloc est la plus petite unité de données écrite. De même, un seul bloc est la plus petite quantité de données que HBase peut lire à partir d'un HFile. Attention à ne pas confondre un bloc HBase avec un bloc HDFS, ou avec les blocs du système de fichiers sous-jacent – ​​ils sont tous différents (3).

Les blocs HBase sont disponibles en 4 variétés : DATAMETAINDEX , et BLOOM .

DATA les blocs stockent les données utilisateur. Lorsque BLOCKSIZE est spécifié pour une famille de colonnes, il s'agit d'un indice pour ce type de bloc. Attention, ce n'est qu'un indice. Lors du vidage du MemStore , HBase fera de son mieux pour respecter cette directive. Après chaque Cell est écrit, le rédacteur vérifie si le montant écrit est>=la cible BLOCKSIZE . Si c'est le cas, il fermera le bloc actuel et démarrera le suivant (4).

INDEX et BLOOM les blocs servent le même objectif ; les deux sont utilisés pour accélérer le chemin de lecture. INDEX les blocs fournissent un index sur la Cell s contenus dans les DATA blocs. BLOOM les blocs contiennent un filtre de floraison sur les mêmes données. L'index permet au lecteur de savoir rapidement où se trouve une Cell doit être stocké. Le filtre indique au lecteur lorsqu'un Cell est définitivement absent des données.

Enfin, META les blocs stockent des informations sur le HFile lui-même et d'autres informations diverses - des métadonnées, comme vous pouvez vous y attendre. Une vue d'ensemble plus complète des formats HFile et des rôles des différents types de blocs est fournie dans Apache HBase I/O - HFile.

HBase BlockCache et ses implémentations

Il existe un seul BlockCache instance dans un serveur de région, ce qui signifie que toutes les données de toutes les régions hébergées par ce serveur partagent le même pool de cache (5). Le BlockCache est instancié au démarrage du serveur de région et est conservé pendant toute la durée de vie du processus. Traditionnellement, HBase ne fournissait qu'un seul BlockCache implémentation :le LruBlockCache . La version 0.92 a introduit la première alternative dans HBASE-4027 :le SlabCache . HBase 0.96 a introduit une autre option via HBASE-7404, appelée BucketCache. .

La principale différence entre le LruBlockCache éprouvé et ces alternatives est la façon dont ils gèrent la mémoire. Plus précisément, LruBlockCache est une structure de données qui réside entièrement sur le tas JVM, tandis que les deux autres sont capables de tirer parti de la mémoire de l'extérieur du tas JVM. Il s'agit d'une distinction importante car la mémoire de tas JVM est gérée par le récupérateur de place JVM, contrairement aux autres. Dans le cas de SlabCache et BucketCache , l'idée est de réduire la pression GC subie par le processus du serveur de région en réduisant le nombre d'objets conservés sur le tas.

LruBlockCache

Il s'agit de l'implémentation par défaut. Les blocs de données sont mis en cache dans le tas JVM à l'aide de cette implémentation. Il est subdivisé en trois zones :accès unique, multi-accès et en mémoire. Les zones sont dimensionnées à 25 %, 50 %, 25 % du total BlockCache taille, respectivement (6). Un bloc initialement lu à partir de HDFS est rempli dans la zone d'accès unique. Les accès consécutifs promeuvent ce bloc dans la zone multi-accès. La zone en mémoire est réservée aux blocs chargés à partir des familles de colonnes marquées comme IN_MEMORY . Quelle que soit la zone, les anciens blocs sont expulsés pour faire place à de nouveaux blocs à l'aide d'un algorithme le moins récemment utilisé, d'où le "Lru" dans "LruBlockCache".

SlabCache

Cette mise en œuvre alloue des zones de mémoire en dehors du tas JVM à l'aide de DirectByteBuffer s. Ces zones fournissent le corps de ce BlockCache . La zone précise dans laquelle un bloc particulier sera placé est basée sur la taille du bloc. Par défaut, deux zones sont allouées, consommant respectivement 80 % et 20 % de la taille totale du cache hors tas configurée. Le premier est utilisé pour mettre en cache des blocs qui ont approximativement la taille de bloc cible (7). Ce dernier contient des blocs d'environ 2 fois la taille du bloc cible. Un bloc est placé dans la plus petite zone où il peut tenir. Si le cache rencontre un bloc plus grand que ne peut tenir dans l'une ou l'autre des zones, ce bloc ne sera pas mis en cache. Comme LruBlockCache , l'éviction des blocs est gérée à l'aide d'un algorithme LRU.

BucketCache

Cette implémentation peut être configurée pour fonctionner dans l'un des trois modes suivants : heapoffheap , et file . Quel que soit le mode de fonctionnement, le BucketCache gère des zones de mémoire appelées "buckets" pour contenir les blocs mis en cache. Chaque compartiment est créé avec une taille de bloc cible. Le heap l'implémentation crée ces compartiments sur le tas JVM ; offheap la mise en œuvre utilise DirectByteByffers pour gérer les compartiments en dehors du tas JVM ; file Le mode attend un chemin vers un fichier sur le système de fichiers dans lequel les compartiments sont créés. file Le mode est destiné à être utilisé avec un magasin de sauvegarde à faible latence - un système de fichiers en mémoire, ou peut-être un fichier hébergé sur un stockage SSD (8). Quel que soit le mode, BucketCache crée 14 seaux de tailles différentes. Il utilise la fréquence d'accès aux blocs pour informer l'utilisation, tout comme LruBlockCache , et a la même répartition d'accès unique, d'accès multiple et en mémoire de 25 %, 50 %, 25 %. Tout comme le cache par défaut, l'éviction des blocs est gérée à l'aide d'un algorithme LRU.

Mise en cache à plusieurs niveaux

Le SlabCache et BucketCache sont conçus pour être utilisés dans le cadre d'une stratégie de mise en cache à plusieurs niveaux. Ainsi, une partie du total BlockCache la taille est allouée à un LruBlockCache exemple. Cette instance agit comme le cache de premier niveau, "L1", tandis que l'autre instance de cache est traitée comme le cache de second niveau, "L2". Cependant, l'interaction entre LruBlockCache et SlabCache est différent de la façon dont LruBlockCache et le BucketCache interagir.

Le SlabCache stratégie, appelée DoubleBlockCache , consiste à toujours mettre en cache les blocs dans les caches L1 et L2. Les deux niveaux de cache fonctionnent indépendamment :les deux sont vérifiés lors de la récupération d'un bloc et chacun expulse des blocs sans tenir compte de l'autre. Le BucketCache stratégie, appelée CombinedBlockCache , utilise le cache L1 exclusivement pour les blocs Bloom et Index. Les blocs de données sont envoyés directement au cache L2. En cas d'éviction du bloc L1, plutôt que d'être entièrement supprimé, ce bloc est rétrogradé dans le cache L2.

Lequel choisir ?

Il y a deux raisons d'envisager d'activer l'une des alternatives BlockCache implémentations. Le premier est simplement la quantité de RAM que vous pouvez dédier au serveur de région. La sagesse de la communauté reconnaît que la limite supérieure du tas JVM, en ce qui concerne le serveur de région, se situe quelque part entre 14 Go et 31 Go (9). La limite précise dépend généralement d'une combinaison de profil matériel, de configuration de cluster, de la forme des tables de données et des modèles d'accès aux applications. Vous saurez que vous êtes entré dans la zone de danger lorsque GC s'interrompt et RegionTooBusyException s commencent à inonder vos journaux.

L'autre moment où envisager un cache alternatif est lorsque la latence de réponse vraiment questions. Maintenir le tas autour de 8 à 12 Go permet au collecteur CMS de fonctionner très bien (10), ce qui a un impact mesurable sur le 99e centile des temps de réponse. Compte tenu de cette restriction, les seuls choix sont d'explorer un ramasse-miettes alternatif ou d'essayer l'une de ces implémentations hors tas.

Cette deuxième option est exactement ce que j'ai fait. Dans mon prochain article, je partagerai des résultats d'expériences non scientifiques mais informatifs où je comparerai les temps de réponse pour différents BlockCache implémentations.

Comme toujours, restez à l'écoute et continuez avec HBase !

1 :Le MemStore accumule les modifications de données au fur et à mesure de leur réception, les mettant en mémoire tampon. Cela a deux objectifs :il augmente la quantité totale de données écrites sur le disque en une seule opération et il conserve ces modifications récentes en mémoire pour un accès ultérieur sous la forme de lectures à faible latence. Le premier est important car il maintient les blocs d'écriture HBase à peu près synchronisés avec les tailles de bloc HDFS, en alignant les modèles d'accès HBase avec le stockage HDFS sous-jacent. Ce dernier est explicite, facilitant les demandes de lecture de données récemment écrites. Il convient de souligner que cette structure n'est pas impliquée dans la durabilité des données. Les modifications sont également écrites dans le journal d'écriture ordonné, le HLog , qui implique une opération d'ajout HDFS à un intervalle configurable, généralement immédiat.

2 :La relecture des données du système de fichiers local est le meilleur scénario. HDFS est un système de fichiers distribué, après tout, donc le pire des cas nécessite la lecture de ce bloc sur le réseau. HBase fait de son mieux pour maintenir la localité des données. Ces deux articles fournissent un aperçu approfondi de ce que la localité des données signifie pour HBase et comment elle est gérée.

3 :Les blocs de système de fichiers, HDFS et HBase sont tous différents mais liés. Le sous-système d'E/S moderne est constitué de plusieurs couches d'abstraction au-dessus de l'abstraction. Au cœur de cette abstraction se trouve le concept d'une seule unité de données, appelée « bloc ». Par conséquent, ces trois couches de stockage définissent leur propre bloc, chacun de sa propre taille. En général, une taille de bloc plus grande signifie un débit d'accès séquentiel accru. Une taille de bloc plus petite facilite un accès aléatoire plus rapide.

4 :Placer le BLOCKSIZE vérifier après l'écriture des données a deux ramifications. Une seule Cell est la plus petite unité de données écrite dans un DATA bloc. Cela signifie également une Cell ne peut pas s'étendre sur plusieurs blocs.

5 :Ceci est différent du MemStore , pour lequel il existe une instance distincte pour chaque région hébergée par le serveur de région.

6 :Jusqu'à très récemment, ces partitions de mémoire étaient définies de manière statique ; il n'y avait aucun moyen de remplacer la répartition 25/50/25. Un segment donné, la zone multi-accès par exemple, pourrait devenir plus grand que son allocation de 50 % tant que les autres zones étaient sous-utilisées. Une utilisation accrue dans les autres zones évincera les entrées de la zone multi-accès jusqu'à ce que l'équilibre 25/50/25 soit atteint. L'opérateur n'a pas pu modifier ces tailles par défaut. HBASE-10263, livré dans HBase 0.98.0, introduit des paramètres de configuration pour ces tailles. Le comportement flexible est conservé.

7 :L'activité « approximativement » consiste à laisser une certaine marge de manœuvre dans la taille des blocs. La taille de bloc HBase est une cible approximative ou un indice, pas une contrainte strictement appliquée. La taille exacte d'un bloc de données particulier dépendra de la taille du bloc cible et de la taille de la Cell valeurs qui y sont contenues. L'indice de taille de bloc est spécifié comme taille de bloc par défaut de 64 Ko.

8 :Utiliser le BucketCache dans file Le mode avec un magasin de sauvegarde persistant a un autre avantage :la persistance. Au démarrage, il recherchera les données existantes dans le cache et vérifiera leur validité.

9 :Si je comprends bien, il y a deux composants qui conseillent la limite supérieure sur cette plage. Le premier est une limite sur l'adressabilité des objets JVM. La JVM est capable de référencer un objet sur le tas avec une adresse relative 32 bits au lieu de l'adresse native 64 bits complète. Cette optimisation n'est possible que si la taille totale du tas est inférieure à 32 Go. Voir Oups compressés pour plus de détails. La seconde est la capacité du ramasse-miettes à suivre le rythme de la rotation des objets dans le système. D'après ce que je peux dire, les trois sources d'attrition d'objets sont MemStoreBlockCache , et les opérations de réseau. Le premier est atténué par le MemSlab fonctionnalité, activée par défaut. La seconde est influencée par la taille de votre jeu de données par rapport à la taille du cache. Le troisième ne peut pas être aidé tant que HBase utilise une pile réseau qui repose sur la copie de données.

10 :Tout comme avec 8, cela suppose un "matériel moderne". Les interactions ici sont assez complexes et vont bien au-delà de la portée d'un seul article de blog.