L’ingestion efficace des données est à la base des déploiements ClickHouse à hautes performances. Choisir la bonne stratégie d’insertion peut avoir un impact considérable sur le débit, le coût et la fiabilité. Cette section présente les bonnes pratiques, les compromis et les options de configuration afin de vous aider à prendre la bonne décision pour votre charge de travail.
Insertions synchrones par défaut
Par défaut, les insertions dans ClickHouse sont synchrones. Chaque requête d’insertion crée immédiatement une part de stockage sur disque, y compris les métadonnées et les index.
Utilisez les insertions synchrones si vous pouvez regrouper les données côté clientSinon, consultez la section Insertions asynchrones ci-dessous.
Nous passons brièvement en revue ci-dessous le fonctionnement des insertions MergeTree dans ClickHouse :
Pour des performances optimales, les données doivent être ① regroupées en lots, ce qui fait de la taille des lots le premier choix à faire.
ClickHouse stocke les données insérées sur le disque, ordonnées selon les colonnes de clé primaire de la table. Le deuxième choix consiste à déterminer s’il faut ② prétrier les données avant de les transmettre au serveur. Si un lot arrive déjà trié selon les colonnes de clé primaire, ClickHouse peut ignorer l’étape ⑩ de tri, ce qui accélère l’ingestion.
Si les données à ingérer n’ont pas de format prédéfini, la décision essentielle consiste à choisir un format. ClickHouse prend en charge l’insertion de données dans plus de 70 formats. Toutefois, lors de l’utilisation du client en ligne de commande ClickHouse ou de bibliothèques clientes pour différents langages, ce choix est souvent géré automatiquement. Si nécessaire, cette sélection automatique peut aussi être remplacée explicitement.
Le choix majeur suivant est ④ de savoir s’il faut compresser les données avant leur transmission au serveur ClickHouse. La compression réduit le volume des transferts et améliore l’efficacité du réseau, ce qui accélère les transferts de données et diminue l’utilisation de la bande passante, en particulier pour les grands jeux de données.
Les données sont ⑤ transmises via une interface réseau ClickHouse — soit l’interface native, soit l’interface HTTP (que nous comparons plus loin dans cet article).
Après ⑥ avoir reçu les données, ClickHouse les ⑦ décompresse si une compression a été utilisée, puis les ⑧ interprète à partir du format d’origine dans lequel elles ont été envoyées.
À partir des valeurs de ces données formatées et de l’instruction DDL de la table cible, ClickHouse ⑨ construit en mémoire un bloc au format MergeTree, ⑩ trie les lignes selon les colonnes de la clé primaire si elles ne sont pas déjà prétriées, ⑪ crée un index primaire épars, ⑫ applique une compression par colonne et ⑬ écrit les données sur disque sous la forme d’une nouvelle ⑭ partie de données.
Insertions par lot en mode synchrone
Les mécanismes décrits ci-dessus montrent qu’il existe un surcoût constant, quelle que soit la taille de l’insertion, ce qui fait de la taille des lots le facteur d’optimisation le plus important pour le débit d’ingestion. Le regroupement des insertions par lots réduit ce surcoût par rapport au temps total d’insertion et améliore l’efficacité du traitement.
Nous recommandons d’insérer les données par lots d’au moins 1 000 lignes, et idéalement entre 10 000 et 100 000 lignes. Des insertions moins fréquentes et plus volumineuses réduisent le nombre de parts écrites, minimisent la charge de fusion et diminuent l’utilisation globale des ressources système.
Pour qu’une stratégie d’insertion synchrone soit efficace, ce regroupement côté client est indispensable.
Si vous ne pouvez pas regrouper les données par lots côté client, ClickHouse prend en charge les insertions asynchrones, qui déplacent ce regroupement vers le serveur (voir Insertions asynchrones).
Quelle que soit la taille de vos insertions, nous vous recommandons de maintenir le nombre de requêtes d’insertion autour d’une par seconde. Cette recommandation s’explique par le fait que les parts créées sont fusionnées en arrière-plan en parts plus volumineuses (afin d’optimiser vos données pour les requêtes de lecture), et qu’un trop grand nombre de requêtes d’insertion par seconde peut conduire à des situations où les fusions en arrière-plan ne parviennent plus à suivre le rythme de création des nouvelles parts. Toutefois, vous pouvez utiliser un taux plus élevé de requêtes d’insertion par seconde avec les insertions asynchrones (voir Insertions asynchrones).
Assurer des retries idempotents
Les insertions synchrones sont également idempotentes. Lors de l’utilisation des moteurs MergeTree, ClickHouse déduplique les insertions par défaut. Cela protège contre des cas d’échec ambigus, tels que :
- L’insertion a réussi, mais le client n’a jamais reçu d’accusé de réception en raison d’une interruption réseau.
- L’insertion a échoué côté serveur et a expiré.
Dans les deux cas, il est sans risque de réessayer l’insertion — tant que le contenu du lot et son ordre restent identiques. C’est pourquoi il est essentiel que les clients réessaient de manière cohérente, sans modifier ni réordonner les données.
Choisissez la bonne cible d’insertion
Pour les clusters avec sharding, vous avez deux possibilités :
- Insérez directement dans une table MergeTree ou ReplicatedMergeTree. C’est l’option la plus efficace lorsque le client peut répartir la charge entre les shards. Avec
internal_replication = true, ClickHouse gère la réplication de manière transparente.
- Insérez dans une table Distributed. Les clients peuvent ainsi envoyer des données à n’importe quel nœud, puis laisser ClickHouse les acheminer vers le shard approprié. C’est plus simple, mais légèrement moins performant en raison de cette étape de transfert supplémentaire.
internal_replication = true reste recommandé.
Dans ClickHouse Cloud, tous les nœuds lisent et écrivent sur un seul et même shard. Les insertions sont automatiquement réparties entre les nœuds. Vous pouvez simplement envoyer vos insertions vers l’endpoint exposé.
Choisir le bon format d’entrée est essentiel pour assurer une ingestion de données efficace dans ClickHouse. Avec plus de 70 formats pris en charge, le choix de l’option la plus performante peut avoir un impact significatif sur la vitesse d’insertion, l’utilisation du CPU et de la mémoire, ainsi que sur l’efficacité globale du système.
Si la flexibilité est utile pour l’ingénierie des données et les imports à partir de fichiers, les applications doivent privilégier les formats optimisés pour les performances :
- Native format (recommandé) : le plus efficace. Format colonnaire, nécessitant un minimum de parsing côté serveur. Utilisé par défaut par les clients Go et Python.
- RowBinary : format en lignes efficace, idéal si la transformation en format colonnaire est difficile côté client. Utilisé par le client Java.
- JSONEachRow : simple à utiliser, mais coûteux à parser. Convient aux cas d’usage à faible volume ou aux intégrations rapides.
La compression joue un rôle essentiel pour réduire la surcharge réseau, accélérer les insertions et diminuer les coûts de stockage dans ClickHouse. Lorsqu’elle est utilisée efficacement, elle améliore les performances d’ingestion sans nécessiter de modifier le format des données ni le schéma.
La compression des données insérées réduit la taille des données envoyées sur le réseau, ce qui limite l’utilisation de la bande passante et accélère la transmission.
Pour les insertions, la compression est particulièrement efficace lorsqu’elle est utilisée avec le format Native, qui correspond déjà au modèle de stockage interne en colonnes de ClickHouse. Dans cette configuration, le serveur peut décompresser efficacement les données et les stocker directement avec un minimum de transformations.
Utilisez LZ4 pour la rapidité, ZSTD pour le taux de compression
ClickHouse prend en charge plusieurs codecs de compression lors de la transmission des données. Deux options courantes sont :
- LZ4 : rapide et léger. Il réduit considérablement la taille des données avec une surcharge CPU minimale, ce qui en fait un choix idéal pour les insertions à haut débit, et c’est l’option par défaut dans la plupart des clients ClickHouse.
- ZSTD : taux de compression plus élevé, mais plus gourmand en CPU. Il est utile lorsque les coûts de transfert réseau sont élevés — par exemple dans des scénarios inter-région ou chez certains fournisseurs cloud — même s’il augmente légèrement les ressources de calcul côté client et le temps de décompression côté serveur.
Bonne pratique : utilisez LZ4, sauf si votre bande passante est limitée ou si vous supportez des coûts de sortie des données — dans ce cas, envisagez ZSTD.
Selon des tests du benchmark FastFormats, des insertions Native compressées avec LZ4 ont réduit la taille des données de plus de 50 %, faisant passer le temps d’ingestion de 150 s à 131 s pour un jeu de données de 5,6 GiB. En passant à ZSTD, ce même jeu de données a été compressé à 1,69 GiB, mais le temps de traitement côté serveur a légèrement augmenté.
La compression réduit l’utilisation des ressources
La compression réduit non seulement le trafic réseau, mais améliore aussi l’efficacité du CPU et de la mémoire sur le serveur. Avec des données compressées, ClickHouse reçoit moins d’octets et passe moins de temps à analyser de gros volumes de données en entrée. Cet avantage est particulièrement important lors de l’ingestion depuis plusieurs clients concurrents, comme dans des scénarios d’observability.
L’impact de la compression sur le CPU et la mémoire est modeste avec LZ4, et modéré avec ZSTD. Même sous charge, l’efficacité côté serveur s’améliore grâce à la réduction du volume de données.
La combinaison de la compression, du traitement par lots et d’un format d’entrée efficace (comme Native) offre les meilleures performances d’ingestion.
Lors de l’utilisation de l’interface native (par ex. clickhouse-client), la compression LZ4 est activée par défaut. Vous pouvez également passer à ZSTD via les paramètres.
Avec l’interface HTTP, utilisez l’en-tête Content-Encoding pour appliquer la compression (par ex. Content-Encoding : lz4). L’intégralité de la charge utile doit être compressée avant l’envoi.
Pré-trier si le coût est faible
Le pré-tri des données selon la clé primaire avant l’insertion peut améliorer l’efficacité de l’ingestion dans ClickHouse, en particulier pour les lots de grande taille.
Lorsque les données arrivent déjà pré-triées, ClickHouse peut éviter ou simplifier l’étape de tri interne lors de la création des parts, ce qui réduit l’utilisation du CPU et accélère le processus d’insertion. Le pré-tri améliore aussi l’efficacité de la compression, car les valeurs similaires se retrouvent regroupées, ce qui permet à des codecs comme LZ4 ou ZSTD d’obtenir un meilleur taux de compression. C’est particulièrement avantageux lorsqu’il est combiné à de grandes insertions par lot et à la compression, car cela réduit à la fois le surcoût de traitement et la quantité de données transférées.
Cela dit, le pré-tri est une optimisation facultative, pas une obligation. ClickHouse trie les données de manière très efficace grâce au traitement parallèle et, dans bien des cas, le tri côté serveur est plus rapide ou plus pratique qu’un pré-tri côté client.
Nous recommandons le pré-tri uniquement si les données sont déjà presque ordonnées, ou si les ressources côté client (CPU, mémoire) sont suffisantes et peu sollicitées. Dans les cas d’usage sensibles à la latence ou à haut débit, comme l’observabilité, où les données arrivent dans le désordre ou proviennent de nombreux agents, il est souvent préférable de ne pas pré-trier et de s’appuyer sur les performances natives de ClickHouse.
Les insertions asynchrones dans ClickHouse offrent une alternative puissante lorsque le batching côté client n’est pas envisageable. Cela est particulièrement utile pour les charges de travail d’observabilité, où des centaines, voire des milliers d’agents envoient des données en continu — logs, métriques, traces — souvent sous la forme de payloads de petite taille en temps réel. Dans ces environnements, le buffering côté client accroît la complexité, car il nécessite une queue centralisée pour garantir l’envoi de batches suffisamment volumineux.
Il n’est pas recommandé d’envoyer de nombreux petits batches en mode synchrone, car cela entraîne la création d’un grand nombre de parts. Cela dégrade les performances des queries et peut provoquer des erreurs “too many part”.
Les insertions asynchrones transfèrent la responsabilité du batching du client vers le server en écrivant les données entrantes dans un tampon en mémoire, puis en les vidant vers le stockage en fonction de thresholds configurables. Cette approche réduit considérablement la surcharge liée à la création de parts, diminue l’utilisation du CPU et garantit une ingestion efficace, même en cas de forte concurrence.
Le comportement principal est contrôlé via le paramètre async_insert.
Les insertions asynchrones sont prises en charge à la fois par les interfaces HTTP et native TCP.
Lorsqu’elles sont activées (async_insert = 1), les insertions sont mises en tampon et écrites sur disk uniquement lorsqu’une des conditions de flush suivantes est remplie :
Le premier seuil atteint déclenche le flush.
Ce processus de batching est invisible pour les clients et aide ClickHouse à fusionner efficacement le trafic d’insertions provenant de plusieurs sources. Cependant, tant qu’un flush n’a pas eu lieu, les données ne peuvent pas être interrogées. Il est important de noter qu’il existe plusieurs tampons par combinaison de forme d’insertion et de paramètres et que, dans les clusters, les tampons sont maintenus par nœud, ce qui permet un contrôle granulaire dans les environnements multi-tenant. Les mécanismes d’insertion sont par ailleurs identiques à ceux décrits pour les insertions synchrones.
Choisir un mode de retour
Le comportement des insertions asynchrones est affiné davantage à l’aide du paramètre wait_for_async_insert.
Lorsqu’il est défini sur 1 (valeur par défaut), ClickHouse n’accuse réception de l’insert qu’une fois les données correctement écrites sur le disque. Cela garantit une forte durabilité et simplifie le traitement des erreurs : si un problème survient pendant le flush, l’erreur est renvoyée au client. Ce mode est recommandé dans la plupart des scénarios de production, en particulier lorsque les échecs d’insert doivent être suivis de manière fiable.
Les benchmarks montrent qu’il passe bien à l’échelle avec une forte concurrency — que vous exécutiez 200 ou 500 clients — grâce aux inserts adaptatifs et à un comportement stable lors de la création des parts.
Définir wait_for_async_insert = 0 active le mode « fire-and-forget ». Dans ce cas, le serveur accuse réception de l’insert dès que les données sont placées dans le tampon, sans attendre qu’elles soient écrites dans le stockage.
Cela permet des inserts à très faible latence et un throughput maximal, idéal pour des données à haut débit et à faible criticité. Cependant, cela implique des compromis : rien ne garantit que les données seront persistées, les erreurs n’apparaissent qu’au moment du flush, et il n’existe pas de dead-letter queue pour les inserts en échec — pour analyser les échecs, il faut inspecter les server logs et les system tables a posteriori. Utilisez ce mode uniquement si votre workload peut tolérer une perte de données.
Les benchmarks montrent également une réduction substantielle du nombre de parts et une baisse de l’utilisation CPU lorsque les flushes du tampon sont peu fréquents (par exemple, toutes les 30 secondes), mais le risque d’échec silencieux demeure.
Nous recommandons vivement d’utiliser async_insert=1,wait_for_async_insert=1 si vous utilisez des insertions asynchrones. Utiliser wait_for_async_insert=0 est très risqué, car votre client INSERT peut ne pas être informé en cas d’erreur. Cela peut également entraîner une surcharge si votre client continue à écrire rapidement alors que le ClickHouse server doit ralentir les écritures et appliquer une certaine contre-pression afin de garantir la fiabilité du service.
Insertions asynchrones adaptatives
Depuis la version 24.2, ClickHouse utilise par défaut des délais de flush adaptatifs (async_insert_use_adaptive_busy_timeout). Au lieu d’un intervalle de flush fixe, le timeout s’ajuste dynamiquement entre un minimum (async_insert_busy_timeout_min_ms, 50 ms par défaut) et un maximum (async_insert_busy_timeout_max_ms, 200 ms par défaut ou 1000 ms sur Cloud) en fonction du débit des données entrantes.
Lorsque les données arrivent fréquemment, le timeout reste proche du minimum afin de déclencher le flush plus rapidement et de réduire la latence de bout en bout. Lorsque les données sont clairsemées, il se rapproche du maximum pour accumuler des lots plus volumineux. Cela est particulièrement utile dans le mode par défaut (wait_for_async_insert=1), où un timeout fixe élevé obligerait les clients à attendre pendant tout l’intervalle, même lorsque les données sont déjà prêtes à être écrites.
La validation du schéma et l’analyse des données ont lieu lors du flush du tampon, et non à la réception de l’insert. Si une ligne d’une requête d’insertion contient une erreur d’analyse ou de type, aucune des données de cette requête n’est écrite — l’intégralité de la charge utile de la requête est rejetée. En mode par défaut (wait_for_async_insert=1), l’erreur est renvoyée au client. En mode fire-and-forget, les erreurs sont consignées dans les journaux du serveur et dans la table system.asynchronous_inserts.
Chaque flush crée au moins une part par valeur distincte de clé de partitionnement dans le tampon. Même pour les tables sans clé de partitionnement, un seul flush peut produire plusieurs parts si les données mises en tampon dépassent max_insert_block_size (environ 1 million de lignes par défaut).
Même avec les insertions asynchrones, vous pouvez toujours rencontrer des erreurs “too many parts” si la clé de partitionnement présente une cardinalité élevée.
Déduplication et fiabilité
Par défaut, ClickHouse effectue une déduplication automatique pour les insertions synchrones, ce qui sécurise les nouvelles tentatives en cas d’échec. Cependant, cette fonctionnalité est désactivée pour les insertions asynchrones, sauf si elle est explicitement activée (elle ne doit pas l’être si vous avez des vues matérialisées dépendantes — voir l’issue).
En pratique, si la déduplication est activée et que la même insertion est relancée — en raison, par exemple, d’un timeout ou d’une interruption du réseau — ClickHouse peut ignorer le doublon en toute sécurité. Cela permet de préserver l’idempotence et d’éviter les écritures de données en double.
Activation des insertions asynchrones
Les insertions asynchrones peuvent être activées pour un utilisateur donné ou pour une requête spécifique :
-
Activation des insertions asynchrones au niveau de l’utilisateur. Cet exemple utilise l’utilisateur
default ; si vous créez un autre utilisateur, remplacez-le par ce nom d’utilisateur :
-
Vous pouvez spécifier les paramètres d’insertion asynchrone à l’aide de la clause SETTINGS dans les requêtes d’insertion :
-
Vous pouvez également définir les paramètres d’insertion asynchrone comme paramètres de connexion lorsque vous utilisez un client ClickHouse pour un langage de programmation.
Par exemple, voici comment procéder dans une chaîne de connexion JDBC lorsque vous utilisez le pilote JDBC Java de ClickHouse pour vous connecter à ClickHouse Cloud :
Les insertions asynchrones ne s’appliquent pas aux requêtes INSERT INTO ... SELECT. Lorsqu’une insertion contient une clause SELECT, la requête est toujours exécutée de manière synchrone, quel que soit le paramètre async_insert.
Flush des tampons à l’arrêt
Pour flush tous les tampons d’async insert en attente — par exemple, lors d’un arrêt propre ou avant une opération de maintenance — exécutez :
Cela garantit que toutes les données en mémoire tampon sont écrites dans le stockage avant que le serveur ne s’arrête.
Comparaison avec les tables Buffer
Les insertions asynchrones constituent le remplaçant moderne des tables Buffer. Principales différences :
- Aucune modification DDL n’est requise. Les insertions asynchrones sont transparentes : vous activez un paramètre, sans créer de tables supplémentaires.
- Mise en tampon par forme de requête. Les insertions asynchrones conservent des tampons distincts pour chaque combinaison unique de forme de requête et de paramètres, ce qui permet des politiques de flush granulaires. Les tables Buffer utilisent un seul tampon par table cible.
- Durabilité. Dans le mode par défaut (
wait_for_async_insert=1), les données sont confirmées sur disque avant que le client ne reçoive la confirmation. Les tables Buffer fonctionnent en mode fire-and-forget : les données mises en tampon sont perdues en cas de crash.
- Comportement du cluster. Dans les clusters, les tampons d’insertion asynchrone sont maintenus par nœud. Les tables Buffer nécessitent une création explicite sur chaque nœud.
Choisissez une interface — HTTP ou native
ClickHouse propose deux interfaces principales pour l’ingestion de données : l’interface native et l’interface HTTP — chacune offrant un compromis entre performances et flexibilité. L’interface native, utilisée par clickhouse-client et certains clients pour différents langages comme Go et C++, est conçue avant tout pour les performances. Elle transmet toujours les données dans le format Native très efficace de ClickHouse, prend en charge la compression par blocs avec LZ4 ou ZSTD, et réduit au minimum le traitement côté serveur en déléguant au client des tâches comme l’analyse syntaxique et la conversion de format.
Elle permet même de calculer côté client les valeurs de colonne MATERIALIZED et DEFAULT, ce qui permet au serveur d’ignorer entièrement ces étapes. L’interface native est donc idéale pour les scénarios d’ingestion à haut débit où l’efficacité est essentielle.
Contrairement à de nombreuses bases de données traditionnelles, ClickHouse prend également en charge une interface HTTP. À l’inverse, celle-ci privilégie la compatibilité et la flexibilité. Elle permet d’envoyer des données dans n’importe quel format pris en charge, notamment JSON, CSV, Parquet, entre autres, et elle est largement prise en charge par la plupart des clients ClickHouse, notamment en Python, Java, JavaScript et Rust.
Cette option est souvent préférable au protocole natif de ClickHouse, car elle permet de rediriger facilement le trafic à l’aide d’équilibreurs de charge. Nous nous attendons à de faibles écarts de performances d’insertion avec le protocole natif, qui implique un peu moins de surcoût.
Cependant, elle ne bénéficie pas de l’intégration plus poussée du protocole natif et ne peut pas effectuer d’optimisations côté client, comme le calcul de valeurs matérialisées ou la conversion automatique au format Native. Bien que les insertions HTTP puissent toujours être compressées à l’aide d’en-têtes HTTP standard (par ex. Content-Encoding: lz4), la compression s’applique à l’ensemble de la charge utile plutôt qu’à des blocs de données individuels. Cette interface est souvent privilégiée dans les environnements où la simplicité du protocole, la répartition de charge ou la large compatibilité des formats importent davantage que les performances brutes.
Pour une description plus détaillée de ces interfaces, voir ici.