L'arbre LSM garantit que la base de données est écrite dans l'ordre (memtable-skiplist), ce qui améliore les performances d'écriture, mais en raison de sa propre structure hiérarchique, il sacrifie les performances de lecture (comme une clé stockée dans un niveau bas, de haut en bas Chaque couche doit être recherchée, ce qui est très coûteux). Il existe donc de nombreuses optimisations pour améliorer les performances de lecture : bloom filter (interprétant efficacement si une clé n'existe pas), index-filter (recherche binaire, dans le cas d'une faible consommation mémoire) pour indexer les données clé-valeur. Ces bases de données doivent être stockées dans des fichiers SST pour une gestion ordonnée des données kv.
Présentation du format de fichier SST
1) Footer : fixe 48 octets, indiquant les informations de décalage de IndexBlock et MetaIndexBlock dans le fichier, c'est la méta information de la méta information, elle est située à la fin du fichier sstable
2) IndexBlock : occupe un espace de bloc et enregistre les métadonnées liées au DataBlock
3) MetaIndexBlock : occupe un espace de bloc, chaque bloc de méta-informations, y compris le filtre, les propriétés (informations d'attribut de la table entière), le dictionnaire de compression, la pierre tombale de suppression de plage
4) MetaBlock : il peut occuper plusieurs espaces de bloc et stocker les données binaires du filtre Bloom et d'autres données de métadonnées
5) DataBlock : il peut occuper plusieurs espaces de bloc et stocker les données réelles, c'est-à-dire le contenu des paires clé-valeur
Structure du pied de page
Le pied de page a une taille fixe de 48 octets et se trouve à la fin du fichier SSTable ; le décalage et la taille de MetaBlockIndex et DataBlockIndex forment le type BlockHandlel, qui est utilisé pour adresser l'emplacement des blocs de MetaBlockIndex et DataBlcokIndex. La taille et décalage sont encodés par une longueur variable varint pour économiser de l'espace. Le décalage et la taille occupent au moins 1 octet et au plus 9 octets, de sorte que le décalage et la taille de MetaBlockIndex et DataBlockIndex occupent au plus 4 * 9 = 36 octets, qui sont remplis à 40 octets (8 caractères) par remplissage. alignement de section); par exemple, DataBlockIndex.offset = 64, DataBlockIndex.size = 216, indiquant que DataBlockIndex est situé entre le 64e octet et le 280e octet du fichier SSTable.
Structure de bloc
La structure de données de 5 parties, à l'exception du pied de page, les autres structures physiques sont sous forme de bloc. Chaque bloc correspond à un bloc de stockage du disque physique, la taille du bloc est donc cohérente avec la taille du bloc de stockage du disque. C'est aussi la raison pour laquelle le pied de page est placé à la fin du fichier, les 48 octets du pied de page lui-même ne pouvant pas occuper un bloc de stockage sur disque. Lorsque le bloc est stocké sur le disque dur, il ajoutera 5 octets de contenu supplémentaire après les données réelles : type de compression, crc.
Structure du bloc de données
Le stockage de DataBlcok Key adopte le mécanisme de compression de préfixe. La compression de préfixe signifie que le même préfixe de la clé n'est stocké qu'une seule fois pour économiser de l'espace. Mais pour SSTable, il n'effectue pas de compression de préfixe sur toutes les clés du bloc entier en même temps, mais configure de nombreux segments, et les clés du même segment effectuent une compression de préfixe une fois, et le point de départ de chaque segment est un point de redémarrage . . Le mécanisme de compression des préfixes amène chaque enregistrement à mémoriser les longueurs partagées et non partagées de sa clé correspondante. La longueur dite partagée de la clé fait référence à la longueur du préfixe commun entre la clé de l'enregistrement en cours et la clé de l'enregistrement précédent, et la longueur non partagée fait référence à la longueur des différentes parties après suppression de la même partie. De cette manière, l'enregistrement en cours n'a besoin de stocker que la valeur des différentes parties de la clé.
La première partie (Entry) est utilisée pour stocker les données clé-valeur. Étant donné que toutes les paires clé-valeur dans sstable sont stockées dans un ordre strict, pour économiser de l'espace de stockage, la valeur clé complète ne sera pas stockée pour chaque paire de paires clé-valeur, mais pas partagée avec la partie clé précédente, pour éviter le stockage. de contenu dupliqué clé. Toutes les quelques paires clé-valeur, une clé complète sera re-stockée pour l'enregistrement. Ce processus est répété (l'intervalle par défaut est de 16) et chaque point auquel la clé complète est restaurée est appelé un point de redémarrage.
Le but du point de redémarrage est d'accélérer le processus de recherche lors de la lecture de contenu stable. Étant donné que chaque point de redémarrage stocke une valeur de clé complète, lors de la recherche de données dans sstable, vous pouvez d'abord utiliser les données du point de redémarrage pour comparer la valeur de clé, afin de localiser rapidement la zone où se trouvent les données cibles ; lorsque la cible les données sont déterminées Lorsque vous êtes dans la zone, comparez tour à tour les valeurs clés de tous les éléments de données de l'intervalle et effectuez une recherche précise; cette idée est quelque peu similaire à l'idée d'utiliser des données de haut niveau pour localiser et rechercher rapidement des données de bas niveau dans la liste de sauts, réduisant ainsi la complexité de la recherche. .
Structure de stockage de données KV
- longueur de clé partagée : la même longueur d'octet de préfixe de clé que le point de redémarrage.
- longueur de clé non partagée : après la clé actuelle moins la même longueur de préfixe du point de redémarrage, la longueur d'octet restante
- longueur de la valeur : la longueur en octets de la valeur
- contenu de la clé non partagée : le contenu de la clé de la partie qui est différent de la clé du point de redémarrage.
- valeur : stocke la valeur réelle
Structure du bloc d'index
Le bloc d'index contient plusieurs enregistrements, chaque enregistrement représente les informations d'index d'un bloc de données, qui sont utilisées pour localiser rapidement le bloc de données contenant une clé spécifique ; le bloc d'index est d'abord un bloc, il contient donc trois parties KeyValue, Type (fixe 1 octet) , code de contrôle CRC (fixe 4 octets); Type identifie si cette partie des données adopte un algorithme de compression, CRC est le code de contrôle de KeyValue + Type.
Un index comprend les éléments suivants :
- key, la valeur est supérieure ou égale à la plus grande clé de son bloc d'index, et inférieure à la plus petite clé du bloc suivant ;
- Le décalage de l'adresse de début du bloc de données dans sstable ;
- La taille du bloc de données ;
Comme DataBlock, IndexBlock adopte la compression de préfixe, mais l'intervalle est de 2 (DataBlock est par défaut de 16).
Le but principal est d'économiser de l'espace ; en supposant que la plus grande clé du bloc indexé est "acknowledge", et que la plus petite clé du bloc suivant est "apple", si la clé de DataBlockIndex adopte la plus grande clé de son bloc d'index, le la longueur occupée est len("acknowledge" ); dans cette dernière méthode, la valeur de la clé peut être "ad" ("acknowledge" < "ad" < "apple"), la longueur n'est que de 2 et l'effet de récupération est le même .
Étant donné que la taille de bloc dans la SSTable n'est pas fixe, bien que le paramètre block_size puisse être spécifié dans l'option, lorsque les données sont stockées dans la SSTable, elles ne sont pas strictement alignées en fonction de block_size, de sorte que le décalage et la taille se réfèrent au nombre d'octets de décalage et le nombre d'octets de longueur. . Il y a deux raisons principales pour cela:
(1) Des clés de n'importe quelle longueur et des valeurs de n'importe quelle longueur peuvent être stockées, et la même valeur de clé ne peut pas être stockée sur plusieurs blocs. Dans des cas extrêmes, comme notre valeur unique est très grande, qui a dépassé block_size, alors pour ce type de Dans ce cas, SSTable ne peut pas être stocké. Donc, généralement, la taille réelle du bloc est légèrement supérieure à block_size ;
(2) D'un autre point de vue, si les données sont stockées strictement selon l'alignement block_size, il doit y avoir de nombreux blocs alignés avec des 0, ce qui gaspille de l'espace de stockage.