Quand utiliser des dictionnaires plutôt que des JOINs
Les dictionnaires sont particulièrement adaptés lorsqu’un côté d’un JOIN est une table de correspondance qui tient en mémoire. Avec un JOIN standard, ClickHouse construit une table de hachage à partir du côté droit avant de l’interroger avec le côté gauche — même si la plupart des lignes sont ensuite écartées par les filtresWHERE. Bien que les versions récentes (24.12+) appliquent les filtres avant les JOINs dans de nombreux cas, cela n’élimine pas toujours ce surcoût. Avec un dictionnaire, vous appelez dictGet directement, de sorte que les lookups ne s’effectuent que sur les lignes qui ont déjà passé le filtrage.
Cependant, dictGet n’est pas toujours le bon choix. Si vous devez appeler dictGet sur un grand pourcentage des lignes d’une table — par exemple, dans une condition WHERE telle que dictGet('dict', 'elevation', id) > 1800 — il peut être préférable d’utiliser une colonne classique avec des index natifs. ClickHouse peut utiliser PREWHERE pour ignorer des granules sur une colonne classique, mais dictGet est évalué ligne par ligne sans prise en charge des index.
En règle générale :
- Utilisez des dictionnaires pour remplacer les JOINs sur de petites tables de dimension lorsque la clé de lookup est déjà disponible.
- Utilisez des colonnes classiques et des index lorsque vous filtrez sur la valeur récupérée sur un grand nombre de lignes.
Choisir une disposition
La clauseLAYOUT contrôle la structure de données interne du dictionnaire. Toutes les dispositions disponibles sont documentées dans la référence des dispositions.
Lorsque vous choisissez une disposition, suivez les recommandations suivantes :
flat— la disposition la plus rapide (lookup simple par décalage dans un tableau), mais les clés doivent être de typeUInt64et sont limitées à 500 000 par défaut (max_array_size). Idéal pour des clés entières croissantes monotones dans des tables de petite ou moyenne taille. Les distributions de clés clairsemées (par exemple, des valeurs de clé 1 et 500 000) gaspillent de la mémoire, car le tableau est dimensionné en fonction de la plus grande clé. Si vous atteignez la limite des 500k, c’est généralement le signe qu’il faut passer àhashed_array.hashed_array— le choix par défaut recommandé pour la plupart des cas d’usage. Stocke les attributs dans des tableaux, avec une table de hachage qui associe les clés aux indices des tableaux. Presque aussi rapide quehashed, mais plus économe en mémoire, surtout lorsqu’il y a beaucoup d’attributs.hashed— stocke l’intégralité du dictionnaire dans une table de hachage. Peut être plus rapide quehashed_arraylorsque vous avez très peu d’attributs, mais consomme davantage de mémoire à mesure que leur nombre augmente.complex_key_hashed/complex_key_hashed_array— utilisez-les lorsque les clés ne peuvent pas être converties enUInt64(par exemple, des clésString). Ils présentent les mêmes compromis de performance que leurs équivalents non complexes.sparse_hashed— réduit l’utilisation de la mémoire au prix d’une consommation CPU plus élevée par rapport àhashed. C’est rarement le meilleur choix : il n’est réellement efficace que lorsque vous n’avez qu’un seul attribut. Dans la plupart des cas,hashed_arrayest plus adapté.cache/ssd_cache— ne mettent en cache que les clés fréquemment consultées. Utile lorsque l’ensemble du dataset ne tient pas en mémoire, mais les lookups peuvent interroger la source en cas de cache miss. Non recommandé pour les charges de travail sensibles à la latence ni pour un enrichissement fiable au moment de l’insertion ; voir Dictionnaires cache dans les chemins d’ingestion.direct— interroge la source à chaque lookup, sans stockage en mémoire. À utiliser lorsque les données changent trop fréquemment pour être mises en cache ou lorsque le dictionnaire est trop volumineux pour tenir en mémoire.
Dictionnaires de type cache dans les chemins d’ingestion
Les dispositions basées sur un cache —cache, complex_key_cache, ssd_cache et complex_key_ssd_cache — ne conservent les entrées préchargées que sur le serveur local. Chaque serveur remplit son cache indépendamment, et un serveur redémarré ou remplacé repart avec un cache vide. Lorsque le cache est froid, les lookups dépendent de la source du dictionnaire, ce qui ajoute de la latence et peut bloquer ou faire échouer les insertions si la source ne parvient pas à répondre. Dans ClickHouse Cloud, l’autoscaling et le remplacement géré des nœuds rendent cette situation plus probable.
Évitez ces dispositions pour l’enrichissement au moment de l’insertion dans les vues matérialisées incrémentales ou dans les colonnes MATERIALIZED et DEFAULT lorsqu’une latence prévisible et la disponibilité de la source sont essentielles. Privilégiez plutôt une disposition qui charge l’intégralité du dictionnaire sur chaque serveur : généralement hashed ou hashed_array pour les clés UInt64, et complex_key_hashed ou complex_key_hashed_array pour les clés de type string ou composées. flat et les autres dispositions entièrement chargées peuvent également convenir. Si le dictionnaire complet ne tient pas en mémoire, enrichissez les données avant l’insertion ou au moment de la requête plutôt que de compter sur un cache préchargé dans le chemin d’ingestion.
Suivi de l’utilisation des dictionnaires
Suivez la consommation mémoire et l’état via la tablesystem.dictionaries :
bytes_allocated— mémoire consommée par le dictionnaire. Les dictionnaires stockent les données non compressées, donc cette valeur peut être nettement supérieure à la taille de la table compressée.hit_rateetfound_rate— utiles pour évaluer l’efficacité du layoutcache.last_exception— vérifiez ce point lorsqu’un dictionnaire ne parvient pas à se charger ou à s’actualiser.