Map(K, V) stocke des paires clé-valeur.
Contrairement à d’autres bases de données, les maps ne sont pas uniques dans ClickHouse, c.-à-d. qu’une map peut contenir deux éléments ayant la même clé.
(Cela s’explique par le fait que les maps sont implémentées en interne sous la forme de Array(Tuple(K, V)).)
Vous pouvez utiliser la syntaxe m[k] pour obtenir la valeur associée à la clé k dans la map m.
De plus, m[k] parcourt la map, c.-à-d. que le temps d’exécution de l’opération est linéaire en fonction de la taille de la map.
Paramètres
K— Le type des clés de la Map. N’importe quel type, à l’exception de Nullable et de LowCardinality imbriqué avec des types Nullable.V— Le type des valeurs de la Map. N’importe quel type.
Query
key2 :
Query
Response
k n’est pas présente dans la map, m[k] renvoie la valeur par défaut du type de valeur, par exemple 0 pour les types entiers et '' pour les types String.
Pour vérifier si une clé existe dans une map, vous pouvez utiliser la fonction mapContains.
Query
Response
Conversion de Tuple en Map
Tuple() peuvent être converties en valeurs de type Map() à l’aide de la fonction CAST :
Exemple
Query
Response
Lire les sous-colonnes d’une Map
keys et values.
Exemple
Query
Response
Sérialisation des maps par buckets dans MergeTree
Map dans MergeTree est stockée dans un unique flux Array(Tuple(K, V)).
Lire une seule clé avec m['key'] nécessite de parcourir toute la colonne — chaque paire clé-valeur de chaque ligne — même si une seule clé est nécessaire.
Pour les maps contenant de nombreuses clés distinctes, cela devient un goulot d’étranglement.
La sérialisation par buckets (with_buckets) répartit les paires clé-valeur dans plusieurs sous-flux indépendants (buckets) en appliquant un hash à la clé.
Lorsqu’une requête accède à m['key'], seul le bucket qui contient cette clé est lu sur le disque, les autres buckets étant ignorés.
Activation de la sérialisation par bucket
basic pour les parts de niveau zéro (créées lors de INSERT) et n’utiliser with_buckets que pour les parts fusionnées :
Fonctionnement
with_buckets :
- Le nombre moyen de clés par ligne est calculé à partir des statistiques du block.
- Le nombre de buckets est déterminé par la stratégie configurée (voir Paramètres).
- Chaque paire clé-valeur est affectée à un bucket en hachant la clé :
bucket = hash(key) % num_buckets. - Chaque bucket est stocké sous forme de sous-flux indépendant, avec ses propres clés, valeurs et offsets.
- Un flux de métadonnées
buckets_infoenregistre le nombre de buckets et les statistiques.
m['key']), l’optimiseur réécrit l’expression en sous-colonne de clé (m.key_<serialized_key>).
La couche de sérialisation détermine à quel bucket appartient la clé demandée et ne lit que ce bucket sur le disque.
Lorsque la map complète est lue (par ex. SELECT m), tous les buckets sont lus puis réassemblés dans la map d’origine. Cette opération est plus lente qu’avec la sérialisation basic, en raison de la surcharge liée à la lecture et à la fusion de plusieurs sous-flux.
L’ordre des clés dans une valeur de map peut différer de l’ordre d’insertion initial lors de l’utilisation de la sérialisation
with_buckets. Les clés sont réparties entre les buckets par hachage, puis réassemblées dans l’ordre des buckets, et non dans l’ordre d’insertion. Avec la sérialisation basic, l’ordre des clés des maps insérées est conservé.basic et with_buckets peuvent coexister dans la même table et être fusionnées de manière transparente.
Paramètres
Compromis en matière de performances
with_buckets par rapport à la sérialisation basic pour différentes tailles de Map (de 10 à 10 000 clés par ligne). Le nombre de buckets a été déterminé à l’aide de la stratégie sqrt, plafonnée à 32. Les chiffres exacts dépendent des types de clés/valeurs, de la distribution des données et du matériel.
Recommandations
- Petites maps (< 32 clés en moyenne) : Conservez la sérialisation
basic. Le surcoût de la répartition en buckets ne se justifie pas pour les petites maps. La valeur par défautmap_buckets_min_avg_size = 32l’impose automatiquement. - Maps moyennes (32–100 clés) : Utilisez
with_bucketsavec la stratégiesqrtsi les requêtes accèdent fréquemment à des clés individuelles. Le gain de performance est de 4 à 8x pour les recherches sur une seule clé. - Grandes maps (100+ clés) : Utilisez
with_buckets. Les recherches sur une seule clé sont 16 à 49x plus rapides. Envisagezmap_serialization_version_for_zero_level_parts = 'basic'pour conserver une vitesse d’insert proche de la référence. - Les scans complets de maps dominent la charge de travail : Conservez
basic. La sérialisation par buckets ajoute un surcoût d’environ 2x pour les scans complets. - Charge de travail mixte (certaines recherches de clé, certains scans complets) : Utilisez
with_bucketsavec les parts de niveau zéro définies surbasic. L’optimisationPREWHEREne lit que le bucket pertinent pour le filtre, puis ne lit la map complète que pour les lignes correspondantes, ce qui se traduit au final par un gain de performance net significatif.
Approches alternatives
Map par buckets ne correspond pas à votre cas d’utilisation, il existe deux autres approches pour améliorer les performances d’accès au niveau des clés :
Utilisation du type de données JSON
max_dynamic_paths sont placés dans une structure de données partagée, qui peut utiliser la sérialisation advanced pour optimiser la lecture d’un chemin unique. Consultez le billet de blog pour une présentation détaillée de la sérialisation advanced.
Utilisez
JSON lorsque différentes clés nécessitent différents types de valeur, lorsque l’ensemble des clés varie fortement d’une ligne à l’autre, ou lorsque les clés fréquemment consultées sont connues à l’avance et peuvent être déclarées comme chemins typés pour un accès direct aux sous-colonnes.
Partitionnement manuel en plusieurs colonnes Map
Map en plusieurs colonnes selon le hachage des clés, au niveau de l’application :
m{hash(key) % 4}. Lors des requêtes, lisez la colonne correspondante : m{hash('target_key') % 4}['target_key'].
Le sharding manuel est utile lorsque les Vertical Merges sont importantes pour réduire l’utilisation mémoire lors des merges de tables comportant de nombreuses colonnes, ou lorsque le nombre de shards doit être fixe et explicitement contrôlé. Pour la plupart des cas d’usage, la sérialisation automatique par buckets est plus simple et suffisante.
Voir aussi
- fonction map()
- fonction CAST()
- combinateur -Map pour le type de données Map