Passer au contenu principal
Le type de données 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.
Exemples Créez une table avec une colonne de type map :
Query
Pour sélectionner les valeurs de key2 :
Query
Response
Si la clé demandée 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

Les valeurs de type 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

Pour éviter de lire toute la map, vous pouvez utiliser, dans certains cas, les sous-colonnes keys et values. Exemple
Query
Response

Sérialisation des maps par buckets dans MergeTree

Par défaut, une colonne 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

Pour éviter de ralentir les insertions, vous pouvez conserver la sérialisation 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

Lorsqu’une part de données est écrite avec la sérialisation with_buckets :
  1. Le nombre moyen de clés par ligne est calculé à partir des statistiques du block.
  2. Le nombre de buckets est déterminé par la stratégie configurée (voir Paramètres).
  3. Chaque paire clé-valeur est affectée à un bucket en hachant la clé : bucket = hash(key) % num_buckets.
  4. Chaque bucket est stocké sous forme de sous-flux indépendant, avec ses propres clés, valeurs et offsets.
  5. Un flux de métadonnées buckets_info enregistre le nombre de buckets et les statistiques.
Lorsqu’une requête lit une clé spécifique (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é.
Le nombre de buckets peut varier d’une part à l’autre. Lorsque des parts avec des nombres de buckets différents sont fusionnées, le nombre de buckets de la nouvelle part est recalculé à partir des statistiques fusionnées. Des parts avec les sérialisations 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

Le tableau suivant résume l’impact sur les performances de 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éfaut map_buckets_min_avg_size = 32 l’impose automatiquement.
  • Maps moyennes (32–100 clés) : Utilisez with_buckets avec la stratégie sqrt si 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. Envisagez map_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_buckets avec les parts de niveau zéro définies sur basic. L’optimisation PREWHERE ne 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

Si la sérialisation 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

Le type de données JSON stocke chaque chemin fréquent dans une sous-colonne dynamique distincte. Les chemins qui dépassent la limite 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

Vous pouvez diviser manuellement une seule Map en plusieurs colonnes selon le hachage des clés, au niveau de l’application :
Lors de l’insertion, acheminez chaque paire clé-valeur vers la colonne 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
Dernière modification le 25 juin 2026