Passer au contenu principal
Voici la version web de notre article scientifique VLDB 2024. Nous avons également publié un article de blog sur son contexte et sa genèse, et nous vous recommandons de regarder la présentation VLDB 2024 donnée par le CTO et créateur de ClickHouse, Alexey Milovidov :

RÉSUMÉ

Au cours des dernières décennies, la quantité de données stockées et analysées a augmenté de façon exponentielle. Les entreprises de tous les secteurs ont commencé à s’appuyer sur ces données pour améliorer leurs produits, évaluer leurs performances et prendre des décisions stratégiques. Toutefois, à mesure que les volumes de données ont atteint l’échelle d’Internet, elles ont dû gérer à moindre coût et de façon évolutive les données historiques comme les nouvelles données, tout en les analysant au moyen d’un grand nombre de requêtes concurrentes, avec des exigences de latence en temps réel (par exemple inférieure à une seconde, selon le cas d’usage). Cet article présente une vue d’ensemble de ClickHouse, une base de données OLAP open source populaire conçue pour l’analytique haute performance sur des jeux de données à l’échelle du pétaoctet, avec des taux d’ingestion élevés. Sa couche de stockage combine un format de données fondé sur des arbres LSM (log-structured merge) traditionnels avec de nouvelles techniques de transformation continue (par exemple agrégation, archivage) des données historiques en arrière-plan. Les requêtes sont écrites dans un dialecte SQL simple d’utilisation et traitées par un moteur moderne d’exécution vectorisée des requêtes, avec compilation de code en option. ClickHouse utilise largement des techniques d’élagage afin d’éviter de traiter des données non pertinentes dans les requêtes. D’autres systèmes de gestion de données peuvent être intégrés au niveau de la fonction de table, du moteur de table ou du moteur de base de données. Des benchmarks en conditions réelles montrent que ClickHouse figure parmi les bases de données analytiques les plus rapides du marché.

1 INTRODUCTION

Cet article décrit ClickHouse, une base de données OLAP columnar conçue pour exécuter des requêtes analytiques hautes performances sur des tables contenant des billions de lignes et des centaines de colonnes. ClickHouse a été créé en 2009 comme opérateur de filtrage et d’agrégation pour des données de fichiers journaux à l’échelle du Web, puis a été publié en open source en 2016. La figure 1 montre à quel moment les principales fonctionnalités décrites dans cet article ont été introduites dans ClickHouse. ClickHouse est conçu pour répondre à cinq grands défis de la gestion moderne des données analytiques :
  1. D’énormes jeux de données avec des taux d’ingestion élevés. De nombreuses applications orientées données dans des secteurs comme l’analytique web, la finance et le e-commerce se caractérisent par des volumes de données énormes et en croissance continue. Pour gérer de tels jeux de données, les bases de données analytiques doivent non seulement offrir des stratégies efficaces d’indexation et de compression, mais aussi permettre la distribution des données sur plusieurs nœuds (scale-out), car un seul serveur est limité à quelques dizaines de téraoctets de stockage. En outre, les données récentes sont souvent plus pertinentes pour des analyses en temps réel que les données historiques. Les bases de données analytiques doivent donc être capables d’ingérer de nouvelles données à des débits durablement élevés ou par pics, tout en « dépriorisant » en continu les données historiques (par exemple en les agrégeant ou en les archivant) sans ralentir les requêtes de reporting exécutées en parallèle.
  2. De nombreuses requêtes simultanées avec une exigence de faible latence. Les requêtes peuvent généralement être classées en deux catégories : ad hoc (par exemple pour l’analyse exploratoire de données) ou récurrentes (par exemple les requêtes périodiques d’un tableau de bord). Plus un cas d’usage est interactif, plus les latences attendues sont faibles, ce qui pose des défis en matière d’optimisation et d’exécution des requêtes. Les requêtes récurrentes offrent en outre la possibilité d’adapter l’organisation physique de la base de données à la charge de travail. Les bases de données doivent donc proposer des techniques d’élagage permettant d’optimiser les requêtes fréquentes. Selon la priorité des requêtes, elles doivent également assurer un accès égalitaire ou prioritaire aux ressources système partagées, telles que le CPU, la mémoire, le disque et les E/S réseau, même lorsqu’un grand nombre de requêtes s’exécutent simultanément.
  3. Des environnements variés en matière de magasins de données, d’emplacements de stockage et de formats. Pour s’intégrer aux architectures de données existantes, les bases de données analytiques modernes doivent faire preuve d’une grande ouverture afin de pouvoir lire et écrire des données externes, quel que soit le système, l’emplacement ou le format.
  4. Un langage de requête pratique avec prise en charge de l’introspection des performances. L’utilisation réelle des bases de données OLAP s’accompagne d’exigences supplémentaires plus « souples ». Par exemple, plutôt qu’un langage de programmation de niche, les utilisateurs préfèrent souvent interagir avec les bases de données au moyen d’un dialecte SQL expressif, avec des types de données imbriqués et un large éventail de fonctions classiques, d’agrégation et de fenêtre. Les bases de données analytiques doivent également fournir des outils sophistiqués pour inspecter les performances du système ou de requêtes individuelles.
  5. Une robustesse de niveau industriel et des options de déploiement variées. Le matériel standard n’étant pas fiable, les bases de données doivent assurer la réplication des données afin de résister aux défaillances de nœuds. Elles doivent aussi pouvoir fonctionner sur tout type de matériel, depuis de vieux ordinateurs portables jusqu’à de puissants serveurs. Enfin, pour éviter le surcoût du garbage collection dans les programmes basés sur la JVM et permettre des performances bare metal (par exemple SIMD), les bases de données sont idéalement déployées sous forme de binaires natifs pour la plateforme cible.
Figure 1 : chronologie de ClickHouse.

Figure 1 : chronologie de ClickHouse.

2 ARCHITECTURE

Figure 2 : architecture de haut niveau du moteur de base de données ClickHouse.

Figure 2 : architecture de haut niveau du moteur de base de données ClickHouse.

Comme l’illustre la Figure 2, le moteur ClickHouse se divise en trois couches principales : la couche de traitement des requêtes (décrite à la section 4)), la couche de stockage (section 3)) et la couche d’intégration (section 5)). En complément, une couche d’accès gère les sessions utilisateur et la communication avec les applications via différents protocoles. Des composants transverses prennent en charge le multithreading, la mise en cache, le contrôle d’accès basé sur les rôles, les sauvegardes et la surveillance continue. ClickHouse est développé en C++ sous la forme d’un unique binaire lié statiquement, sans dépendances. Le traitement des requêtes suit le paradigme classique consistant à analyser les requêtes entrantes, à construire et optimiser des plans de requête logiques et physiques, puis à les exécuter. ClickHouse utilise un modèle d’exécution vectorisé semblable à celui de MonetDB/X100 [11], combiné à une compilation de code opportuniste [53]. Les requêtes peuvent être écrites dans un dialecte SQL riche en fonctionnalités, en PRQL [76] ou en KQL de Kusto [50]. La couche de stockage se compose de différents moteurs de table qui encapsulent le format et l’emplacement des données des tables. Les moteurs de table se répartissent en trois catégories : la première est la famille de moteurs de table MergeTree*, qui constitue le principal format de persistance dans ClickHouse. Reposant sur le concept des arbres LSM [60], les tables sont divisées en parts horizontales triées, fusionnées en continu par un processus d’arrière-plan. Les différents moteurs de table MergeTree* se distinguent par la manière dont la fusion combine les lignes de leurs parts d’entrée. Par exemple, les lignes peuvent être agrégées ou remplacées lorsqu’elles sont obsolètes. La deuxième catégorie regroupe les moteurs de table spécialisés, utilisés pour accélérer ou distribuer l’exécution des requêtes. Cette catégorie comprend des moteurs de table clé-valeur en mémoire appelés dictionnaires. Un dictionnaire met en cache le résultat d’une requête exécutée périodiquement sur une source de données interne ou externe. Cela réduit considérablement les latences d’accès dans les scénarios où un certain degré d’obsolescence des données est tolérable. Parmi les autres exemples de moteurs de table spécialisés figurent un moteur entièrement en mémoire utilisé pour les tables temporaires, ainsi que le moteur de table Distributed pour le partitionnement transparent des données (voir ci-dessous). La troisième catégorie de moteurs de table est constituée de moteurs de table virtuels permettant l’échange bidirectionnel de données avec des systèmes externes tels que des bases de données relationnelles (par exemple PostgreSQL, MySQL), des systèmes de publication/abonnement (par exemple Kafka, RabbitMQ [24]) ou des magasins clé/valeur (par exemple Redis). Les moteurs virtuels peuvent également interagir avec des data lakes (par exemple Iceberg, DeltaLake, Hudi [36]) ou avec des fichiers dans un stockage objet (par exemple AWS S3, Google GCP). ClickHouse prend en charge le sharding et la réplication des tables sur plusieurs nœuds de cluster afin d’assurer la scalabilité et la disponibilité. Le sharding partitionne une table en un ensemble de shards selon une expression de sharding. Les shards individuels sont des tables mutuellement indépendantes et sont généralement situés sur des nœuds différents. Les clients peuvent lire et écrire directement dans les shards, c.-à-d. les traiter comme des tables distinctes, ou utiliser le moteur de table spécial Distributed, qui fournit une vue globale de tous les shards de la table. L’objectif principal du sharding est de traiter des ensembles de données qui dépassent la capacité des nœuds individuels (généralement quelques dizaines de téraoctets de données). Un autre usage du sharding consiste à répartir la charge en lecture-écriture d’une table sur plusieurs nœuds, c.-à-d. l’équilibrage de charge. Indépendamment de cela, un shard peut être répliqué sur plusieurs nœuds afin de tolérer les défaillances de nœuds. À cette fin, chaque moteur de table Merge-Tree* possède un moteur ReplicatedMergeTree* correspondant, qui utilise un schéma de coordination multi-maître fondé sur le consensus Raft [59] (implémenté par Keeper, un remplacement compatible d’Apache Zookeeper écrit en C++) pour garantir que chaque shard dispose, à tout moment, d’un nombre configurable de répliques. La section 3.6 décrit en détail le mécanisme de réplication. À titre d’exemple, la figure 2 montre une table avec deux shards, chacun répliqué sur deux nœuds. Enfin, le moteur de base de données ClickHouse peut être utilisé en modes on-premise, Cloud, standalone ou in-process. En mode on-premise, les utilisateurs configurent ClickHouse localement comme un serveur unique ou un cluster multinœud avec sharding et/ou réplication. Les clients communiquent avec la base de données via les protocoles binaires natif, MySQL et PostgreSQL, ou via une API REST HTTP. Le mode Cloud est représenté par ClickHouse Cloud, une offre DBaaS entièrement managée avec autoscaling. Bien que cet article se concentre sur le mode on-premise, nous prévoyons de décrire l’architecture de ClickHouse Cloud dans une publication ultérieure. Le mode standalone transforme ClickHouse en utilitaire en ligne de commande pour analyser et transformer des fichiers, ce qui en fait une alternative fondée sur SQL à des outils Unix comme cat et grep. Bien que cela ne nécessite aucune configuration préalable, le mode standalone est limité à un seul serveur. Récemment, un mode in-process appelé chDB [15] a été développé pour des cas d’usage d’analyse interactive de données comme les Jupyter notebooks [37] avec des DataFrames Pandas [61]. Inspiré par DuckDB [67], chDB intègre ClickHouse comme moteur OLAP haute performance dans un processus hôte. Par rapport aux autres modes, cela permet de transférer efficacement les données source et les données de résultat entre le moteur de base de données et l’application sans copie, puisqu’ils s’exécutent dans le même espace d’adressage.

3 COUCHE DE STOCKAGE

Cette section présente les moteurs de table MergeTree* comme le format de stockage natif de ClickHouse. Nous décrivons leur représentation sur disque et examinons trois techniques d’élagage des données dans ClickHouse. Nous présentons ensuite des stratégies de fusion qui transforment continuellement les données sans perturber les insertions simultanées. Enfin, nous expliquons comment sont mises en œuvre les mises à jour et les suppressions, ainsi que la déduplication des données, la réplication des données et la conformité ACID.

3.1 Format sur disque

Chaque table utilisant le moteur de table MergeTree* est organisée comme un ensemble de table parts immuables. Une part est créée chaque fois qu’un ensemble de lignes est inséré dans la table. Les parts sont autonomes, en ce sens qu’elles incluent toutes les métadonnées nécessaires pour interpréter leur contenu sans lookup supplémentaire dans un catalog central. Afin de limiter le nombre de parts par table, une tâche de merge en arrière-plan combine périodiquement plusieurs petites parts en une part plus grande, jusqu’à atteindre une part size configurable (150 GB par défaut). Comme les parts sont triées selon les colonnes de clé primaire de la table (voir Section 3.2), un algorithme efficace de tri par fusion k-way [40] est utilisé pour le merging. Les parts source sont marquées comme inactives puis, à terme, supprimées dès que leur compteur de références tombe à zéro, c.-à-d. qu’aucune requête ne les lit encore. Les lignes peuvent être insérées selon deux modes : en mode de Synchronous inserts, chaque instruction INSERT crée une nouvelle part et l’ajoute à la table. Pour minimiser l’overhead des merges, il est recommandé aux clients de base de données d’insérer les Tuples en masse, par exemple 20 000 lignes à la fois. Cependant, les délais causés par le client-side batching sont souvent inacceptables si les données doivent être analysées en real-time. Par exemple, les cas d’usage d’observability impliquent fréquemment des milliers d’agents de Monitoring envoyant en continu de petites quantités de données d’événements et de metrics. Dans ce type de scénario, on peut utiliser le mode d’asynchronous insert, dans lequel ClickHouse met en buffer les lignes de plusieurs INSERT entrants dans la même table et ne crée une nouvelle part qu’une fois que la taille du buffer dépasse un threshold configurable ou qu’un timeout expire.
Figure 3 : inserts et merges pour les tables utilisant le moteur MergeTree*.

Figure 3 : inserts et merges pour les tables utilisant le moteur MergeTree*.

La Figure 3 illustre quatre Synchronous inserts et deux asynchronous inserts dans une table utilisant le moteur MergeTree*. Deux merges ont réduit le nombre d’active parts, de cinq au départ à deux. Comparé aux arbres LSM [58] et à leur implémentation dans diverses bases de données [13, 26, 56], ClickHouse traite toutes les parts sur un pied d’égalité au lieu de les organiser selon une hiérarchie. En conséquence, les merges ne sont plus limités aux parts d’un même Level. Comme cela renonce également à l’ordre chronologique implicite des parts, des mécanismes alternatifs pour les mises à jour et les suppressions, qui ne reposent pas sur des tombstones, sont nécessaires (voir Section 3.4). ClickHouse écrit les inserts directement sur le disk, tandis que les autres systèmes de stockage fondés sur les arbres LSM utilisent généralement le write-ahead logging (voir Section 3.7)). Une part correspond à un répertoire sur disque, contenant un fichier pour chaque colonne. À des fins d’optimisation, les colonnes d’une petite part (moins de 10 Mo par défaut) sont stockées de manière consécutive dans un seul fichier afin d’améliorer la localité spatiale des lectures et des écritures. Les lignes d’une part sont en outre divisées logiquement en groupes de 8192 enregistrements, appelés granules. Un granule représente la plus petite unité de données indivisible traitée par les opérateurs de scan et de recherche d’index dans ClickHouse. Les lectures et écritures des données sur disque ne sont toutefois pas effectuées au niveau du granule, mais à la granularité des blocs, qui combinent plusieurs granules voisins au sein d’une colonne. De nouveaux blocs sont formés en fonction d’une taille configurable en octets par bloc (1 Mo par défaut), c’est-à-dire que le nombre de granules dans un bloc est variable et dépend du type de données et de la distribution de la colonne. Les blocs sont en outre compressés afin de réduire leur taille et les coûts d’E/S. Par défaut, ClickHouse utilise LZ4 [75] comme algorithme de compression polyvalent, mais les utilisateurs peuvent aussi spécifier des codecs spécialisés comme Gorilla [63] ou FPC [12] pour les données en virgule flottante. Les algorithmes de compression peuvent également être chaînés. Par exemple, il est possible de réduire d’abord la redondance logique des valeurs numériques à l’aide du codage delta [23], puis d’effectuer une compression plus poussée, et enfin de chiffrer les données à l’aide d’un codec AES. Les blocs sont décompressés à la volée lorsqu’ils sont chargés depuis le disque en mémoire. Pour permettre un accès aléatoire rapide à des granules individuels malgré la compression, ClickHouse stocke en plus, pour chaque colonne, une table de correspondance qui associe chaque identifiant de granule au décalage du bloc compressé qui le contient dans le fichier de colonne, ainsi qu’au décalage du granule dans le bloc non compressé. Les colonnes peuvent en outre être encodées par dictionnaire [2, 77, 81] ou rendues nullables à l’aide de deux types de données enveloppes spéciaux : LowCardinality(T) remplace les valeurs de la colonne d’origine par des identifiants entiers et réduit ainsi significativement le surcoût de stockage pour les données comportant peu de valeurs uniques. Nullable(T) ajoute un bitmap interne à la colonne T, indiquant si les valeurs de la colonne sont NULL ou non. Enfin, les tables peuvent être partitionnées par plage, hachage ou round-robin à l’aide d’expressions de partitionnement arbitraires. Pour permettre l’élagage de partition, ClickHouse stocke en plus les valeurs minimum et maximum de l’expression de partitionnement pour chaque partition. Les utilisateurs peuvent éventuellement créer des statistiques de colonnes plus avancées (par exemple des statistiques HyperLogLog [30] ou t-digest [28]) qui fournissent également des estimations de cardinalité.

3.2 Élagage des données

Dans la plupart des cas d’usage, parcourir des pétaoctets de données simplement pour répondre à une seule requête est trop lent et trop coûteux. ClickHouse prend en charge trois techniques d’élagage des données qui permettent d’ignorer la majorité des lignes lors des recherches et, par conséquent, d’accélérer considérablement les requêtes. Premièrement, les utilisateurs peuvent définir un index de clé primaire pour une table. Les colonnes de clé primaire déterminent l’ordre de tri des lignes au sein de chaque part, c.-à-d. que l’index y est clusterisé localement. ClickHouse stocke en outre, pour chaque part, une correspondance entre les valeurs des colonnes de clé primaire de la première ligne de chaque granule et l’identifiant du granule, c.-à-d. que l’index est clairsemé [31]. La structure de données qui en résulte est généralement assez compacte pour rester entièrement en mémoire ; par exemple, 1000 entrées seulement suffisent pour indexer 8,1 millions de lignes. L’objectif principal d’une clé primaire est d’évaluer les prédicats d’égalité et d’intervalle pour les colonnes fréquemment filtrées à l’aide d’une recherche binaire plutôt que de balayages séquentiels (section 4.4). Le tri local peut en outre être exploité pour les fusions de parts et l’optimisation des requêtes, par exemple pour l’agrégation basée sur le tri ou pour supprimer des opérateurs de tri du plan d’exécution physique lorsque les colonnes de clé primaire forment un préfixe des colonnes de tri. La figure 4 montre un index de clé primaire sur la colonne EventTime pour une table contenant des statistiques d’affichage de pages. Les granules qui correspondent au prédicat d’intervalle de la requête peuvent être trouvés par recherche binaire dans l’index de clé primaire au lieu de parcourir EventTime séquentiellement.
Figure 4 : Évaluation des filtres avec un index de clé primaire.

Figure 4 : Évaluation des filtres avec un index de clé primaire.

Deuxièmement, les utilisateurs peuvent créer des projections de table, c.-à-d. des versions alternatives d’une table qui contiennent les mêmes lignes triées selon une clé primaire différente [71]. Les projections permettent d’accélérer les requêtes qui filtrent sur des colonnes autres que la clé primaire de la table principale, au prix d’une surcharge accrue pour les insertions, les fusions et l’espace de stockage consommé. Par défaut, les projections ne sont remplies que de manière différée, à partir des seules parts nouvellement insérées dans la table principale, et non des parts existantes, sauf si l’utilisateur matérialise entièrement la projection. L’optimiseur de requêtes choisit de lire soit depuis la table principale, soit depuis une projection, en fonction des coûts d’E/S estimés. Si aucune projection n’existe pour une part, l’exécution de la requête se rabat sur la part correspondante de la table principale. Troisièmement, les indices de saut offrent une alternative légère aux projections. Le principe des indices de saut consiste à stocker de petites quantités de métadonnées au niveau de plusieurs granules consécutifs, ce qui permet d’éviter de parcourir des lignes non pertinentes. Les indices de saut peuvent être créés pour des expressions d’index arbitraires et avec une granularité configurable, c.-à-d. un nombre de granules dans un bloc d’index de saut. Les types d’indices de saut disponibles incluent : 1. les indices min-max [51], qui stockent les valeurs minimale et maximale de l’expression d’index pour chaque bloc d’index. Ce type d’index fonctionne bien pour des données clusterisées localement avec de faibles plages de valeurs absolues, par exemple des données grossièrement triées. 2. les indices Set, qui stockent un nombre configurable de valeurs uniques par bloc d’index. Ces index sont particulièrement adaptés aux données ayant une faible cardinalité locale, c.-à-d. des valeurs « regroupées ». 3. les indices à filtre de Bloom [9] construits pour des valeurs de ligne, de jeton ou de n-gramme avec un taux de faux positifs configurable. Ces index prennent en charge la recherche textuelle [73], mais contrairement aux indices min-max et Set, ils ne peuvent pas être utilisés pour des prédicats d’intervalle ou négatifs.

3.3 Transformation des données lors des fusions

Les cas d’usage de business intelligence et d’observabilité doivent souvent traiter des données générées à des débits durablement élevés ou par pics. En outre, les données récentes sont généralement plus pertinentes pour produire des informations exploitables en temps réel que les données historiques. De tels cas d’usage exigent des bases de données qu’elles prennent en charge des taux élevés d’ingestion de données tout en réduisant en continu le volume des données historiques grâce à des techniques comme l’agrégation ou le vieillissement des données. ClickHouse permet une transformation incrémentielle continue des données existantes au moyen de différentes stratégies de fusion. La transformation des données lors des fusions ne compromet pas les performances des instructions INSERT, mais elle ne peut pas garantir que les tables ne contiennent jamais de valeurs indésirables (par ex. obsolètes ou non agrégées). Si nécessaire, toutes les transformations effectuées lors des fusions peuvent être appliquées au moment de la requête en spécifiant le mot-clé FINAL dans les instructions SELECT. Les fusions de remplacement ne conservent que la version d’un tuple insérée le plus récemment, en se basant sur l’horodatage de création de la part qui le contient ; les versions plus anciennes sont supprimées. Les tuples sont considérés comme équivalents s’ils ont les mêmes valeurs dans les colonnes de clé primaire. Pour contrôler explicitement le tuple conservé, il est également possible de spécifier une colonne de version spéciale pour la comparaison. Les fusions de remplacement sont couramment utilisées comme mécanisme de mise à jour lors des fusions (généralement dans les cas d’usage où les mises à jour sont fréquentes), ou comme alternative à la déduplication des données au moment de l’insert (Section 3.5)). Les fusions d’agrégation regroupent les lignes ayant les mêmes valeurs dans les colonnes de clé primaire en une ligne agrégée. Les colonnes hors clé primaire doivent être dans un état d’agrégation partielle contenant les valeurs de synthèse. Deux états d’agrégation partielle, par ex. une somme et un décompte pour avg(), sont combinés en un nouvel état d’agrégation partielle. Les fusions d’agrégation sont généralement utilisées dans des vues matérialisées plutôt que dans des tables classiques. Les vues matérialisées sont alimentées à partir d’une requête de transformation sur une table source. Contrairement à d’autres bases de données, ClickHouse n’actualise pas périodiquement les vues matérialisées avec l’intégralité du contenu de la table source. Les vues matérialisées sont plutôt mises à jour de manière incrémentielle avec le résultat de la requête de transformation lorsqu’une nouvelle part est insérée dans la table source. La Figure 5 montre une vue matérialisée définie sur une table contenant des statistiques d’impressions de page. Pour les nouvelles parts insérées dans la table source, la requête de transformation calcule les latences maximales et moyennes, regroupées par région, puis insère le résultat dans une vue matérialisée. Les fonctions d’agrégation avg() et max() avec l’extension -State renvoient des états d’agrégation partielle au lieu de résultats effectifs. Une fusion d’agrégation définie pour la vue matérialisée combine en continu les états d’agrégation partielle dans différentes parts. Pour obtenir le résultat final, les utilisateurs consolident les états d’agrégation partielle dans la vue matérialisée à l’aide de avg() et max()) avec l’extension -Merge.
Figure 5 : Fusions d’agrégation dans les vues matérialisées.

Figure 5 : Fusions d’agrégation dans les vues matérialisées.

Les fusions TTL (time-to-live) assurent le vieillissement des données historiques. Contrairement aux fusions de suppression et d’agrégation, les fusions TTL ne traitent qu’une seule part à la fois. Les fusions TTL sont définies en termes de règles avec des déclencheurs et des actions. Un déclencheur est une expression qui calcule un horodatage pour chaque ligne, lequel est comparé au moment où la fusion TTL s’exécute. Bien que cela permette aux utilisateurs de contrôler les actions à la granularité de la ligne, nous avons constaté qu’il suffisait de vérifier si toutes les lignes satisfont une condition donnée et d’exécuter l’action sur l’ensemble de la part. Les actions possibles incluent 1. déplacer la part vers un autre volume (par ex. un stockage plus lent mais moins coûteux), 2. recompresser la part (par ex. avec un codec plus lourd), 3. supprimer la part, et 4. consolider, c.-à-d. agréger les lignes à l’aide d’une clé de regroupement et de fonctions d’agrégation. À titre d’exemple, considérons la définition de la table de journalisation dans Listing 1. ClickHouse déplacera les parts dont les valeurs de la colonne timestamp remontent à plus d’une semaine vers un stockage objet S3 lent mais peu coûteux.
Liste 1 : Déplacer une part vers le stockage d’objets au bout d’une semaine.

3.4 Mises à jour et suppressions

La conception des moteurs de table MergeTree* privilégie les charges de travail en ajout uniquement, mais certains cas d’usage exigent de modifier occasionnellement des données existantes, par exemple pour satisfaire à des exigences de conformité réglementaire. Il existe deux approches pour mettre à jour ou supprimer des données, et aucune ne bloque les insertions parallèles. Les mutations réécrivent sur place toutes les parts d’une table. Pour éviter qu’une table (suppression) ou une colonne (mise à jour) ne double temporairement de taille, cette opération n’est pas atomique, c’est-à-dire que des instructions SELECT parallèles peuvent lire à la fois des parts mutées et non mutées. Les mutations garantissent que les données sont physiquement modifiées à la fin de l’opération. Les mutations de suppression restent coûteuses, car elles réécrivent toutes les colonnes de toutes les parts. Comme alternative, les lightweight deletes ne mettent à jour qu’une colonne bitmap interne, indiquant si une ligne est supprimée ou non. ClickHouse ajoute aux requêtes SELECT un filtre supplémentaire sur la colonne bitmap afin d’exclure les lignes supprimées du résultat. Les lignes supprimées ne sont physiquement retirées que lors des fusions régulières, à un moment ultérieur non spécifié. Selon le nombre de colonnes, les lightweight deletes peuvent être bien plus rapides que les mutations, au prix de SELECT plus lents. Les opérations de mise à jour et de suppression effectuées sur la même table sont censées être rares et sérialisées afin d’éviter les conflits logiques.

3.5 Insertions idempotentes

Un problème fréquent en pratique est de savoir comment les clients doivent gérer les délais d’expiration de connexion après avoir envoyé des données au serveur pour les insérer dans une table. Dans cette situation, il est difficile pour les clients de déterminer si les données ont bien été insérées ou non. Traditionnellement, ce problème est résolu en renvoyant les données du client au serveur et en s’appuyant sur la clé primaire ou sur des contraintes d’unicité pour rejeter les insertions en double. Les bases de données effectuent rapidement les recherches ponctuelles nécessaires à l’aide de structures d’index fondées sur des arbres binaires [39, 68], des arbres radix [45] ou des tables de hachage [29]. Comme ces structures de données indexent chaque tuple, leur coût en espace et en mises à jour devient prohibitif pour de grands jeux de données et des taux d’ingestion élevés. ClickHouse propose une alternative plus légère, fondée sur le fait que chaque insertion finit par créer une part. Plus précisément, le serveur conserve les hachages des N dernières parts insérées (par exemple N=100) et ignore les réinsertions de parts dont le hachage est déjà connu. Les hachages des tables non répliquées et répliquées sont stockés respectivement en local et dans Keeper. Les insertions deviennent ainsi idempotentes, c’est-à-dire que les clients peuvent simplement renvoyer le même lot de lignes après un délai d’expiration et partir du principe que le serveur se charge de la déduplication. Pour un contrôle plus fin du processus de déduplication, les clients peuvent également fournir un jeton d’insertion qui tient lieu de hachage de part. Bien que la déduplication fondée sur le hachage entraîne un surcoût lié au calcul du hachage des nouvelles lignes, le coût de stockage et de comparaison des hachages est négligeable.

3.6 Réplication des données

La réplication est un prérequis pour la haute disponibilité (tolérance aux défaillances des nœuds), mais elle sert aussi à la répartition de charge et aux mises à niveau sans interruption de service [14]. Dans ClickHouse, la réplication repose sur la notion d’états de table, qui se composent d’un ensemble de parts de table (section 3.1) et de métadonnées de table, telles que les noms de colonnes et les types. Les nœuds font évoluer l’état d’une table au moyen de trois opérations : 1. les insertions ajoutent une nouvelle part à l’état, 2. les fusions ajoutent une nouvelle part à l’état et en retirent des parts existantes, 3. les mutations et les instructions DDL ajoutent des parts, et/ou suppriment des parts, et/ou modifient les métadonnées de table, selon l’opération concernée. Les opérations sont exécutées localement sur un seul nœud et consignées comme une séquence de transitions d’état dans un journal global de réplication. Le journal de réplication est maintenu par un ensemble de généralement trois processus ClickHouse Keeper, qui utilisent l’algorithme de consensus Raft [59] pour fournir une couche de coordination distribuée et tolérante aux pannes à un cluster de nœuds ClickHouse. Tous les nœuds du cluster pointent initialement vers la même position dans le journal de réplication. Tandis que les nœuds exécutent localement des insertions, des fusions, des mutations et des instructions DDL, le journal de réplication est rejoué de manière asynchrone sur tous les autres nœuds. En conséquence, les tables répliquées ne sont cohérentes qu’à terme, c’est-à-dire que les nœuds peuvent temporairement lire d’anciens états de table tout en convergeant vers l’état le plus récent. La plupart des opérations susmentionnées peuvent aussi être exécutées de manière synchrone jusqu’à ce qu’un quorum de nœuds (par exemple, une majorité des nœuds ou l’ensemble des nœuds) ait adopté le nouvel état. À titre d’exemple, la figure 6 montre une table répliquée initialement vide dans un cluster de trois nœuds ClickHouse. Le nœud 1 reçoit d’abord deux instructions d’insertion et les consigne ( 1 2 ) dans le journal de réplication stocké dans l’ensemble Keeper. Ensuite, le nœud 2 rejoue la première entrée du journal en la récupérant ( 3 ) et en téléchargeant la nouvelle part depuis le nœud 1 ( 4 ), tandis que le nœud 3 rejoue les deux entrées du journal ( 3 4 5 6 ). Enfin, le nœud 3 fusionne les deux parts en une nouvelle part, supprime les parts d’entrée et consigne une entrée de fusion dans le journal de réplication ( 7 ).
Figure 6 : Réplication dans un cluster de trois nœuds.

Figure 6 : Réplication dans un cluster de trois nœuds.

Il existe trois optimisations pour accélérer la synchronisation : premièrement, les nouveaux nœuds ajoutés au cluster ne rejouent pas le journal de réplication depuis le début ; ils copient simplement l’état du nœud qui a écrit la dernière entrée du journal de réplication. Deuxièmement, les fusions sont rejouées soit en les répétant localement, soit en récupérant la part résultante depuis un autre nœud. Le comportement exact est configurable et permet d’arbitrer entre consommation CPU et E/S réseau. Par exemple, la réplication entre centres de données privilégie généralement les fusions locales afin de minimiser les coûts d’exploitation. Troisièmement, les nœuds rejouent en parallèle les entrées mutuellement indépendantes du journal de réplication. Cela inclut, par exemple, la récupération de nouvelles parts insérées consécutivement dans la même table, ou des opérations sur différentes tables.

3.7 Conformité ACID

Pour maximiser les performances des opérations concurrentes de lecture et d’écriture, ClickHouse évite autant que possible le verrouillage. Les requêtes sont exécutées sur un instantané de toutes les parts de toutes les tables impliquées, créé au début de la requête. Cela garantit que les nouvelles parts insérées par des INSERT parallèles ou des fusions (section 3.1) ne participent pas à l’exécution. Pour éviter que des parts soient modifiées ou supprimées simultanément (section 3.4), le compteur de références des parts traitées est incrémenté pendant toute la durée de la requête. Formellement, cela correspond à une isolation par instantané reposant sur une variante de MVCC [6] basée sur des parts versionnées. Par conséquent, les instructions ne sont généralement pas conformes à ACID, sauf dans le cas rare où les écritures concurrentes au moment de la prise de l’instantané n’affectent chacune qu’une seule part. En pratique, la plupart des cas d’usage de ClickHouse orientés prise de décision et générant beaucoup d’écritures tolèrent même un faible risque de perte de nouvelles données en cas de panne de courant. La base de données en tire parti en ne forçant pas, par défaut, le commit (fsync) des parts nouvellement insérées sur disque, ce qui permet au noyau de regrouper les écritures au prix de l’abandon de l’atomicité.

4 COUCHE DE TRAITEMENT DES REQUÊTES

Figure 7 : Parallélisation entre les unités SIMD, les cœurs et les nœuds.

Figure 7 : Parallélisation entre les unités SIMD, les cœurs et les nœuds.

Comme l’illustre la figure 7, ClickHouse parallélise les requêtes au niveau des éléments de données, des blocs de données et des segments de table. Plusieurs éléments de données peuvent être traités en une seule fois au sein des opérateurs à l’aide d’instructions SIMD. Sur un nœud unique, le moteur de requêtes exécute les opérateurs en parallèle dans plusieurs threads. ClickHouse utilise le même modèle de vectorisation que MonetDB/X100 [11] : les opérateurs produisent, transmettent et consomment plusieurs lignes (blocs de données) au lieu d’une seule, afin de minimiser le surcoût des appels de fonctions virtuelles. Si une table source est répartie en segments de table disjoints, plusieurs nœuds peuvent parcourir ces segments simultanément. Toutes les ressources matérielles sont ainsi pleinement exploitées, et le traitement des requêtes peut être mis à l’échelle horizontalement en ajoutant des nœuds et verticalement en ajoutant des cœurs. Le reste de cette section décrit d’abord plus en détail le traitement parallèle à la granularité des éléments de données, des blocs de données et des segments. Nous présentons ensuite quelques optimisations clés pour maximiser les performances des requêtes. Enfin, nous examinons la manière dont ClickHouse gère les ressources système partagées en présence de requêtes simultanées.

4.1 Parallélisation SIMD

Le passage de plusieurs lignes entre les opérateurs crée une opportunité de vectorisation. La vectorisation repose soit sur des intrinsèques écrits manuellement [64, 80], soit sur l’auto-vectorisation du compilateur [25]. Le code qui en bénéficie est compilé en différents noyaux de calcul. Par exemple, la boucle interne critique d’un opérateur de requête peut être implémentée sous la forme d’un noyau non vectorisé, d’un noyau AVX2 auto-vectorisé et d’un noyau AVX-512 vectorisé manuellement. Le noyau le plus rapide est choisi à l’exécution en fonction de l’instruction cpuid. Cette approche permet à ClickHouse de fonctionner sur des systèmes vieux de 15 ans (avec SSE 4.2 comme minimum), tout en offrant des accélérations significatives sur du matériel récent.

4.2 Parallélisation multicœur

Figure 8 : un plan physique d'opérateurs avec trois voies d'exécution.

Figure 8 : un plan physique d'opérateurs avec trois voies d'exécution.

ClickHouse suit l’approche conventionnelle [31] consistant à transformer les requêtes SQL en un graphe orienté d’opérateurs du plan physique. L’entrée du plan d’opérateurs est représentée par des opérateurs source spéciaux qui lisent les données au format natif ou dans l’un des formats tiers pris en charge (voir la section 5). De même, un opérateur sink spécial convertit le résultat dans le format de sortie souhaité. Le plan physique d’opérateurs est déployé, au moment de la compilation de la requête, en voies d’exécution indépendantes, en fonction d’un nombre maximal configurable de worker threads (par défaut, le nombre de cœurs) et de la taille de la table source. Les voies d’exécution décomposent les données à traiter par les opérateurs parallèles en plages sans chevauchement. Afin de maximiser les possibilités de traitement en parallèle, les voies d’exécution sont fusionnées aussi tard que possible. À titre d’exemple, l’encadré du nœud 1 de la figure 8 montre le graphe d’opérateurs d’une requête OLAP typique exécutée sur une table contenant des statistiques d’impressions de pages. Dans la première étape, trois plages disjointes de la table source sont filtrées simultanément. Un opérateur Repartition exchange achemine dynamiquement les fragments de résultats entre la première et la deuxième étape afin de maintenir une charge équilibrée entre les threads de traitement. Les voies d’exécution peuvent devenir déséquilibrées après la première étape si les plages analysées présentent des sélectivités sensiblement différentes. Dans la deuxième étape, les lignes qui ont passé le filtre sont regroupées par RegionID. Les opérateurs Aggregate maintiennent des groupes de résultats locaux avec RegionID comme colonne de regroupement, ainsi qu’une somme et un décompte par groupe comme état d’agrégation partiel pour avg(). Les résultats d’agrégation locaux sont ensuite fusionnés par un opérateur GroupStateMerge en un résultat d’agrégation global. Cet opérateur est aussi un pipeline breaker, c’est-à-dire que la troisième étape ne peut commencer qu’une fois le résultat d’agrégation entièrement calculé. Dans la troisième étape, les groupes de résultats sont d’abord répartis par un opérateur Distribute exchange en trois partitions disjointes de taille égale, qui sont ensuite triées par AvgLatency. Le tri s’effectue en trois étapes : d’abord, les opérateurs ChunkSort trient les fragments individuels de chaque partition. Ensuite, les opérateurs StreamSort maintiennent un résultat trié local, qu’ils combinent avec les fragments triés entrants à l’aide d’un tri par fusion à deux voies. Enfin, un opérateur MergeSort combine les résultats locaux au moyen d’un tri à k voies afin d’obtenir le résultat final. Les opérateurs sont des machines à états et sont reliés entre eux via des ports d’entrée et de sortie. Les trois états possibles d’un opérateur sont need-chunk, ready et done. Pour passer de need-chunk à ready, un fragment est placé dans le port d’entrée de l’opérateur. Pour passer de ready à done, l’opérateur traite le fragment d’entrée et génère un fragment de sortie. Pour passer de done à need-chunk, le fragment de sortie est retiré du port de sortie de l’opérateur. Les opérateurs source (et les opérateurs sink) n’ont que les états ready et done (respectivement need-chunk et done). Les worker threads parcourent en continu le plan physique d’opérateurs et effectuent les transitions d’état. Pour garder les caches CPU chauds, le plan contient des indications selon lesquelles le même thread doit traiter des opérateurs consécutifs dans une même voie d’exécution. Le traitement en parallèle se produit à la fois horizontalement entre des entrées disjointes au sein d’une étape (par exemple, dans la figure 8, les opérateurs Aggregate sont exécutés de manière concurrente) et verticalement entre des étapes non séparées par des pipeline breakers (par exemple, dans la figure 8, les opérateurs Filter et Aggregate d’une même voie d’exécution peuvent s’exécuter simultanément). Afin d’éviter la surallocation comme la sous-allocation lorsque de nouvelles requêtes démarrent ou que des requêtes concurrentes se terminent, le degré de parallélisme peut être modifié en cours d’exécution de la requête entre un et le nombre maximal de worker threads défini pour la requête à son démarrage (voir la section 4.5)). Les opérateurs peuvent en outre influer sur l’exécution des requêtes à l’exécution de deux façons. Premièrement, les opérateurs peuvent créer et connecter dynamiquement de nouveaux opérateurs. Cela est principalement utilisé pour basculer vers des algorithmes d’aggregation externe, de tri ou de join, au lieu d’annuler une requête lorsque la consommation mémoire dépasse un seuil configurable. Deuxièmement, les opérateurs peuvent demander à des worker threads de passer dans une file d’attente asynchrone. Cela permet une utilisation plus efficace des worker threads lors de l’attente de données distantes. Le moteur d’exécution des requêtes de ClickHouse et le parallélisme piloté par morsels [44] se ressemblent en ce que les voies sont généralement exécutées sur des cœurs / sockets NUMA différents et que les threads de travail peuvent voler des tâches à d’autres voies. De plus, il n’existe pas de composant central d’ordonnancement ; à la place, les threads de travail sélectionnent individuellement leurs tâches en parcourant en continu le plan d’opérateurs. Contrairement au parallélisme piloté par morsels, ClickHouse intègre le degré maximal de parallélisme au plan et utilise des ranges bien plus grands pour partitionner la table source, par rapport aux tailles de morsel par défaut d’environ 100 000 lignes. Bien que cela puisse, dans certains cas, provoquer des blocages (par exemple lorsque le temps d’exécution des opérateurs de filtrage dans différentes voies varie fortement), nous constatons qu’un usage généreux d’opérateurs d’échange tels que Repartition évite au moins que de tels déséquilibres ne s’accumulent d’une étape à l’autre.

4.3 Parallélisation sur plusieurs nœuds

Si la table source d’une requête est fragmentée, l’optimiseur de requête du nœud qui a reçu la requête (nœud initiateur) tente de faire exécuter autant de travail que possible sur les autres nœuds. Les résultats provenant des autres nœuds peuvent être intégrés à différents endroits du plan de requête. Selon la requête, les nœuds distants peuvent soit 1. envoyer en flux les colonnes brutes de la table source au nœud initiateur, 2. filtrer les colonnes de la table source et envoyer les lignes conservées, 3. exécuter les étapes de filtrage et d’agrégation, puis envoyer des groupes de résultats locaux avec des états d’agrégation partiels, ou 4. exécuter l’intégralité de la requête, y compris les filtres, l’agrégation et le tri. Les nœuds 2 … N de la Figure 8 montrent des fragments de plan exécutés sur d’autres nœuds hébergeant des fragments de la table hits. Ces nœuds filtrent et regroupent les données locales, puis envoient le résultat au nœud initiateur. L’opérateur GroupStateMerge sur le nœud 1 fusionne les résultats locaux et distants avant le tri final des groupes de résultats.

4.4 Optimisation globale des performances

Cette section présente une sélection des principales optimisations de performances appliquées aux différentes étapes de l’exécution des requêtes. Optimisation des requêtes. Le premier ensemble d’optimisations s’applique à une représentation sémantique de la requête obtenue à partir de son AST. Parmi ces optimisations figurent le constant folding (par ex. concat(lower(‘a’),upper(‘b’)) devient ‘aB’), l’extraction de scalaires de certaines fonctions d’agrégation (par ex. sum(a2) devient 2 * sum(a)), l’élimination des sous-expressions communes et la transformation de disjonctions de filtres d’égalité en listes IN (par ex. x=c OR x=d devient x IN (c,d)). La représentation sémantique optimisée de la requête est ensuite transformée en plan logique d’opérateurs. Les optimisations appliquées à ce plan logique incluent le pushdown des filtres, ainsi que la réorganisation de l’évaluation des fonctions et des étapes de tri selon celle qui est estimée la plus coûteuse. Enfin, le plan logique de la requête est transformé en plan physique d’opérateurs. Cette transformation peut exploiter les particularités des moteur de table impliqués. Par exemple, dans le cas d’un table engine MergeTree, si les colonnes ORDER BY forment un préfixe de la clé primaire, les données peuvent être lues dans l’ordre du disque et les opérateurs de tri peuvent être supprimés du plan. De plus, si les colonnes de regroupement d’une agrégation forment un préfixe de la clé primaire, ClickHouse peut utiliser l’agrégation par tri [33], c’est-à-dire agréger directement les séquences de même valeur dans les entrées prétriées. Par rapport à l’agrégation par hachage, l’agrégation par tri consomme nettement moins de mémoire, et la valeur agrégée peut être transmise à l’opérateur suivant immédiatement après le traitement d’une séquence. Compilation des requêtes. ClickHouse utilise la compilation des requêtes basée sur LLVM pour fusionner dynamiquement des opérateurs de plan adjacents [38, 53]. Par exemple, l’expression a * b + c + 1 peut être combinée en un seul opérateur au lieu de trois. Outre les expressions, ClickHouse utilise également la compilation pour évaluer plusieurs fonctions d’agrégation en une seule fois (c.-à-d. pour GROUP BY) et pour le tri avec plusieurs clés de tri. La compilation des requêtes réduit le nombre d’appels virtuels, conserve les données dans les registres ou les caches CPU, et aide le prédicteur de branchement, car moins de code doit être exécuté. En outre, la compilation à l’exécution permet de tirer parti d’un large éventail d’optimisations, telles que les optimisations logiques et les optimisations locales implémentées dans les compilateurs, et donne accès aux instructions CPU locales les plus rapides disponibles. La compilation n’est déclenchée que lorsque la même expression régulière, d’agrégation ou de tri est exécutée par différentes requêtes plus d’un nombre configurable de fois. Les opérateurs de requête compilés sont mis en cache et peuvent être réutilisés par de futures requêtes.[7] Évaluation de l’index de clé primaire. ClickHouse évalue les conditions WHERE à l’aide de l’index de clé primaire si un sous-ensemble des clauses de filtre de la forme normale conjonctive de la condition constitue un préfixe des colonnes de la clé primaire. L’index de clé primaire est analysé de gauche à droite sur des plages de valeurs de clé triées lexicographiquement. Les clauses de filtre correspondant à une colonne de clé primaire sont évaluées à l’aide d’une logique ternaire : elles sont soit toutes vraies, soit toutes fausses, soit mixtes pour les valeurs de la plage. Dans ce dernier cas, la plage est divisée en sous-plages, qui sont analysées récursivement. Des optimisations supplémentaires existent pour les fonctions dans les conditions de filtre. Premièrement, les fonctions possèdent des propriétés décrivant leur monotonie ; par ex., toDayOfMonth(date) est monotone par morceaux à l’intérieur d’un mois. Ces propriétés de monotonie permettent de déduire si une fonction produit des résultats triés à partir de plages triées de valeurs de clé en entrée. Deuxièmement, certaines fonctions peuvent calculer l’image réciproque d’un résultat de fonction donné. Cela permet de remplacer des comparaisons de constantes avec des appels de fonction sur les colonnes de clé par des comparaisons entre la valeur de la colonne de clé et l’image réciproque. Par exemple, toYear(k) = 2024 peut être remplacé par k >= 2024-01-01 && k < 2025-01-01. Évitement de lecture de données. ClickHouse essaie d’éviter les lectures de données à l’exécution de la requête en utilisant les structures de données présentées dans la section 3.2. En outre, les filtres sur différentes colonnes sont évalués séquentiellement par ordre décroissant de sélectivité estimée, sur la base d’heuristiques et de statistiques de colonnes (facultatives). Seuls les chunks de données qui contiennent au moins une ligne correspondante sont transmis au prédicat suivant. Cela réduit progressivement la quantité de données lues et le nombre de calculs à effectuer d’un prédicat à l’autre. Cette optimisation n’est appliquée que lorsqu’au moins un prédicat hautement sélectif est présent ; sinon, la latence de la requête se dégraderait par rapport à une évaluation de tous les prédicats en parallèle. Tables de hachage. Les tables de hachage sont des structures de données fondamentales pour l’agrégation et les jointures par hachage. Choisir le bon type de table de hachage est essentiel pour les performances. ClickHouse instancie diverses tables de hachage (plus de 30 en mars 2024) à partir d’un modèle générique de table de hachage, avec pour points de variation la fonction de hachage, l’allocateur, le type de cellule et la politique de redimensionnement. Selon le type de données des colonnes de regroupement, la cardinalité estimée de la table de hachage et d’autres facteurs, la table de hachage la plus rapide est sélectionnée individuellement pour chaque opérateur de requête. Parmi les autres optimisations mises en œuvre pour les tables de hachage figurent :
  • une organisation à deux niveaux avec 256 sous-tables (basées sur le premier octet du hachage) pour prendre en charge d’énormes ensembles de clés,
  • des tables de hachage pour chaînes [79] avec quatre sous-tables et différentes fonctions de hachage selon la longueur des chaînes,
  • des tables de lookup qui utilisent directement la clé comme indice de bucket (c’est-à-dire sans hachage) lorsqu’il n’y a que peu de clés,
  • des valeurs avec hachages intégrés pour accélérer la résolution des collisions lorsque la comparaison est coûteuse (par ex. chaînes, AST),
  • la création de tables de hachage fondée sur des tailles prédites à partir de statistiques d’exécution afin d’éviter des redimensionnements inutiles,
  • l’allocation de plusieurs petites tables de hachage ayant le même cycle de vie de création/destruction dans une même tranche mémoire,
  • l’effacement instantané des tables de hachage en vue de leur réutilisation à l’aide de compteurs de version par table de hachage et par cellule,
  • l’utilisation de la prélecture CPU (__builtin_prefetch) pour accélérer la récupération des valeurs après le hachage de la clé.
Jointures. Comme ClickHouse ne prenait initialement en charge les jointures que de manière rudimentaire, de nombreux cas d’usage ont historiquement eu recours à des tables dénormalisées. Aujourd’hui, la base de données propose tous les types de jointure disponibles en SQL (inner, left- /right/full outer, cross, as-of), ainsi que différents algorithmes de jointure tels que la jointure par hachage (naïf, grace), la jointure par tri-fusion et la jointure par index pour les moteurs de table offrant une recherche key-value rapide (généralement les dictionnaires). Comme les jointures comptent parmi les opérations de base de données les plus coûteuses, il est important de proposer des variantes parallèles des algorithmes de jointure classiques, idéalement avec des compromis espace/temps configurables. Pour les jointures par hachage, ClickHouse implémente l’algorithme non bloquant à partitions partagées décrit dans [7]. Par exemple, la requête de la Figure 9 calcule comment les utilisateurs passent d’une URL à l’autre via une self-join sur une table de statistiques de visites de pages. La phase de build de la jointure est divisée en trois voies, couvrant trois plages disjointes de la table source. Au lieu d’une table de hachage globale, une table de hachage partitionnée est utilisée. Les (généralement trois) threads de travail déterminent la partition cible pour chaque ligne d’entrée du côté build en calculant le modulo d’une fonction de hachage. L’accès aux partitions de la table de hachage est synchronisé à l’aide d’opérateurs Gather exchange. La phase de probe détermine de façon similaire la partition cible de ses tuples d’entrée. Bien que cet algorithme introduise deux calculs de hachage supplémentaires par tuple, il réduit fortement la contention sur les verrous pendant la phase de build, selon le nombre de partitions de la table de hachage.
Figure 9 : Jointure par hachage parallèle avec trois partitions de table de hachage.

Figure 9 : Jointure par hachage parallèle avec trois partitions de table de hachage.

4.5 Isolation des charges de travail

ClickHouse offre un contrôle de la concurrence, des limites d’utilisation de la mémoire et une planification des E/S, ce qui permet aux utilisateurs d’isoler les requêtes dans des classes de charge de travail. En définissant des limites sur les ressources partagées (cœurs de CPU, DRAM, E/S disque et réseau) pour des classes de charge de travail spécifiques, ClickHouse garantit que ces requêtes n’affectent pas d’autres requêtes métier critiques. Le contrôle de la concurrence évite le surabonnement des threads dans les scénarios comportant un grand nombre de requêtes concurrentes. Plus précisément, le nombre de threads de travail par requête est ajusté dynamiquement selon un ratio spécifié par rapport au nombre de cœurs de CPU disponibles. ClickHouse suit la taille en octets des allocations mémoire au niveau du serveur, de l’utilisateur et de la requête, et permet ainsi de définir des limites souples d’utilisation de la mémoire. Le memory overcommit permet aux requêtes d’utiliser davantage de mémoire libre au-delà de la mémoire garantie, tout en garantissant des limites mémoire pour les autres requêtes. En outre, l’utilisation de la mémoire pour les clauses d’aggregation, de sort et de join peut être limitée, ce qui entraîne un basculement vers des algorithmes externes lorsque la limite mémoire est dépassée. Enfin, la planification des E/S permet aux utilisateurs de restreindre les accès aux disques locaux et distants pour les classes de charge de travail selon une bande passante maximale, les requêtes en cours et la politique (par ex. FIFO, SFC [32]).

5 COUCHE D’INTÉGRATION

Les applications de prise de décision en temps réel dépendent souvent d’un accès efficace et à faible latence à des données réparties sur plusieurs sites. Il existe deux approches pour rendre des données externes accessibles dans une base de données OLAP. Avec un accès aux données de type push, un composant tiers fait le lien entre la base de données et des systèmes de stockage externes. Les outils spécialisés d’extract-transform-load (ETL), qui poussent les données distantes vers le système de destination, en sont un exemple. Dans le modèle pull, la base de données elle-même se connecte aux sources de données distantes et récupère les données pour les interroger dans des tables locales, ou exporte les données vers des systèmes distants. Bien que les approches de type push soient plus polyvalentes et plus répandues, elles impliquent une architecture plus lourde et créent un goulot d’étranglement en matière de montée en charge. À l’inverse, la connectivité distante directement dans la base de données offre des capacités intéressantes, comme les jointures entre données locales et distantes, tout en conservant une architecture simple et en réduisant le temps nécessaire pour obtenir des insights. Le reste de cette section explore les méthodes d’intégration de données de type pull dans ClickHouse, conçues pour accéder à des données distantes. Notons que l’idée de la connectivité distante dans les bases de données SQL n’est pas nouvelle. Par exemple, le standard SQL/MED [35], introduit en 2001 et implémenté par PostgreSQL depuis 2011 [65], propose les foreign data wrappers comme interface unifiée pour gérer des données externes. Une interopérabilité maximale avec d’autres systèmes de stockage et formats de données est l’un des objectifs de conception de ClickHouse. En mars 2024, ClickHouse offre, à notre connaissance, le plus grand nombre d’options d’intégration de données intégrées parmi toutes les bases de données analytiques. Connectivité externe. ClickHouse fournit plus de 50 fonctions de table et moteurs d’intégration pour se connecter à des systèmes externes et à des emplacements de stockage, notamment ODBC, MySQL, PostgreSQL, SQLite, Kafka, Hive, MongoDB, Redis, les stockages objet S3/GCP/Azure et divers data lakes. Nous les répartissons plus en détail dans les catégories présentées dans la figure bonus suivante (qui ne fait pas partie de l’article original du VLDB).
Figure bonus : options d’interopérabilité de ClickBench.

Figure bonus : options d’interopérabilité de ClickBench.

Accès temporaire avec les fonctions de table d’intégration. Les fonctions de table peuvent être invoquées dans la clause FROM des requêtes SELECT pour lire des données distantes dans le cadre de requêtes exploratoires ad hoc. Elles peuvent aussi être utilisées pour écrire des données dans des stockages distants à l’aide d’instructions INSERT INTO TABLE FUNCTION. Accès persistant. Il existe trois méthodes pour créer des connexions permanentes avec des systèmes distants de stockage et de traitement des données. Premièrement, les moteurs de table d’intégration représentent une source de données distante, telle qu’une table MySQL, sous la forme d’une table locale persistante. Les utilisateurs enregistrent la définition de la table à l’aide de la syntaxe CREATE TABLE AS, combinée à une requête SELECT et à la fonction de table. Il est possible de spécifier un schéma personnalisé, par exemple pour ne cibler qu’un sous-ensemble des colonnes distantes, ou d’utiliser l’inférence de schéma pour déterminer automatiquement les noms de colonnes et les types ClickHouse équivalents. Nous distinguons en outre les comportements d’exécution passif et actif : les moteurs de table passifs transmettent les requêtes au système distant et alimentent une table proxy locale avec le résultat. À l’inverse, les moteurs de table actifs extraient périodiquement les données du système distant ou s’abonnent aux modifications distantes, par exemple via le protocole de réplication logique de PostgreSQL. Par conséquent, la table locale contient une copie complète de la table distante. Deuxièmement, les moteurs de base de données d’intégration associent dans ClickHouse toutes les tables d’un schéma de base de données d’un stockage de données distant. Contrairement aux précédents, ils exigent généralement que le stockage de données distant soit une base de données relationnelle et offrent en plus une prise en charge limitée des instructions DDL. Troisièmement, les dictionnaires peuvent être alimentés à l’aide de requêtes arbitraires sur presque toutes les sources de données possibles disposant d’une fonction de table d’intégration ou d’un moteur correspondant. Le comportement d’exécution est actif, puisque les données sont extraites du stockage distant à intervalles réguliers. Formats de données. Pour interagir avec des systèmes tiers, les bases de données analytiques modernes doivent aussi être capables de traiter des données dans n’importe quel format. En plus de son format natif, ClickHouse prend en charge plus de 90 formats, notamment CSV, JSON, Parquet, Avro, ORC, Arrow et Protobuf. Chaque format peut être un format d’entrée (que ClickHouse peut lire), un format de sortie (que ClickHouse peut exporter), ou les deux. Certains formats orientés analytique, comme Parquet, sont également intégrés au traitement des requêtes : l’optimiseur peut exploiter les statistiques embarquées, et les filtres sont évalués directement sur les données compressées. Interfaces de compatibilité. Outre son protocole binaire natif et HTTP, les clients peuvent interagir avec ClickHouse via des interfaces compatibles avec les protocoles réseau MySQL ou PostgreSQL. Cette fonctionnalité de compatibilité est utile pour permettre l’accès depuis des applications propriétaires (par exemple certains outils de business intelligence), lorsque les éditeurs n’ont pas encore implémenté de connectivité ClickHouse native.

6 LA PERFORMANCE COMME FONCTIONNALITÉ

Cette section présente les outils intégrés d’analyse des performances et évalue celles-ci à l’aide de requêtes issues de cas réels et de benchmarks.

6.1 Outils intégrés d’analyse des performances

Un large éventail d’outils est disponible pour analyser les goulots d’étranglement des performances dans des requêtes individuelles ou des opérations en arrière-plan. Les utilisateurs interagissent avec tous ces outils via une interface uniforme basée sur les tables système. Métriques du serveur et des requêtes. Les statistiques au niveau du serveur, telles que le nombre de parts actives, le débit réseau et les taux de réussite du cache, sont complétées par des statistiques par requête, comme le nombre de blocks lus ou les statistiques d’utilisation des index. Les métriques sont calculées de manière synchrone (à la demande) ou asynchrone, à des intervalles configurables. Profiler par échantillonnage. Les piles d’appels des threads du serveur peuvent être collectées à l’aide d’un profiler par échantillonnage. Les résultats peuvent ensuite être exportés, si besoin, vers des outils externes tels que des visualiseurs de flamegraph. Intégration OpenTelemetry. OpenTelemetry est une norme ouverte de tracing des lignes de données à travers plusieurs systèmes de traitement des données [8]. ClickHouse peut générer des spans OpenTelemetry avec une granularité configurable pour toutes les étapes du traitement des requêtes, ainsi que collecter et analyser des spans OpenTelemetry provenant d’autres systèmes. Requête EXPLAIN. Comme dans d’autres bases de données, les requêtes SELECT peuvent être précédées de EXPLAIN pour obtenir des informations détaillées sur l’AST d’une requête, ses plans d’opérateurs logiques et physiques, et son comportement à l’exécution.

6.2 Tests de performance

Bien que les tests de performance aient été critiqués pour leur manque de réalisme [10, 52, 66, 74], ils restent utiles pour identifier les forces et les faiblesses des bases de données. Dans ce qui suit, nous expliquons comment les tests de performance sont utilisés pour évaluer les performances de ClickHouse.

6.2.1 Tables dénormalisées

Les requêtes de filtrage et d’agrégation sur des tables de faits dénormalisées constituent historiquement le principal cas d’usage de ClickHouse. Nous présentons les temps d’exécution de ClickBench, une charge de travail typique de ce type qui simule des requêtes de reporting ad hoc et périodiques utilisées pour l’analyse des flux de clics et du trafic. Le benchmark se compose de 43 requêtes sur une table contenant 100 millions de visites de pages anonymisées, issues de l’une des plus grandes plateformes d’analytics du web. Un dashboard en ligne [17] affiche les mesures (temps d’exécution à froid/à chaud, temps d’importation des données, taille sur disque) pour plus de 45 bases de données commerciales et de recherche, en date de juin 2024. Les résultats sont soumis par des contributeurs indépendants à partir du jeu de données et des requêtes disponibles publiquement [16]. Les requêtes testent des chemins d’accès par scan séquentiel et par scan d’index, et mettent régulièrement en évidence des opérateurs relationnels limités par le CPU, les IO ou la mémoire. La figure 10 montre les temps d’exécution relatifs cumulés à froid et à chaud pour l’exécution séquentielle de toutes les requêtes ClickBench dans des bases de données fréquemment utilisées pour l’analytics. Les mesures ont été effectuées sur une instance AWS EC2 c6a.4xlarge mono-nœud avec 16 vCPU, 32 Go de RAM et un disque de 5000 IOPS / 1000 MiB/s. Des systèmes comparables ont été utilisés pour Redshift (ra3.4xlarge, 12 vCPU, 96 Go de RAM) et Snowfake (warehouse size S: 2x8 vCPU, 2x16 Go de RAM). La conception physique de la base de données n’est ajustée que légèrement ; par exemple, nous spécifions des clés primaires, mais nous ne modifions pas la compression des colonnes individuelles et ne créons ni projections ni index de saut. Nous vidons également le page cache Linux avant chaque exécution de requête à froid, mais nous n’ajustons pas les paramètres de la base de données ou du système d’exploitation. Pour chaque requête, le temps d’exécution le plus rapide parmi les bases de données est utilisé comme référence. Les temps d’exécution relatifs des requêtes pour les autres bases de données sont calculés comme ( + 10)/(_ + 10). Le temps d’exécution relatif total d’une base de données est la moyenne géométrique des ratios par requête. Bien que la base de données de recherche Umbra [54] obtienne le meilleur temps d’exécution global à chaud, ClickHouse surpasse toutes les autres bases de données de niveau production, aussi bien à chaud qu’à froid.
Figure 10 : temps d’exécution relatifs à froid et à chaud de ClickBench.

Figure 10 : temps d’exécution relatifs à froid et à chaud de ClickBench.

Pour suivre dans le temps les performances des SELECTs sur des charges de travail plus variées, nous utilisons une combinaison de quatre benchmarks appelée VersionsBench [19]. Ce benchmark est exécuté une fois par mois, lorsqu’une nouvelle release est publiée, afin d’en évaluer les performances [20] et d’identifier les modifications de code ayant potentiellement entraîné une baisse de performances : les benchmarks individuels incluent : 1. ClickBench (décrit ci-dessus), 2. 15 requêtes MgBench [21], 3. 13 requêtes sur une table de faits dénormalisée Star Schema Benchmark [57] contenant 600 millions de lignes. 4. 4 requêtes sur NYC Taxi Rides avec 3,4 milliards de lignes [70]. La figure 11 montre l’évolution des temps d’exécution de VersionsBench pour 77 versions de ClickHouse entre mars 2018 et mars 2024. Pour compenser les écarts de temps d’exécution relatif entre les requêtes individuelles, nous normalisons les temps d’exécution à l’aide d’une moyenne géométrique, avec comme poids le ratio par rapport au temps d’exécution minimal de chaque requête parmi toutes les versions. Les performances de VersionBench se sont améliorées d’un facteur de 1,72 × au cours des six dernières années. Les dates des releases avec support à long terme (LTS) sont indiquées sur l’axe des x. Bien que les performances se soient temporairement dégradées sur certaines périodes, les releases LTS présentent généralement des performances comparables ou supérieures à celles de la version LTS précédente. L’amélioration significative d’août 2022 a été provoquée par la technique d’évaluation des filtres colonne par colonne décrite dans la section 4.4.
Figure 11 : Temps d’exécution relatifs en cache chaud de VersionsBench 2018-2024.

Figure 11 : Temps d’exécution relatifs en cache chaud de VersionsBench 2018-2024.

6.2.2 Tables normalisées

Dans les entrepôts de données classiques, les données sont souvent modélisées selon des schémas en étoile ou en flocon. Nous présentons les temps d’exécution des requêtes TPC-H (facteur d’échelle 100), tout en soulignant que les tables normalisées constituent un cas d’usage émergent pour ClickHouse. La Figure 12 montre les temps d’exécution à chaud des requêtes TPC-H obtenus avec l’algorithme de jointure par hachage parallèle décrit à la section 4.4. Les mesures ont été effectuées sur une seule instance AWS EC2 c6i.16xlarge, avec 64 vCPU, 128 GB de RAM et un disque à 5000 IOPS / 1000 MiB/s. Le meilleur temps parmi cinq exécutions a été retenu. À titre de référence, nous avons effectué les mêmes mesures sur un système Snowfake de taille comparable (taille de warehouse L, 8x8 vCPU, 8x16 GB de RAM). Les résultats de onze requêtes sont exclus du tableau : les requêtes Q2, Q4, Q13, Q17 et Q20-22 incluent des sous-requêtes corrélées, qui ne sont pas prises en charge à ce jour dans ClickHouse v24.6. Les requêtes Q7-Q9 et Q19 dépendent d’optimisations étendues au niveau du plan pour les jointures, comme la réorganisation des jointures et le pushdown des prédicats de jointure (toutes deux absentes à ce jour dans ClickHouse v24.6.), pour atteindre des temps d’exécution acceptables. La décorrélation automatique des sous-requêtes et une meilleure prise en charge des jointures par l’optimiseur sont prévues pour 2024 [18]. Parmi les 11 requêtes restantes, 5 (6) s’exécutent plus rapidement dans ClickHouse (Snowfake). Les optimisations mentionnées ci-dessus étant connues pour être essentielles aux performances [27], nous nous attendons à ce qu’une fois implémentées, elles améliorent encore les temps d’exécution de ces requêtes.
Figure 12 : Temps d’exécution à chaud (en secondes) des requêtes TPC-H.

Figure 12 : Temps d’exécution à chaud (en secondes) des requêtes TPC-H.

Les bases de données analytiques suscitent un vif intérêt académique et commercial depuis plusieurs décennies [1]. Les premiers systèmes comme Sybase IQ [48], Teradata [72], Vertica [42] et Greenplum [47] se caractérisaient par des traitements ETL par lots coûteux et une élasticité limitée du fait de leur nature on-premise. Au début des années 2010, l’avènement des entrepôts de données cloud-native et des offres de base de données en tant que service (DBaaS) telles que Snowfake [22], BigQuery [49] et Redshift [4] a considérablement réduit le coût et la complexité de l’analytique pour les organisations, tout en apportant une haute disponibilité et une mise à l’échelle automatique des ressources. Plus récemment, des noyaux d’exécution analytiques (par ex. Photon [5] et Velox [62]) proposent un traitement de données réutilisable dans différentes applications analytiques, de streaming et d’apprentissage automatique. Les bases de données les plus proches de ClickHouse, en termes d’objectifs et de principes de conception, sont Druid [78] et Pinot [34]. Ces deux systèmes visent l’analytique en temps réel avec des débits élevés d’ingestion de données. Comme dans ClickHouse, les tables sont divisées en parties horizontales appelées segments. Alors que ClickHouse fusionne en continu les petites parties et réduit éventuellement les volumes de données à l’aide des techniques de la section 3.3, les parties restent immuables de façon permanente dans Druid et Pinot. En outre, Druid et Pinot nécessitent des nœuds spécialisés pour créer, modifier et interroger les tables, tandis que ClickHouse utilise un binaire monolithique pour ces tâches. Snowfake [22] est un entrepôt de données cloud propriétaire populaire reposant sur une architecture shared-disk. Son approche consistant à diviser les tables en micro-partitions est similaire au concept de parties dans ClickHouse. Snowfake utilise des pages PAX hybrides [3] pour la persistance, tandis que le format de stockage de ClickHouse est strictement colonnaire. Snowfake met également l’accent sur le caching local et l’élagage des données à l’aide d’index légers créés automatiquement [31, 51] comme source de bonnes performances. À l’instar des clés primaires dans ClickHouse, les utilisateurs peuvent éventuellement créer des index clusterisés afin de colocaliser les données ayant les mêmes valeurs. Photon [5] et Velox [62] sont des moteurs d’exécution de requêtes conçus pour être utilisés comme composants dans des systèmes complexes de gestion des données. Les deux systèmes reçoivent des plans de requête en entrée, qui sont ensuite exécutés sur le nœud local sur des fichiers Parquet (Photon) ou Arrow (Velox) [46]. ClickHouse est capable de consommer et de générer des données dans ces formats génériques, mais préfère son format de fichier natif pour le stockage. Bien que Velox et Photon n’optimisent pas le plan de requête (Velox effectue des optimisations d’expression de base), ils utilisent des techniques d’adaptation à l’exécution, comme le basculement dynamique des noyaux de calcul selon les caractéristiques des données. De même, les opérateurs du plan dans ClickHouse peuvent créer d’autres opérateurs à l’exécution, principalement pour basculer vers une agrégation externe ou des opérateurs de jointure, en fonction de la consommation mémoire de la requête. L’article sur Photon note que les conceptions reposant sur la génération de code [38, 41, 53] sont plus difficiles à développer et à déboguer que les conceptions vectorisées interprétées [11]. La prise en charge (Experimental) de la génération de code dans Velox construit et lie une bibliothèque partagée produite à partir de code C++ généré à l’exécution, tandis que ClickHouse interagit directement avec l’API de compilation à la demande de LLVM. DuckDB [67] est également conçu pour être embarqué dans un processus hôte, mais fournit en plus l’optimisation des requêtes et les transactions. Il a été conçu pour des requêtes OLAP mêlées à des instructions OLTP occasionnelles. DuckDB a donc choisi le format de stockage DataBlocks [43], qui utilise des méthodes de compression légères telles que des dictionnaires préservant l’ordre ou le frame-of-reference [2] afin d’obtenir de bonnes performances dans des charges de travail hybrides. En revanche, ClickHouse est optimisé pour des cas d’usage en ajout uniquement, c.-à-d. sans mises à jour ni suppressions, ou avec de très rares mises à jour et suppressions. Les blocs sont compressés à l’aide de techniques lourdes comme LZ4, en partant du principe que les utilisateurs recourent largement à l’élagage des données pour accélérer les requêtes fréquentes et que les coûts d’E/S l’emportent sur les coûts de décompression pour les requêtes restantes. DuckDB fournit également des transactions sérialisables basées sur le schéma MVCC de Hyper [55], tandis que ClickHouse n’offre que l’isolation snapshot.

8 CONCLUSION ET PERSPECTIVES

Nous avons présenté l’architecture de ClickHouse, une base de données OLAP open-source à hautes performances. Reposant sur une couche de stockage optimisée pour l’écriture et un moteur de requête vectorisé de pointe, ClickHouse permet l’analytics en temps réel sur des jeux de données à l’échelle du pétaoctet avec des taux d’ingestion élevés. En fusionnant et en transformant les données de manière asynchrone en arrière-plan, ClickHouse dissocie efficacement la maintenance des données des insertions parallèles. Sa couche de stockage permet un élagage agressif des données à l’aide d’index primaires creux, d’index de saut et de tables de projection. Nous avons décrit l’implémentation, dans ClickHouse, des mises à jour et des suppressions, des insertions idempotentes et de la réplication des données entre nœuds pour assurer une haute disponibilité. La couche de traitement des requêtes optimise les requêtes grâce à un large éventail de techniques et parallélise leur exécution sur l’ensemble des ressources du serveur et du cluster. Les moteurs de table d’intégration et les fonctions offrent un moyen pratique d’interagir en toute transparence avec d’autres systèmes de gestion de données et d’autres formats de données. À travers des benchmarks, nous montrons que ClickHouse figure parmi les bases de données analytiques les plus rapides du marché, et nous avons mis en évidence des améliorations significatives des performances des requêtes typiques dans des déploiements réels de ClickHouse au fil des ans. Toutes les fonctionnalités et améliorations prévues pour 2024 figurent sur la feuille de route publique [18]. Les améliorations prévues comprennent la prise en charge des transactions utilisateur, de PromQL [69] comme query language alternatif, d’un nouveau type de données pour les données semi-structurées (par ex. JSON), de meilleures optimisations des jointures au niveau du plan, ainsi que l’implémentation de mises à jour légères en complément des suppressions légères.

REMERCIEMENTS

Depuis la version 24.6, SELECT * FROM system.contributors renvoie 1994 personnes ayant contribué à ClickHouse. Nous tenons à remercier toute l’équipe d’ingénierie de ClickHouse Inc. ainsi que l’incroyable communauté open-source de ClickHouse pour leur travail acharné et leur engagement à développer cette base de données ensemble.

RÉFÉRENCES

  • 1 Daniel Abadi, Peter Boncz, Stavros Harizopoulos, Stratos Idreaos, et Samuel Madden. 2013. The Design and Implementation of Modern Column-Oriented Database Systems. https://doi.org/10.1561/9781601987556
  • 2 Daniel Abadi, Samuel Madden et Miguel Ferreira. 2006. Integrating Compression and Execution in Column-Oriented Database Systems. Dans les actes de la conférence internationale ACM SIGMOD 2006 sur la gestion des données (SIGMOD ‘06). 671–682.https://doi.org/10.1145/1142473.1142548
  • 3 Anastassia Ailamaki, David J. DeWitt, Mark D. Hill, et Marios Skounakis. 2001. Weaving Relations for Cache Performance. Dans les actes de la 27th International Conference on Very Large Data Bases (VLDB ‘01). Morgan Kaufmann Publishers Inc., San Francisco, CA, USA, 169–180.
  • 4 Nikos Armenatzoglou, Sanuj Basu, Naga Bhanoori, Mengchu Cai, Naresh Chainani, Kiran Chinta, Venkatraman Govindaraju, Todd J. Green, Monish Gupta, Sebastian Hillig, Eric Hotinger, Yan Leshinksy, Jintian Liang, Michael McCreedy, Fabian Nagel, Ippokratis Pandis, Panos Parchas, Rahul Pathak, Orestis Polychroniou, Foyzur Rahman, Gaurav Saxena, Gokul Soundararajan, Sriram Subramanian et Doug Terry. 2022. Amazon Redshift Re-Invented. Dans les actes de l’International Conference on Management of Data 2022 (Philadelphie, Pennsylvanie, États-Unis) (SIGMOD ‘22). Association for Computing Machinery, New York, NY, USA, 2205–2217. https://doi.org/10.1145/3514221.3526045
  • 5 Alexander Behm, Shoumik Palkar, Utkarsh Agarwal, Timothy Armstrong, David Cashman, Ankur Dave, Todd Greenstein, Shant Hovsepian, Ryan Johnson, Arvind Sai Krishnan, Paul Leventis, Ala Luszczak, Prashanth Menon, Mostafa Mokhtar, Gene Pang, Sameer Paranjpye, Greg Rahn, Bart Samwel, Tom van Bussel, Herman van Hovell, Maryann Xue, Reynold Xin, et Matei Zaharia. 2022. Photon: A Fast Query Engine for Lakehouse Systems (SIGMOD ‘22). Association for Computing Machinery, New York, NY, USA, 2326–2339. https://doi.org/10.1145/3514221. 3526054
  • 6 Philip A. Bernstein and Nathan Goodman. 1981. Concurrency Control in Distributed Database Systems. ACM Computing Survey 13, 2 (1981), 185–221. https://doi.org/10.1145/356842.356846
  • 7 Spyros Blanas, Yinan Li, and Jignesh M. Patel. 2011. Design and evaluation of main memory hash join algorithms for multi-core CPUs. In Proceedings of the 2011 ACM SIGMOD International Conference on Management of Data (Athens, Greece) (SIGMOD ‘11). Association for Computing Machinery, New York, NY, USA, 37–48. https://doi.org/10.1145/1989323.1989328
  • 8 Daniel Gomez Blanco. 2023. Practical OpenTelemetry. Springer Nature.
  • 9 Burton H. Bloom. 1970. Space/Time Trade-Ofs in Hash Coding with Allowable Errors. Commun. ACM 13, 7 (1970), 422–426. https://doi.org/10.1145/362686. 362692
  • 10 Peter Boncz, Thomas Neumann et Orri Erling. 2014. TPC-H Analyzed: Hidden Messages and Lessons Learned from an Infuential Benchmark. In Performance Characterization and Benchmarking. 61–76. https://doi.org/10.1007/978-3-319- 04936-6_5
  • 11 Peter Boncz, Marcin Zukowski, and Niels Nes. 2005. MonetDB/X100: Hyper-Pipelining Query Execution. In CIDR.
  • 12 Martin Burtscher et Paruj Ratanaworabhan. 2007. High Throughput Compression of Double-Precision Floating-Point Data. In Data Compression Conference (DCC). 293–302. https://doi.org/10.1109/DCC.2007.44
  • 13 Jef Carpenter et Eben Hewitt. 2016. Cassandra: The Defnitive Guide (2nd ed.). O’Reilly Media, Inc.
  • 14 Bernadette Charron-Bost, Fernando Pedone et André Schiper (dir.). 2010. Replication: Theory and Practice. Springer-Verlag.
  • 15 chDB. 2024. chDB - un moteur SQL OLAP embarqué. Consulté le 2024-06-20 à l’adresse https://github.com/chdb-io/chdb
  • 16 ClickHouse. 2024. ClickBench: a Benchmark For Analytical Databases. Consulté le 20/06/2024 sur https://github.com/ClickHouse/ClickBench
  • 17 ClickHouse. 2024. ClickBench: Comparative Measurements. Consulté le 20/06/2024 sur https://benchmark.clickhouse.com
  • 18 ClickHouse. 2024. ClickHouse Roadmap 2024 (GitHub). Consulté le 2024-06-20 à l’adresse https://github.com/ClickHouse/ClickHouse/issues/58392
  • 19 ClickHouse. 2024. Benchmark des versions de ClickHouse. Consulté le 2024-06-20 à l’adresse https://github.com/ClickHouse/ClickBench/tree/main/versions
  • 20 ClickHouse. 2024. Résultats du benchmark des versions de ClickHouse. Consulté le 20/06/2024 sur https://benchmark.clickhouse.com/versions/
  • 21 Andrew Crotty. 2022. MgBench. Consulté le 2024-06-20 à l’adresse https://github.com/ andrewcrotty/mgbench
  • 22 Benoit Dageville, Thierry Cruanes, Marcin Zukowski, Vadim Antonov, Artin Avanes, Jon Bock, Jonathan Claybaugh, Daniel Engovatov, Martin Hentschel, Jiansheng Huang, Allison W. Lee, Ashish Motivala, Abdul Q. Munir, Steven Pelley, Peter Povinec, Greg Rahn, Spyridon Triantafyllis, et Philipp Unterbrunner. 2016. The Snowfake Elastic Data Warehouse. Dans les actes de la conférence internationale de 2016 sur la gestion des données (San Francisco, Californie, États-Unis) (SIGMOD ‘16). Association for Computing Machinery, New York, NY, USA, 215–226. https: //doi.org/10.1145/2882903.2903741
  • 23 Patrick Damme, Annett Ungethüm, Juliana Hildebrandt, Dirk Habich, et Wolfgang Lehner. 2019. From a Comprehensive Experimental Survey to a Cost-Based Selection Strategy for Lightweight Integer Compression Algorithms. ACM Trans. Database Syst. 44, 3, article 9 (2019), 46 pages. https://doi.org/10.1145/3323991
  • 24 Philippe Dobbelaere et Kyumars Sheykh Esmaili. 2017. Kafka versus RabbitMQ : étude comparative de deux implémentations industrielles de référence du modèle publication/abonnement : article industriel (DEBS ‘17). Association for Computing Machinery, New York, NY, USA, 227–238. https://doi.org/10.1145/3093742.3093908
  • 25 Documentation de LLVM. 2024. Auto-vectorisation dans LLVM. Consulté le 2024-06-20 à l’adresse https://llvm.org/docs/Vectorizers.html
  • 26 Siying Dong, Andrew Kryczka, Yanqin Jin et Michael Stumm. 2021. RocksDB: Evolution of Development Priorities in a Key-value Store Serving Large-scale Applications. ACM Transactions on Storage 17, 4, article 26 (2021), 32 pages. https://doi.org/10.1145/3483840
  • 27 Markus Dreseler, Martin Boissier, Tilmann Rabl, and Matthias Ufacker. 2020. Quantifying TPC-H choke points and their optimizations. Proc. VLDB Endow. 13, 8 (2020), 1206–1220. https://doi.org/10.14778/3389133.3389138
  • 28 Ted Dunning. 2021. The t-digest: efficient estimates of distributions. Software Impacts 7 (2021). https://doi.org/10.1016/j.simpa.2020.100049
  • 29 Martin Faust, Martin Boissier, Marvin Keller, David Schwalb, Holger Bischof, Katrin Eisenreich, Franz Färber, et Hasso Plattner. 2016. Footprint Reduction and Uniqueness Enforcement with Hash Indices in SAP HANA. In Database and Expert Systems Applications. 137–151. https://doi.org/10.1007/978-3-319-44406- 2_11
  • 30 Philippe Flajolet, Eric Fusy, Olivier Gandouet et Frederic Meunier. 2007. HyperLogLog: the analysis of a near-optimal cardinality estimation algorithm. In AofA: Analysis of Algorithms, vol. AH des actes DMTCS, 2007 Conference on Analysis of Algorithms (AofA 07). Discrete Mathematics and Theoretical Computer Science, 137–156. https://doi.org/10.46298/dmtcs.3545
  • 31 Hector Garcia-Molina, Jefrey D. Ullman, et Jennifer Widom. 2009. Database Systems - The Complete Book (2. Ed.).
  • 32 Pawan Goyal, Harrick M. Vin, and Haichen Chen. 1996. Ordonnancement équitable à temps de démarrage : un algorithme d’ordonnancement pour les réseaux à commutation de paquets à services intégrés. 26, 4 (1996), 157–168. https://doi.org/10.1145/248157.248171
  • 33 Goetz Graefe. 1993. Query Evaluation Techniques for Large Databases. ACM Comput. Surv. 25, 2 (1993), 73–169. https://doi.org/10.1145/152610.152611
  • 34 Jean-François Im, Kishore Gopalakrishna, Subbu Subramaniam, Mayank Shrivastava, Adwait Tumbde, Xiaotian Jiang, Jennifer Dai, Seunghyun Lee, Neha Pawar, Jialiang Li, et Ravi Aringunram. 2018. Pinot: Realtime OLAP for 530 Million Users. Dans les actes de l’International Conference on Management of Data 2018 (Houston, TX, USA) (SIGMOD ‘18). Association for Computing Machinery, New York, NY, USA, 583–594. https://doi.org/10.1145/3183713.3190661
  • 35 ISO/IEC 9075-9:2001 2001. Technologies de l’information — Langage de base de données — SQL — Partie 9 : gestion des données externes (SQL/MED). Norme. Organisation internationale de normalisation.
  • 36 Paras Jain, Peter Kraft, Conor Power, Tathagata Das, Ion Stoica et Matei Zaharia. 2023. Analyse et comparaison des systèmes de stockage lakehouse. CIDR.
  • 37 Project Jupyter. 2024. Jupyter Notebooks. Consulté le 20/06/2024 sur https: //jupyter.org/
  • 38 Timo Kersten, Viktor Leis, Alfons Kemper, Thomas Neumann, Andrew Pavlo et Peter Boncz. 2018. Everything You Always Wanted to Know about Compiled and Vectorized Queries but Were Afraid to Ask. Proc. VLDB Endow. 11, 13 (sept. 2018), 2209–2222. https://doi.org/10.14778/3275366.3284966
  • 39 Changkyu Kim, Jatin Chhugani, Nadathur Satish, Eric Sedlar, Anthony D. Nguyen, Tim Kaldewey, Victor W. Lee, Scott A. Brandt, et Pradeep Dubey. 2010. FAST: fast architecture sensitive tree search on modern CPUs and GPUs. In Proceedings of the 2010 ACM SIGMOD International Conference on Management of Data (Indianapolis, Indiana, USA) (SIGMOD ‘10). Association for Computing Machinery, New York, NY, USA, 339–350. https://doi.org/10.1145/1807167.1807206
  • 40 Donald E. Knuth. 1973. The Art of Computer Programming, Volume III: Sorting and Searching. Addison-Wesley.
  • 41 André Kohn, Viktor Leis et Thomas Neumann. 2018. Adaptive Execution of Compiled Queries. Dans les actes de la 34e conférence internationale IEEE sur l’ingénierie des données (ICDE), 2018. 197–208. https://doi.org/10.1109/ICDE.2018.00027
  • 42 Andrew Lamb, Matt Fuller, Ramakrishna Varadarajan, Nga Tran, Ben Vandiver, Lyric Doshi et Chuck Bear. 2012. La base de données analytique Vertica : C-Store, sept ans plus tard. Proc. VLDB Endow. 5, 12 (août 2012), 1790–1801. https://doi.org/10. 14778/2367502.2367518
  • 43 Harald Lang, Tobias Mühlbauer, Florian Funke, Peter A. Boncz, Thomas Neumann, et Alfons Kemper. 2016. Data Blocks: Hybrid OLTP and OLAP on Compressed Storage using both Vectorization and Compilation. Dans les actes de l’International Conference on Management of Data 2016 (San Francisco, Californie, États-Unis) (SIGMOD ‘16). Association for Computing Machinery, New York, NY, USA, 311–326. https://doi.org/10.1145/2882903.2882925
  • 44 Viktor Leis, Peter Boncz, Alfons Kemper, et Thomas Neumann. 2014. Morseldriven parallelism: a NUMA-aware query evaluation framework for the manycore age. In Proceedings of the 2014 ACM SIGMOD International Conference on Management of Data (Snowbird, Utah, USA) (SIGMOD ‘14). Association for Computing Machinery, New York, NY, USA, 743–754. https://doi.org/10.1145/2588555. 2610507
  • 45 Viktor Leis, Alfons Kemper et Thomas Neumann. 2013. The adaptive radix tree: ARTful indexing for main-memory databases. Dans 2013 IEEE 29th International Conference on Data Engineering (ICDE). 38–49. https://doi.org/10.1109/ICDE. 2013.6544812
  • 46 Chunwei Liu, Anna Pavlenko, Matteo Interlandi et Brandon Haynes. 2023. Une analyse approfondie des formats ouverts couramment utilisés pour les DBMS analytiques. 16, 11 (juil. 2023), 3044–3056. https://doi.org/10.14778/3611479.3611507
  • 47 Zhenghua Lyu, Huan Hubert Zhang, Gang Xiong, Gang Guo, Haozhou Wang, Jinbao Chen, Asim Praveen, Yu Yang, Xiaoming Gao, Alexandra Wang, Wen Lin, Ashwin Agrawal, Junfeng Yang, Hao Wu, Xiaoliang Li, Feng Guo, Jiang Wu, Jesse Zhang, et Venkatesh Raghavan. 2021. Greenplum : une base de données hybride pour des charges de travail transactionnelles et analytiques (SIGMOD ‘21). Association for Computing Machinery, New York, NY, USA, 2530–2542. https: //doi.org/10.1145/3448016.3457562
  • 48 Roger MacNicol and Blaine French. 2004. Sybase IQ Multiplex - Designed for Analytics. Dans les actes de la Thirtieth International Conference on Very Large Data Bases - Volume 30 (Toronto, Canada) (VLDB ‘04). VLDB Endowment, 1227–1230.
  • 49 Sergey Melnik, Andrey Gubarev, Jing Jing Long, Geofrey Romer, Shiva Shivakumar, Matt Tolton, Theo Vassilakis, Hossein Ahmadi, Dan Delorey, Slava Min, Mosha Pasumansky, et Jef Shute. 2020. Dremel: A Decade of Interactive SQL Analysis at Web Scale. Proc. VLDB Endow. 13, 12 (août 2020), 3461–3472. https://doi.org/10.14778/3415478.3415568
  • 50 Microsoft. 2024. Kusto Query Language. Consulté le 2024-06-20 à l’adresse https: //github.com/microsoft/Kusto-Query-Language
  • 51 Guido Moerkotte. 1998. Small Materialized Aggregates: A Light Weight Index Structure for Data Warehousing. In Proceedings of the 24rd International Conference on Very Large Data Bases (VLDB ‘98). 476–487.
  • 52 Jalal Mostafa, Sara Wehbi, Suren Chilingaryan, et Andreas Kopmann. 2022. SciTS: A Benchmark for Time-Series Databases in Scientifc Experiments and Industrial Internet of Things. In Proceedings of the 34th International Conference on Scientifc and Statistical Database Management (SSDBM ‘22). Article 12. https: //doi.org/10.1145/3538712.3538723
  • 53 Thomas Neumann. 2011. efficiently Compiling efficient Query Plans for Modern Hardware. Proc. VLDB Endow. 4, 9 (jun 2011), 539–550. https://doi.org/10.14778/ 2002938.2002940
  • 54 Thomas Neumann et Michael J. Freitag. 2020. Umbra : un système sur disque offrant des performances en mémoire. Dans les actes en ligne de la 10e Conference on Innovative Data Systems Research, CIDR 2020, Amsterdam, Pays-Bas, 12-15 janvier 2020. www.cidrdb.org. http://cidrdb.org/cidr2020/papers/p29-neumanncidr20.pdf
  • 55 Thomas Neumann, Tobias Mühlbauer et Alfons Kemper. 2015. Fast Serializable Multi-Version Concurrency Control for Main-Memory Database Systems. Dans les actes de la 2015 ACM SIGMOD International Conference on Management of Data (Melbourne, Victoria, Australie) (SIGMOD ‘15). Association for Computing Machinery, New York, NY, États-Unis, 677–689. https://doi.org/10.1145/2723372. 2749436
  • 56 LevelDB sur GitHub. 2024. LevelDB. Consulté le 2024-06-20 sur https://github. com/google/leveldb
  • 57 Patrick O’Neil, Elizabeth O’Neil, Xuedong Chen, and Stephen Revilak. 2009. The Star Schema Benchmark and Augmented Fact Table Indexing. In Performance Evaluation and Benchmarking. Springer Berlin Heidelberg, 237–252. https: //doi.org/10.1007/978-3-642-10424-4_17
  • 58 Patrick E. O’Neil, Edward Y. C. Cheng, Dieter Gawlick, et Elizabeth J. O’Neil. 1996. The log-structured Merge-Tree (LSM-tree). Acta Informatica 33 (1996), 351–385. https://doi.org/10.1007/s002360050048
  • 59 Diego Ongaro et John Ousterhout. 2014. In Search of an Understandable Consensus Algorithm. In Proceedings of the 2014 USENIX Conference on USENIX Annual Technical Conference (USENIX ATC’14). 305–320. https://doi.org/doi/10. 5555/2643634.2643666
  • 60 Patrick O’Neil, Edward Cheng, Dieter Gawlick, et Elizabeth O’Neil. 1996. The Log-Structured Merge-Tree (LSM-Tree). Acta Inf. 33, 4 (1996), 351–385. https: //doi.org/10.1007/s002360050048
  • 61 Pandas. 2024. Pandas DataFrames. Consulté le 2024-06-20 à l’adresse https://pandas. pydata.org/
  • 62 Pedro Pedreira, Orri Erling, Masha Basmanova, Kevin Wilfong, Laith Sakka, Krishna Pai, Wei He, et Biswapesh Chattopadhyay. 2022. Velox: Meta’s Unified Execution Engine. Proc. VLDB Endow. 15, 12 (aug 2022), 3372–3384. https: //doi.org/10.14778/3554821.3554829
  • 63 Tuomas Pelkonen, Scott Franklin, Justin Teller, Paul Cavallaro, Qi Huang, Justin Meza, et Kaushik Veeraraghavan. 2015. Gorilla: A Fast, Scalable, in-Memory Time Series Database. Proceedings of the VLDB Endowment 8, 12 (2015), 1816–1827. https://doi.org/10.14778/2824032.2824078
  • 64 Orestis Polychroniou, Arun Raghavan et Kenneth A. Ross. 2015. Repenser la vectorisation SIMD pour les bases de données en mémoire. Dans les actes de la conférence internationale ACM SIGMOD 2015 sur la gestion des données (SIGMOD ‘15). 1493–1508. https://doi.org/10.1145/2723372.2747645
  • 65 PostgreSQL. 2024. PostgreSQL - Foreign Data Wrappers. Consulté le 2024-06-20 sur https://wiki.postgresql.org/wiki/Foreign&#95;data&#95;wrappers
  • 66 Mark Raasveldt, Pedro Holanda, Tim Gubner, et Hannes Mühleisen. 2018. Fair Benchmarking Considered difficult: Common Pitfalls In Database Performance Testing. Dans les actes du Workshop on Testing Database Systems (Houston, TX, USA) (DBTest’18). Article 2, 6 pages. https://doi.org/10.1145/3209950.3209955
  • 67 Mark Raasveldt and Hannes Mühleisen. 2019. DuckDB: An Embeddable Analytical Database (SIGMOD ‘19). Association for Computing Machinery, New York, NY, USA, 1981–1984. https://doi.org/10.1145/3299869.3320212
  • 68 Jun Rao et Kenneth A. Ross. 1999. Cache Conscious Indexing for Decision-Support in Main Memory. Dans les actes de la 25e conférence internationale sur les très grandes bases de données (VLDB ‘99). San Francisco, CA, USA, 78–89.
  • 69 Navin C. Sabharwal et Piyush Kant Pandey. 2020. Working with Prometheus Query Language (PromQL). Dans Monitoring Microservices and Containerized Applications. https://doi.org/10.1007/978-1-4842-6216-0&#95;5
  • 70 Todd W. Schneider. 2022. New York City Taxi and For-Hire Vehicle Data. Consulté le 20/06/2024 sur https://github.com/toddwschneider/nyc-taxi-data
  • 71 Mike Stonebraker, Daniel J. Abadi, Adam Batkin, Xuedong Chen, Mitch Cherniack, Miguel Ferreira, Edmond Lau, Amerson Lin, Sam Madden, Elizabeth O’Neil, Pat O’Neil, Alex Rasin, Nga Tran, and Stan Zdonik. 2005. C-Store : un SGBD orienté colonnes. Dans les actes de la 31e conférence internationale sur les très grandes bases de données (VLDB ‘05). 553–564.
  • 72 Teradata. 2024. Teradata Database. Consulté le 2024-06-20 à l’adresse https://www. teradata.com/resources/datasheets/teradata-database
  • 73 Frederik Transier. 2010. Algorithms and Data Structures for In-Memory Text Search Engines. Thèse de doctorat. https://doi.org/10.5445/IR/1000015824
  • 74 Adrian Vogelsgesang, Michael Haubenschild, Jan Finis, Alfons Kemper, Viktor Leis, Tobias Muehlbauer, Thomas Neumann, et Manuel Then. 2018. Get Real: How Benchmarks Fail to Represent the Real World. Dans les actes du Workshop on Testing Database Systems (Houston, TX, USA) (DBTest’18). Article 1, 6 pages. https://doi.org/10.1145/3209950.3209952
  • 75 Site web de LZ4. 2024. LZ4. Consulté le 2024-06-20 sur https://lz4.org/
  • 76 Site web de PRQL. 2024. PRQL. Consulté le 20/06/2024 sur https://prql-lang.org 77 Till Westmann, Donald Kossmann, Sven Helmer et Guido Moerkotte. 2000. The Implementation and Performance of Compressed Databases. SIGMOD Rec.
  • 29, 3 (sept. 2000), 55–67. https://doi.org/10.1145/362084.362137 78 Fangjin Yang, Eric Tschetter, Xavier Léauté, Nelson Ray, Gian Merlino et Deep Ganguli. 2014. Druid: A Real-Time Analytical Data Store. In Proceedings of the 2014 ACM SIGMOD International Conference on Management of Data (Snowbird, Utah, USA) (SIGMOD ‘14). Association for Computing Machinery, New York, NY, USA, 157–168. https://doi.org/10.1145/2588555.2595631
  • 79 Tianqi Zheng, Zhibin Zhang, et Xueqi Cheng. 2020. SAHA: A String Adaptive Hash Table for Analytical Databases. Applied Sciences 10, 6 (2020). https: //doi.org/10.3390/app10061915
  • 80 Jingren Zhou and Kenneth A. Ross. 2002. Implémentation d’opérations de base de données à l’aide d’instructions SIMD. Dans les actes de la conférence internationale ACM SIGMOD 2002 sur la gestion des données (SIGMOD ‘02). 145–156. https://doi.org/10. 1145/564691.564709
  • 81 Marcin Zukowski, Sandor Heman, Niels Nes et Peter Boncz. 2006. Super-Scalar RAM-CPU Cache Compression. In Proceedings of the 22nd International Conference on Data Engineering (ICDE ‘06). 59. https://doi.org/10.1109/ICDE. 2006.150
Dernière modification le 29 juin 2026