05 Benchmark et bascule
Reconstruisez les tableaux de bord dans ClickHouse, comparez les sept requêtes sur les deux moteurs, basculez le producteur, vérifiez la parité et supprimez les environnements.
Point de départ
Module 04 terminé : la couche analytics est remplie et testée.
analytics.fact_trips contient environ 50 millions de lignes ;
analytics.dim_taxi_zones, analytics.dim_payment_type, analytics.dim_vendor et
analytics.dim_date sont entièrement chargées ; dbt test réussit de bout en bout ; et
analytics.taxi_zones_dict est actif et renvoie les arrondissements avec dictGet().
analytics.agg_hourly_zone_trips reste vide — par conception, pas par défaut — jusqu’à
la bascule de ce module. Le producteur Snowflake fonctionne encore et l’écart entre
Snowflake et ClickHouse reste ouvert. Prévoyez environ 45 minutes.
Pourquoi
Tous les modules précédents ont servi de préparation. Le module 03 a prouvé que ClickHouse pouvait contenir 50 millions de lignes ; le module 04, que le pipeline dbt pouvait s’y exécuter. Aucun ne suffit à lui seul pour qu’un partenaire valide une « migration » : il manque encore une mesure et une bascule.
La mesure est le benchmark de l’étape 2 : les sept mêmes requêtes que dans votre plan de migration, exécutées l’une après l’autre dans Snowflake et ClickHouse, avec la médiane de trois passages sur chaque moteur. C’est ce qui transforme « ClickHouse devrait être plus rapide » en un gain précis et défendable qu’un partenaire peut présenter à ses propres parties prenantes.
La bascule de l’étape 3 complète le travail. Jusqu’ici, les deux systèmes ont fonctionné côte à côte : Snowflake comme système de référence, ClickHouse rattrapant son retard. Une migration qui ne déplace jamais réellement le chemin d’écriture reste une copie, pas une migration. L’étape 3 arrête le producteur Snowflake, referme l’écart entretenu par ses écritures continues depuis le module 01, puis commence à écrire les nouvelles courses dans ClickHouse — le moment où ClickHouse devient le système de référence.
C’est également le dernier module participant avant l’évaluation écrite du module 06, réalisée à livre ouvert à partir de vos productions. Les tableaux de bord, le CSV du benchmark et la vérification de parité doivent être réels avant de supprimer quoi que ce soit à l’étape 5.
Concepts — fonctionnement interne
La couche BI, concrètement. À l’étape 1,
bash superset/add_clickhouse_connection.sh appelle directement l’API REST de Superset ;
aucun parcours manuel de l’interface n’est nécessaire. Le script enregistre la connexion
ClickHouse, puis importe l’export de tableau de bord fourni, ajoutant quatre tableaux
ClickHouse aux trois tableaux Snowflake créés au module 01 (sept au total) :
| Tableau de bord | Reproduit | Ce qu’il démontre |
|---|---|---|
| CH — Operations Command Center | Snowflake Dashboard 1 | Données actives de fact_trips (après la bascule) ; mêmes KPI, requêtes plus rapides |
| CH — Executive Weekly Report | Snowflake Dashboard 2 | QUALIFY réécrit sous forme de sous-requête avec ROW_NUMBER() |
| CH — Driver & Quality Analytics | Snowflake Dashboard 3 | JSONExtractString à la place de LATERAL FLATTEN de Snowflake |
| CH — Capabilities Showcase | (nouveau — aucun équivalent Snowflake) | Fonctions approximatives, jointures par dictionnaire et clause SAMPLE |
Ce que sollicitent les sept requêtes du benchmark. L’étape 2 exécute les sept mêmes requêtes que votre plan sur les deux moteurs et compare le temps réel écoulé. Chacune cible une différence de dialecte ou une capacité du moteur relevée au module 02 :
| Requête | Élément sollicité |
|---|---|
| Q1 | Chiffre d’affaires horaire par arrondissement |
| Q2 | Distance moyenne glissante sur 7 jours |
| Q3 | 10 principales courses — QUALIFY de Snowflake contre sous-requête ROW_NUMBER() dans ClickHouse |
| Q4 | Notes des chauffeurs — LATERAL FLATTEN de Snowflake contre JSONExtractString de ClickHouse |
| Q5 | Tarification dynamique — VARIANT de Snowflake contre String + JSONExtract* de ClickHouse |
| Q6 | Agrégation horaire — MERGE de Snowflake contre ReplacingMergeTree de ClickHouse |
| Q7 | CDC / fraîcheur des données actives |
L’écart de bascule. Le producteur Snowflake écrit environ 60 courses par minute
depuis le module 01 sans jamais s’arrêter. Le script du module 03 a capturé TRIPS_RAW
dans l’état où elle se trouvait au moment de son exécution, puis le module 04 a construit
le pipeline dbt au-dessus de cet instantané. Toutes les courses ajoutées depuis le
dernier lot n’existent que dans Snowflake : la fin manque dans ClickHouse. Le passage de
rattrapage --resume de l’étape 3 referme précisément cet écart : il lit le
max(pickup_at) déjà présent dans ClickHouse et ne récupère que les lignes écrites après.
Le rattrapage d’un écart ouvert depuis le module 01 prend ainsi quelques secondes ou
minutes, pas les 40 à 50 minutes de la migration en masse initiale. Si vous l’ignorez et
basculez quand même, ClickHouse perd définitivement toutes les courses arrivées dans
l’intervalle — une erreur de parité silencieuse que l’étape 4 est conçue pour détecter,
mais seulement si l’étape 3 a été exécutée dans l’ordre.
agg_hourly_zone_trips se remplit ici, et uniquement ici. Elle est vide depuis le
module 03 par conception : son filtre incrémentiel
WHERE pickup_at >= now() - INTERVAL 2 HOUR ne correspond qu’aux lignes écrites par un
producteur actif, et jusqu’ici le seul producteur écrivait dans Snowflake. Dès que
l’étape 3 démarre le producteur ClickHouse, les nouvelles lignes entrent enfin dans cette
fenêtre de deux heures. La table — et tous les graphiques qui en dépendent — cesse alors
d’être vide pour la première fois de l’atelier.
Étape 1 — Ajouter les tableaux de bord ClickHouse
Pour réaliser manuellement toute la construction — création pas à pas des 7 jeux de données, 18 graphiques et 4 tableaux de bord dans l’interface Superset —, suivez Superset sur ClickHouse. Pour ignorer ces étapes et tout importer en une seule fois :
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse"
source .env && source .clickhouse_state
bash superset/add_clickhouse_connection.shL’hôte ClickHouse contenu dans le fichier
superset/dashboards/dashboard_export_*.zip du dépôt a été remplacé par
your-instance.clickhouse.cloud. Le raccourci ci-dessus corrige l’URI depuis .env
avant l’import, de façon transparente. En revanche, si vous importez le ZIP
manuellement dans l’interface Superset, la connexion créée ne fonctionnera pas : vous
devrez ensuite la modifier afin qu’elle utilise le véritable CLICKHOUSE_HOST et les
bons identifiants. Consultez
Superset sur ClickHouse pour
les instructions précises.
Vérification :
Ouvrez http://localhost:8088 (admin / admin). Sous Dashboards, vous devez voir 7
éléments au total : 3 tableaux de bord Snowflake et 4 dont le nom commence par CH —.
Étape 2 — Exécuter le benchmark
Exécutez les sept requêtes successivement dans Snowflake et ClickHouse, puis comparez le temps écoulé :
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse"
source .env && source .clickhouse_state
./scripts/run_benchmark.shSortie attendue :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
NYC Taxi Lab — Query Benchmark: Snowflake vs ClickHouse
(median of 3 runs each)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Query Snowflake ClickHouse Speedup
────────────────────────────────────────────────────────────────────────
Q1 Hourly revenue by borough 5.0s 0.7s 6x
Q2 Rolling 7-day avg distance 5.5s 0.8s 6x
Q3 Top 10 trips (QUALIFY→subquery) 5.0s 0.7s 6x
Q4 Driver ratings (JSON flatten) 5.4s 0.8s 6x
Q5 Surge pricing (VARIANT) 5.1s 0.7s 6x
Q6 Hourly aggregation (MERGE→RMT) 5.9s 0.8s 7x
Q7 CDC/live data freshness 7.9s 0.8s 9x
────────────────────────────────────────────────────────────────────────
Total 40.1s 5.6s 7x avg
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━Ces chiffres correspondent à une exécution représentative, pas à une garantie. Vos résultats varieront selon la taille du warehouse, le niveau ClickHouse Cloud et les autres charges présentes sur les services au même moment.
Le script écrit chaque exécution dans
workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse/scripts/benchmark_results_<timestamp>.csv.
Conservez le fichier sur le disque : c’est l’un des deux fichiers nécessaires à
l’évaluation du module 06. Ne le supprimez pas lors du nettoyage de l’étape 5.
Étape 3 — Basculer vers ClickHouse
Écart de migration. Le producteur Snowflake est resté actif pendant tout l’atelier,
avec environ 60 courses par minute. Le script du module 03 a capturé TRIPS_RAW telle
qu’elle était au moment de son exécution ; les lignes ajoutées ensuite n’existent que
dans Snowflake. Refermez cet écart avant de déplacer le chemin d’écriture.
Exécutez les quatre opérations suivantes dans l’ordre indiqué — cet ordre assure la cohérence des deux systèmes :
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse"
source .env && source .clickhouse_state
source .venv/bin/activate
# Step 1: Stop the Snowflake producer (freeze the dataset)
docker stop nyc_taxi_producer
# Step 2: Catch up the delta — only migrates rows with pickup_at newer than
# what's already in ClickHouse. Runs in seconds to minutes, not the original
# 40-50 minutes, because only the gap rows move.
python scripts/02_migrate_trips.py --resume
# Step 3: Refresh the analytics tables with the newly migrated rows
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse/dbt/nyc_taxi_dbt_ch"
dbt run
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse"
# Step 4: Start the ClickHouse producer
source .env && source .clickhouse_state
./scripts/03_cutover.shNe sautez pas l’étape 2. --resume lit le max(pickup_at) déjà présent dans
ClickHouse et ajoute un filtre WHERE PICKUP_DATETIME > <watermark> à la requête
Snowflake. Il ne transfère ainsi que les lignes écrites pendant et après la migration
initiale du module 03 — celles que ClickHouse n’a jamais vues. Une bascule sans ce passage
ferait perdre définitivement toutes les courses de cette fenêtre. La vérification de
parité de l’étape 4 est conçue pour le détecter, mais seulement si cette étape est
exécutée d’abord.
./scripts/03_cutover.sh affiche l’invite Type "cutover" to confirm, puis répète les
étapes 1 à 3 comme protection supplémentaire : il arrête à nouveau le producteur
Snowflake (sans effet s’il est déjà arrêté), exécute un autre dbt run, puis construit
et démarre le producteur ClickHouse (nyc_taxi_ch_producer). Trente secondes après son
démarrage, il vérifie l’arrivée de nouvelles lignes dans default.trips_raw et relance
dbt une dernière fois — l’exécution qui donne enfin ses premières lignes à
agg_hourly_zone_trips et referme l’écart volontairement laissé par le module 04.
Vérification :
-- Most recent trip should be within the last 60 seconds
SELECT max(pickup_at) AS most_recent_trip FROM default.trips_raw;
-- Row count should be increasing — wait 60 seconds and run again
SELECT count() FROM default.trips_raw;
-- agg_hourly_zone_trips should now have rows for the first time in the lab
SELECT count() FROM analytics.agg_hourly_zone_trips;docker ps | grep nyc_taxi_ch_producer # should show runningMaintenir la fraîcheur de la couche analytics. fact_trips et
agg_hourly_zone_trips sont des modèles dbt incrémentiels : ils ne s’actualisent pas
automatiquement. 03_cutover.sh exécute dbt run une fois après avoir confirmé
l’activité du producteur, mais les tableaux de bord prennent du retard à mesure que les
courses s’accumulent. Relancez dbt run depuis dbt/nyc_taxi_dbt_ch chaque fois que
vous voulez des chiffres à jour (en production, vous le planifieriez avec cron, Airflow
ou dbt Cloud ; à la demande suffit ici). À l’inverse,
analytics.mv_live_trip_feed est une vue matérialisée actualisable — le dbt run du
module 04 l’a déjà créée avec engine = 'ReplacingMergeTree(refreshed_at)' — mais l’atelier n’active jamais son intervalle :
l’instruction MODIFY REFRESH EVERY 30 SECOND qui permettrait sa réexécution autonome
n’existe que comme commentaire dans le modèle. Vous pouvez l’activer vous-même par une
unique instruction ALTER TABLE ; sans cela, mv_live_trip_feed n’est actualisée qu’une
fois, lors de sa création par dbt.
Bascule inverse, si vous devez annuler cette étape et revenir au producteur Snowflake :
docker stop nyc_taxi_ch_producer
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/01-setup-snowflake/superset"
docker-compose --env-file ../.env up -d producerÉtape 4 — Vérifier la parité
Maintenant que le rattrapage --resume a été effectué et que le producteur ClickHouse
est actif, les deux systèmes doivent être à parité. Confirmez-le :
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse"
source .env && source .clickhouse_state
bash scripts/01_verify_migration.shSortie attendue :
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Migration Parity Check
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ ClickHouse default.trips_raw: 50,008,250 rows
✓ Snowflake NYC_TAXI_DB.RAW.TRIPS_RAW: 50,008,250 rows
✓ Row count parity: PASS (difference: 0 rows = 0.0000%)
✓ trip_metadata populated: 50,008,250 non-empty rows
pickup_at range: 2022-03-30 2026-03-31
✓ ClickHouse has 50,008,250 rows — migration looks complete
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━Votre nombre de lignes sera différent ; c’est la ligne de parité qui compte. Le
producteur Snowflake est désormais arrêté, donc aucune nouvelle ligne n’y arrive. Les
comptages doivent être identiques ou ne différer que de quelques lignes si un lot était
encore en cours pendant le passage --resume, bien sous le seuil de 0,01 % vérifié par
le script.
Si la vérification de parité échoue (écart supérieur à 0,01 %), l’écart n’a pas été entièrement refermé. Relancez le rattrapage, puis vérifiez à nouveau :
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse"
source .env && source .clickhouse_state
python scripts/02_migrate_trips.py --resume
bash scripts/01_verify_migration.shÉtape 5 — Supprimer les environnements
Avant toute suppression, confirmez que la migration se trouve dans un état final correct :
| Vérification | Commande | Résultat attendu |
|---|---|---|
| Parité du nombre de lignes | bash scripts/01_verify_migration.sh | Correspondance ≥ 99,9 % |
| Tests dbt | dbt test (depuis dbt/nyc_taxi_dbt_ch) | Tous les tests réussissent |
| Tableaux de bord Superset | Ouvrir http://localhost:8088 | 7 tableaux visibles (3 SF + 4 CH) |
| Résultats du benchmark | cat scripts/benchmark_results_<timestamp>.csv | Les 7 requêtes ont une valeur de gain |
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse"
source .env && source .clickhouse_state
bash scripts/01_verify_migration.sh
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse/dbt/nyc_taxi_dbt_ch"
dbt testLorsque les quatre vérifications réussissent, deux fichiers suffisent au module 06 et
survivent à la suppression :
workshop_public/snowflake_migration_lab/02-plan-and-design/migration-plan.md et
workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse/scripts/benchmark_results_<timestamp>.csv.
Le module 06 est une évaluation écrite à livre ouvert d’environ 60 minutes qui n’a
besoin de rien d’autre : inutile de laisser fonctionner un service ClickHouse Cloud
payant pendant un examen écrit. Copiez ou notez le contenu des deux fichiers dans un
endroit accessible, puis supprimez tout :
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse"
source .env && ./teardown.shCette commande détruit le service ClickHouse Cloud (avec terraform destroy) et, si la
bascule a eu lieu, le conteneur du producteur de courses ClickHouse.
Les ressources Snowflake de la partie 1 ne sont pas supprimées par ce script. Supprimez séparément la partie Snowflake :
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/01-setup-snowflake"
source .env && ./teardown.shComment vérifier que vous avez terminé
À ce stade du module, vous devez avoir confirmé, dans l’ordre :
- Réussite de la vérification de parité —
01_verify_migration.shà l’étape 4 a indiqué PASS avec un écart de nombre de lignes inférieur à 0,01 %. - CSV du benchmark présent sur le disque — l’étape 2 a écrit
benchmark_results_<timestamp>.csvavec une valeur de gain pour chacune des 7 requêtes, et vous l’avez conservé avant la suppression de l’étape 5. - Présence des 7 tableaux de bord — la vérification Superset de l’étape 1 a montré
côte à côte 3 tableaux Snowflake et 4 tableaux
CH —. - Écriture du producteur ClickHouse — le bloc de vérification de l’étape 3 a montré
que
default.trips_rawrecevait des lignes et quenyc_taxi_ch_producerfonctionnait, avant son arrêt par l’étape 5.
Si l’une de ces conditions n’était pas remplie à ce moment-là, revenez à l’étape correspondante au lieu de relancer ces vérifications maintenant : l’étape 5 a déjà détruit le service ClickHouse Cloud et, si la bascule a eu lieu, le conteneur du producteur.
État final
La migration est terminée et mesurée : 50 millions de lignes ont été déplacées de Snowflake vers ClickHouse et vérifiées à parité ; les sept requêtes ont été comparées directement, ClickHouse étant plus rapide pour chacune ; la couche BI a été reconstruite avec 4 tableaux de bord ClickHouse à côté des 3 tableaux de bord Snowflake d’origine ; et le chemin d’écriture a définitivement basculé de Snowflake vers ClickHouse. Les deux environnements cloud ont été supprimés : plus de service ClickHouse Cloud, plus de conteneur du producteur ClickHouse et, après la suppression de la partie 1, plus de warehouse Snowflake.
Deux fichiers survivent et suffisent au module 06 :
workshop_public/snowflake_migration_lab/02-plan-and-design/migration-plan.md et
workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse/scripts/benchmark_results_<timestamp>.csv.
Le module 06 est une évaluation écrite à livre ouvert : apportez uniquement ces deux
fichiers.
04 Reconstruction du pipeline dbt
Reconstruisez le pipeline Medallion dans ClickHouse avec dbt-clickhouse — modèles incrémentiels delete_insert, ReplacingMergeTree et vues matérialisées actualisables — puis créez le dictionnaire des zones.
06 Évaluation
Réalisez l’évaluation à livre ouvert, composée de 20 questions à choix multiple et de 4 questions ouvertes, pour obtenir le badge de compétence en migration ClickHouse.