Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Réduire les coûts BigQuery et Looker grâce à ClickHouse — Partie 2

By Sayle MatthewsJun 30, 202416 min read

Cette page est également disponible en English, Deutsch, Español, Italiano, 日本語 et Português.

La suite

Dans la partie précédente, nous avons présenté ce que ce plan va concrètement accomplir et comment créer un service ClickHouse de base en s'appuyant sur l'offre DBaaS d'Aiven ou de ClickHouse. Dans cette partie, nous allons amorcer l'ingestion des données dans ClickHouse et mettre en place la réplication entre celui-ci et BigQuery.

Dépôt GitHub

Tout au long de cet article, je ferai référence à une Cloud Function et à un job BigQuery. Le code source de ces deux artefacts se trouve ici, dans ce dépôt GitHub.

Ce code est volontairement très simple et conçu à des fins pédagogiques. Il y a donc de fortes chances qu'il doive être adapté pour répondre à un usage réel correspondant à votre scénario.

Extraire les données de BigQuery vers ClickHouse

Il n'existe à ce jour aucune méthode automatisée pour faire du CDC ou du streaming depuis BigQuery vers une destination arbitraire. Les données doivent donc être soit captées avant leur insertion dans BigQuery, soit extraites après coup. L'absence de méthode officielle fait du sujet un domaine particulièrement vaste. Pour cette raison, je ne couvrirai dans cet article que les chargements en batch.

Nous y reviendrons dans la section suivante, mais il faut d'abord extraire le jeu de données initial de BigQuery vers ClickHouse afin d'établir notre référence.

BigQuery dispose d'une fonction d'export, qui constitue le moyen le plus simple d'en extraire les données vers une autre destination. Elle présente toutefois deux inconvénients majeurs : elle ne peut exporter qu'une seule table à la fois, et elle ne peut cibler que GCS pour le stockage.

Compte tenu de cette limitation à une table à la fois, c'est le bon moment pour déterminer si toutes les tables doivent être répliquées dans ClickHouse, ou quel sous-ensemble de tables se prête le mieux à une réplication en vue d'une utilisation par Looker.

Un moyen rapide de lister toutes les tables est d'exécuter la requête suivante sur votre dataset, qui vous renverra la liste complète. De plus, si vous ne savez pas quelles tables sont les plus sollicitées, cette requête listera le nombre de requêtes ayant ciblé chaque table du dataset. Notez que cette requête peut coûter cher : vérifiez d'abord l'estimation de coût dans l'UI puis ajustez le nombre de jours analysés avant de la lancer.

Pour réaliser l'export proprement dit, le mieux est d'utiliser la commande CLI bq pour récupérer une table entière. Je le recommande car la commande SQL EXPORT DATA… entraîne des frais de traitement/scan sur le volume de données exporté, ou des frais de slots si vous utilisez Editions, alors que la commande CLI ou l'appel API se contente de déverser la table entière sans frais supplémentaires.

Si vous ne voulez qu'une partie d'une table, ce qui correspond généralement à un ensemble de partitions, il existe une astuce avec la commande bq cp pour copier une partition vers une nouvelle table qui pourra ensuite être chargée directement dans ClickHouse. Malheureusement, cette commande ne fonctionne ni avec des wildcards ni avec plusieurs partitions à la fois ; il faut donc l'exécuter pour chaque partition. Cela peut être scripté assez facilement, mais voici la commande :

bq cp –append_table=true `<source_project_id>:<source_dataset>.<source_table>$<source_partition_name>` `<target_project_id>.<target_dataset>.<target_table>`

Si la table n'est pas partitionnée (et n'est pas massive), je recommande simplement de la charger entièrement dans ClickHouse et de la filtrer ensuite en SQL afin d'éviter d'engager des frais de traitement sur BigQuery.

Une fois prêt à effectuer l'export initial, poursuivons.

Je vais exporter les données au format parquet : c'est le type de fichier le plus proche du système de fichiers utilisé par BigQuery, et il se charge facilement dans ClickHouse en conservant les types de colonnes intacts.

La commande pour exporter une table depuis BigQuery est :

bq extract — destination_format=PARQUET \
<project_id>:<dataset>.<table_name> \
gs://<bucket_name>/<path_and_filename>

(Petite remarque : si vous placez les fichiers dans un sous-dossier, assurez-vous qu'il existe déjà dans votre bucket, sans quoi vous obtiendrez un message d'erreur très obscur sur les arguments positionnels.)

Une fois la table exportée vers GCS, il est temps de la charger dans ClickHouse — et notre périple continue.

Chargement initial des données dans ClickHouse

Pour le chargement dans ClickHouse, la requête SQL officielle à exécuter est la suivante (au moment de la rédaction) :

CREATE TABLE <table_name>
ENGINE = MergeTree
ORDER BY tuple() AS
SELECT *
FROM S3Cluster(default,
  'https://storage.googleapis.com/<bucket_name>/<path>/*.parquet');

Note : certaines versions de ClickHouse présentent un bug qui fait échouer la requête ci-dessus, tout en créant néanmoins la table. Pour contourner le problème, faites simplement un INSERT INTO

suivi de la partie SELECT vue plus haut, et l'insertion s'effectuera correctement.

La méthode recommandée et plus sécurisée consiste à utiliser une clé HMAC. Veillez à bien l'ajouter d'abord au bucket pour le compte de service utilisé : c'est une erreur fréquente. Notez que ces clés servent également à certaines méthodes de réplication présentées plus loin dans cet article ; mieux vaut donc s'en occuper dès maintenant et avoir vos clés générées et chargées dans les bons buckets.

Si vous utilisez ces clés, employez plutôt ce SQL dans ClickHouse :

CREATE TABLE <table_name>
ENGINE = MergeTree
ORDER BY tuple() AS
SELECT *
FROM s3Cluster(‘default’,
  ‘https://storage.googleapis.com/<bucket_name>/<path>/*.parquet’,
  <hmac_access_key>,
  <hmac_secret>)

Dans ces requêtes, l'astérisque (*) est utilisé dans le nom de fichier pour récupérer tous les fichiers du répertoire ayant l'extension parquet. Veillez aussi à ne pas confondre une clé de compte de service avec une clé HMAC : ce sont deux choses totalement différentes. Les clés HMAC vivent uniquement dans GCS, pas dans le service IAM de GCP. Par ailleurs, si vous ne mettez pas l'extension sur vos fichiers, vous devrez passer Parquet en argument de l'appel à s3Cluster, sans quoi l'appel pourra retourner une erreur (un problème courant avec les services qui interrogent GCS via des clés HMAC).

C'est ici que les choses deviennent intéressantes et que ClickHouse prend tout son sens. La ligne avec ENGINE est l'une des clés de la performance. Ces éléments s'appellent des Table Engines et font à eux seuls l'objet d'au moins une série complète de formations ClickHouse ; je ne les couvrirai donc pas ici. Je renvoie plutôt vers la documentation officielle de ClickHouse à leur sujet, disponible ici.

Lors du chargement, vous devez choisir le bon engine en fonction de votre usage. Les engines de la famille MergeTree sont d'excellents moteurs de stockage généralistes, avec quelques variantes spécifiques, mais je recommande FORTEMENT de regarder les autres options et d'aligner votre usage sur celle qui correspond le mieux. C'est l'une des clés — sinon LA clé — de la performance avec ClickHouse ! On ne le répétera jamais assez.

Cela étant dit, il reste une dernière étape importante dans le chargement initial des données dans ClickHouse : supprimer les données temporaires. Trop de personnes oublient cette étape et finissent par payer le stockage GCS jusqu'à ce qu'un audit le révèle plus tard ; ne tombez pas dans ce piège et traitez le sujet maintenant pour économiser. À défaut, mettez-vous au moins un rappel pour ne pas l'oublier.

De manière générale, c'est une bonne idée de supprimer les fichiers de votre bucket GCS pour réduire les coûts. Si vous n'êtes pas sûr d'en avoir besoin pour un rechargement ultérieur, mettez en place une règle de cycle de vie pour les supprimer après 30 jours (ou plus) afin de ne pas oublier.

Schémas de réplication

L'étape suivante consiste à mettre en place la réplication des données, dont les méthodes varient selon votre mode actuel d'ingestion vers BigQuery. Dans cet article, je couvrirai cela via des ingestions en batch. Le streaming sera traité dans un prochain article, car il s'agit d'un scénario bien plus complexe.

L'autre point important, c'est qu'il n'existe pas d'approche universelle : tout dépend du fonctionnement actuel de votre pipeline de données. Je vais donc proposer les schémas les plus fréquents que nous observons chez DoiT et vous laisser, en tant qu'implémentateur, décider de la meilleure stratégie pour votre organisation.

Prérequis de la réplication

Toutes ces méthodes de réplication des données vers les deux sources reposent sur certains prérequis. Heureusement, la plupart des datasets les intègrent déjà, donc ce n'est généralement pas un sujet d'inquiétude — mais par souci de transparence, je dois les mentionner.

Prérequis 1 : un horodatage d'ingestion ou des marqueurs incrémentaux

Pouvoir déterminer où la réplication a commencé et/ou s'est arrêtée est essentiel pour éviter d'insérer des doublons et donc des incohérences de données. Il peut s'agir d'un horodatage d'ingestion ou d'une clé primaire unique de nature incrémentale. Dans BigQuery, cela coïncide souvent avec une frontière de partition.

Prérequis 2 : déterminer la source de vos données

Cela revient à identifier d'où viennent vos données avant qu'elles ne soient chargées dans BigQuery. Très souvent, c'est GCS ou S3 pour des données de type batch ; ou alors elles sont streamées directement dans BigQuery via un abonnement Pub/Sub ; ou encore, elles proviennent de Dataflow/Beam après transformation. Connaître l'origine des données est indispensable pour choisir la meilleure méthode de réplication.

Prérequis 3 : déterminer la fraîcheur attendue des données

C'est plutôt une question de fond : avez-vous besoin de données en temps réel ou de données éventuelles que votre outil de BI pourra interroger dans ClickHouse ? Si vous avez besoin des données à la minute près, parlons de temps réel ; si une mise à jour toutes les 30 minutes ou plus suffit, parlons d'éventuel pour choisir une stratégie.

Mise en place de la réplication en batch

La réplication en batch vers ClickHouse s'effectue à intervalles réguliers pour acheminer les données depuis la source — ou depuis BigQuery — vers ClickHouse. Ces stratégies sont les plus simples à mettre en place et s'appliquent lorsque vous disposez des données dans des fichiers ou via un autre mécanisme de batch avant le chargement dans BigQuery.

L'autre option consiste à extraire les données de BigQuery vers GCS, puis à les charger dans ClickHouse depuis là.

La toute première méthode consiste à charger les données stockées dans GCS directement dans ClickHouse via une Cloud Function. Cela ne fonctionne que si vos données sont déjà dans GCS et dans un format compatible avec ClickHouse.

Cette méthode est très simple : c'est un job de chargement direct, comparable à ce que la plupart des clients font vers BigQuery.

La seconde méthode s'applique lorsque les données sont déjà stockées dans BigQuery et doivent en être répliquées vers ClickHouse pour utilisation. Elle est moins simple, comporte plus d'éléments mobiles et engendre plus de coûts ; ce n'est donc pas la méthode privilégiée.

Notez que si vos données se trouvent dans un autre datastore — par exemple une base MySQL ou PostgreSQL — qui sert d'étape intermédiaire avant BigQuery ou est répliqué vers BigQuery, alors la seconde méthode est celle à privilégier. Je préciserai plus bas la marche à suivre pour les scénarios avec datastore non-BigQuery.

Réplication en batch avec données dans GCS avant chargement dans BigQuery

Cette méthode est de loin la plus simple : quelques étapes seulement, avec une mise en place minimale.

En résumé, il s'agit simplement de prendre vos fichiers de données depuis GCS et de réaliser un LOAD de ces fichiers vers ClickHouse. Bien sûr, ce n'est jamais aussi simple en pratique (comme toujours), mais on n'en est pas loin, et je fournis déjà l'essentiel du code nécessaire.

La méthode la plus simple que j'aie trouvée jusqu'ici consiste à mettre un trigger sur GCS pour les fichiers finalisés dans votre bucket, et à appeler une Cloud Function qui charge le fichier dans ClickHouse. La procédure est documentée ici ; utilisez le code source fourni dans votre déploiement, mais lisez d'abord la documentation pour vous assurer que les prérequis sont remplis (en particulier la création du secret qui contiendra l'ensemble de vos informations de connexion).

À noter : vous voudrez sans doute ne charger que les fichiers d'un certain type, et éventuellement les charger dans des tables différentes selon le chemin ou le nom de fichier. Cette logique devra être ajoutée par vos soins selon votre cas d'usage ; j'ai indiqué dans le fichier les endroits où ces modifications doivent être apportées.

Réplication en batch avec données déjà dans BigQuery

Vient ensuite la réplication en batch depuis BigQuery vers ClickHouse à intervalles réguliers. Ces méthodes sont parfaites si vous chargez les données directement depuis ailleurs vers BigQuery sans pouvoir les capter en amont. C'est souvent le cas avec des inserts en streaming, des chargements via la BigQuery Storage API, ou des données chargées depuis des sources externes comme Stitch ou Fivetran vers BigQuery.

Note sur les coûts : cette méthode devra probablement interroger les données à intervalles réguliers, ce qui engendrera certains frais. Gardez-le en tête et veillez à partitionner et/ou clusteriser correctement vos données dans la table sous-jacente afin de minimiser ces coûts. Vous pouvez aussi opter pour la facturation à la demande au lieu d'Editions selon le volume de données ingéré. Bien souvent, créer un projet distinct dédié à ces exports des données stockées dans le projet principal est la voie privilégiée pour bien séparer les workloads. Cela permet aussi d'utiliser la facturation à la demande, ou une réservation distincte sous Editions, et donc de bénéficier de la Standard Edition pour des coûts plus faibles.

Le principe global de cette méthode : exécuter à intervalle régulier une scheduled query dans BigQuery qui exporte les nouvelles données vers un bucket GCS. Une fois dans le bucket GCS, une Cloud Function se déclenche et charge les données dans ClickHouse une fois l'écriture du fichier terminée.

Cette méthode peut être coûteuse en termes de requêtes, de stockage et de volume d'appels à la Cloud Function, mais elle assure un chargement à intervalle très prévisible. Si vous ne pouvez pas charger les données avant BigQuery, c'est probablement la meilleure option, et vous économiserez sans doute tout de même sur les coûts de requêtage.

Pour réduire le coût, il est généralement préférable d'aligner vos exports sur une partition, afin que celle-ci puisse être exportée directement vers GCS (voir la commande plus haut). Par exemple, si vous partitionnez par heure, planifiez le processus de manière à ce qu'il puisse récupérer en une fois l'intégralité des données de l'heure concernée.

Entrons maintenant dans le vif du sujet pour voir comment cela fonctionne !

L'exemple concret

Pour les besoins de cet exemple, j'ai créé une table très basique avec le schéma suivant dans BigQuery :

Je l'ai partitionnée sur transaction_time pour accélérer les requêtes dans ce cas précis.

Les données sont chargées dans cette table chaque heure et contiennent l'ensemble des données de l'heure écoulée — autrement dit, aucune donnée ne sera oubliée pour être incluse dans un chargement ultérieur. C'est un choix de simplicité, qui se rencontrerait très rarement dans la vraie vie.

Toujours par souci de simplicité, et c'est aussi une excellente pratique, lorsque le chargement est terminé un message est publié dans un topic Pub/Sub. Un abonnement est ensuite attaché à ce topic et déclenche une Cloud Function qui se charge du travail à votre place, évitant la mise en place d'un petit pipeline. Variante : à la fin de l'écriture du fichier, une Cloud Function peut être déclenchée pour le charger automatiquement, ce qui rapprocherait davantage le résultat du temps réel qu'un chargement à intervalle fixe.

Voici le lien vers la Cloud Function qui réalise ce chargement, accompagné d'un fichier readme expliquant son utilisation.

J'ai placé les identifiants ClickHouse dans un secret Secret Manager et expose ces valeurs comme variables d'environnement de la Cloud Function. C'est la manière la plus simple — et l'une des plus sûres — d'accéder à vos identifiants depuis une Cloud Function. J'ai documenté tout cela dans le fichier README.md associé au code Python.

La première étape consiste à créer une Cloud Function. Notez que c'est l'événement finalized sur un bucket GCS qui la déclenche. Votre configuration de la Cloud Function doit ressembler à ceci (notez aussi l'avertissement concernant le compte de service : assurez-vous de bien lui accorder les permissions requises) :

Avant de cliquer sur Enregistrer, déroulez la section Runtime, build, connections and security settings et accédez à Security and Image Repo. Tout en haut, une section Secrets vous permet d'ajouter un secret pour chacune des valeurs requises, comme ci-dessous. Veillez à sélectionner Exposed as environment variable dans la liste déroulante pour chacun afin que cette Cloud Function fonctionne.

Il faudra le faire pour chacun des secrets nécessaires à la Cloud Function (host, port, secure, username et password). Port et secure ont des valeurs par défaut respectives de 9019 et True ; si ces valeurs vous conviennent, les secrets correspondants ne sont pas requis. Notez également l'avertissement concernant le compte de service que j'ai laissé visible : assurez-vous qu'il a accès en lecture à Secret Manager avant de continuer.

L'étape suivante consiste à utiliser cette requête en tant que Scheduled Query (voici la documentation officielle pour ceux qui n'en ont jamais créé) dans BigQuery, planifiée pour s'exécuter chaque heure, 5 minutes après l'heure pleine.

Cela fait, attendez l'heure prévue puis vérifiez votre bucket GCS : un nouveau répertoire contenant les fichiers doit avoir été créé. Si tel est le cas, votre Cloud Function a fonctionné.

Utiliser cet exemple en conditions réelles

Cet exemple est volontairement TRÈS simple et idéalisé. Dans la vraie vie, on sait que les données ne se chargent jamais parfaitement dans des bornes horaires précises, ni à l'heure, sans arrivées tardives. J'ai sciemment laissé ces conditions de côté, car ClickHouse y apporte une bonne réponse : ReplacingMergeTree et CollapsingMergeTree.

L'équipe ClickHouse a publié un excellent article à ce sujet, disponible ici, qui explique comment les utiliser pour les mises à jour et les suppressions. Le choix de l'un ou l'autre dépendra des schémas d'utilisation des données.

Par ailleurs, vous voudrez probablement enrichir le dispositif de fonctionnalités supplémentaires : notifications, déclenchement de jobs ETL/ELT, etc. Je recommande donc d'ajouter du code en fin d'exécution de la Cloud Function pour publier un message dans Pub/Sub et déclencher toute logique en aval.

Connecter Looker à ClickHouse

Dernière étape : convertir vos connexions Looker pour qu'elles utilisent désormais ClickHouse.

L'emplacement n'est pas toujours évident à trouver, mais rendez-vous dans le mode Admin de Looker. Cela se trouve dans Database -> Connections, dans le panneau de gauche du mode Admin.

Admin -> Database -> Connections

Cliquez ensuite sur Add et renseignez les informations relatives à votre instance ClickHouse sur la page de connexion.

En cliquant sur Connections puis en sélectionnant le dialecte ClickHouse, l'écran suivant apparaît :

Vue de la nouvelle connexion ClickHouse dans Looker

Une fois cet écran chargé, copiez-collez les informations issues de votre instance, comme celles ci-dessous depuis la console Aiven :

Informations de connexion à l'instance depuis la console ClickHouse d'Aiven

Les informations à récupérer correspondent généralement au connecteur JDBC ou à l'accès HTTPS de ClickHouse.

Une fois renseignées, utilisez le bouton de test dans Looker pour vérifier que la connexion fonctionne.

Enfin, point essentiel pour la sécurité : la fonctionnalité de whitelist d'IP de votre instance ClickHouse. Cette liste définit les seules IP autorisées à se connecter à votre instance. En suivant les instructions disponibles ici chez Google, vous obtiendrez la liste des adresses IP à autoriser. Cela garantira que seules les connexions provenant des IP de Looker pourront atteindre votre instance ClickHouse.

Vue des adresses IP autorisées depuis la console Aiven

Ce schéma devrait vous permettre de démarrer avec une instance ClickHouse référencée depuis Looker à la place de BigQuery, et ainsi d'économiser potentiellement beaucoup d'argent sur vos capacités de requêtage.

Comme il s'agit d'un exemple élémentaire, ce ne sera probablement pas une solution clé en main pour l'ensemble de vos besoins de chargement de données ; voyez-le plutôt comme la première pierre du gué dans votre parcours vers des économies obtenues en mettant les données en cache dans ClickHouse plutôt qu'en payant à la requête sur BigQuery.