05 Benchmark dan cutover
Bangun ulang dashboard di ClickHouse, benchmark ketujuh query terhadap kedua engine, cutover producer, verifikasi paritas, dan bongkar semuanya.
Titik awal
Modul 04 selesai: lapisan analytics sudah terisi dan teruji. analytics.fact_trips
menyimpan kurang lebih 50 juta baris; analytics.dim_taxi_zones,
analytics.dim_payment_type, analytics.dim_vendor, dan analytics.dim_date terisi penuh;
dbt test lulus dari ujung ke ujung; dan analytics.taxi_zones_dict hidup serta
mengembalikan borough melalui dictGet(). analytics.agg_hourly_zone_trips masih kosong —
sesuai desain, bukan karena cacat — dan tetap demikian sampai cutover di modul ini. Producer
Snowflake masih berjalan, dan jeda antara Snowflake dan ClickHouse masih terbuka. Siapkan
sekitar 45 menit.
Mengapa
Setiap modul sampai titik ini adalah persiapan. Modul 03 membuktikan ClickHouse bisa menampung 50 juta baris; modul 04 membuktikan pipeline dbt berjalan di atasnya. Tak satu pun dari keduanya, sendirian, adalah sesuatu yang akan disetujui seorang partner sebagai "termigrasi" — untuk itu butuh dua hal lagi: sebuah angka, dan sebuah cutover.
Angkanya adalah benchmark di Langkah 2: ketujuh query yang sama dari rencana migrasi Anda, dijalankan beruntun terhadap Snowflake dan terhadap ClickHouse, dengan median dari tiga eksekusi masing-masing. Itulah yang mengubah "ClickHouse seharusnya lebih cepat" menjadi percepatan spesifik yang bisa dipertahankan dan bisa diletakkan seorang partner di depan pemangku kepentingannya sendiri.
Cutover di Langkah 3 adalah separuh lainnya. Setiap modul sejauh ini menjaga kedua sistem berjalan berdampingan, Snowflake sebagai sistem pencatatan dan ClickHouse menyusul di belakangnya. Migrasi yang tidak pernah benar-benar memindahkan jalur tulis adalah penyalinan, bukan migrasi. Langkah 3 menghentikan producer Snowflake, menutup jeda yang dijaga terbuka oleh penulisan terus-menerusnya sejak modul 01, dan mulai menulis perjalanan baru ke ClickHouse — momen ClickHouse menjadi sistem pencatatan.
Ini juga modul learner terakhir sebelum asesmen tertulis di modul 06, yang bersifat open-book terhadap apa pun yang Anda hasilkan di sini. Dashboard, CSV benchmark, dan pemeriksaan paritas semuanya harus nyata sebelum Anda membongkar apa pun di Langkah 5.
Konsep — di balik layar
Lapisan BI, secara mekanis. bash superset/add_clickhouse_connection.sh memanggil REST
API Superset secara langsung di Langkah 1 — tidak ada klik manual di UI Superset. Ia
mendaftarkan koneksi ClickHouse, lalu mengimpor ekspor dashboard yang di-commit, menambahkan
empat dashboard ClickHouse di samping tiga dashboard Snowflake yang dibangun modul 01 (total
tujuh):
| Dashboard | Mencerminkan | Yang didemonstrasikan |
|---|---|---|
| CH — Operations Command Center | Snowflake Dashboard 1 | Data fact_trips live (pasca-cutover); KPI yang sama, query lebih cepat |
| CH — Executive Weekly Report | Snowflake Dashboard 2 | QUALIFY ditulis ulang sebagai subquery ROW_NUMBER() |
| CH — Driver & Quality Analytics | Snowflake Dashboard 3 | JSONExtractString menggantikan LATERAL FLATTEN milik Snowflake |
| CH — Capabilities Showcase | (baru — tanpa padanan Snowflake) | Fungsi aproksimasi, join dictionary, klausa SAMPLE |
Apa yang diuji ketujuh query benchmark. Langkah 2 menjalankan ketujuh query yang sama dari rencana migrasi Anda terhadap kedua engine dan membandingkan waktu jam dinding. Masing-masing menyasar celah dialek atau fitur engine spesifik dari rencana modul 02:
| Query | Menguji |
|---|---|
| Q1 | Pendapatan per jam menurut borough |
| Q2 | Rata-rata jarak bergulir 7 hari |
| Q3 | 10 perjalanan teratas — QUALIFY Snowflake vs subquery ROW_NUMBER() ClickHouse |
| Q4 | Rating pengemudi — LATERAL FLATTEN Snowflake vs JSONExtractString ClickHouse |
| Q5 | Harga lonjakan — VARIANT Snowflake vs String + JSONExtract* ClickHouse |
| Q6 | Agregasi per jam — MERGE Snowflake vs ReplacingMergeTree ClickHouse |
| Q7 | Kesegaran data CDC / live |
Jeda cutover. Producer Snowflake sudah menulis kurang lebih 60 perjalanan per menit sejak
modul 01, dan belum pernah berhenti. Skrip migrasi modul 03 menangkap TRIPS_RAW seperti
keadaannya saat skrip itu berjalan, dan modul 04 membangun pipeline dbt di atas snapshot
tersebut. Setiap perjalanan yang ditulis ke Snowflake setelah batch terakhir skrip migrasi
hanya ada di Snowflake — ekor ClickHouse hilang. Tahap penyusulan --resume di Langkah 3
menutup persis jeda itu: ia membaca max(pickup_at) yang sudah ada di ClickHouse dan menarik
hanya baris yang ditulis setelahnya, jadi satu tahap yang menutup jeda yang terbuka sejak
modul 01 memakan hitungan detik sampai menit, bukan 40-50 menit seperti migrasi massal
awalnya. Lewati langkah itu dan tetap melakukan cutover, dan ClickHouse akan permanen
kehilangan berapa pun jumlah perjalanan yang mendarat di dalam jeda tersebut — kegagalan
paritas senyap yang dibangun Langkah 4 untuk ditangkap, tetapi hanya jika Langkah 3 berjalan
sesuai urutan.
agg_hourly_zone_trips terisi di sini, dan hanya di sini. Ia kosong sejak modul 03 sesuai
desain: filter inkrementalnya adalah WHERE pickup_at >= now() - INTERVAL 2 HOUR, yang hanya
cocok dengan baris yang ditulis producer live, dan sampai modul ini satu-satunya producer yang
menulis adalah milik Snowflake. Begitu Langkah 3 menjalankan producer ClickHouse, baris baru
akhirnya mendarat di dalam jendela dua jam itu, dan tabel tersebut — beserta setiap chart
dashboard yang ditopangnya — berhenti kosong untuk pertama kali dalam lab ini.
Langkah 1 — Tambahkan dashboard ClickHouse
Untuk pembangunan manual lengkap — membuat masing-masing dari 7 dataset, 18 chart, dan 4 dashboard langkah demi langkah di UI Superset — ikuti Superset di ClickHouse. Untuk melewati langkah manual dan mengimpor semuanya sekali jalan:
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.shsuperset/dashboards/dashboard_export_*.zip yang di-commit punya host ClickHouse yang
disamarkan menjadi your-instance.clickhouse.cloud. Jalan pintas di atas menambal URI itu
dari .env sebelum mengimpor, jadi hal ini transparan — Anda tidak akan menyadari
penyamarannya. Tetapi jika Anda malah mengimpor ZIP secara manual melalui UI Superset,
koneksi database yang dibuatnya tidak akan tersambung — Anda harus menyunting koneksi itu
sesudahnya agar menunjuk ke CLICKHOUSE_HOST dan kredensial Anda yang sebenarnya. Lihat
Superset di ClickHouse untuk cara
persisnya.
Verifikasi:
Buka http://localhost:8088 (admin / admin). Di bawah Dashboards, Anda seharusnya melihat
total 7 — 3 dashboard Snowflake dan 4 yang berawalan CH —.
Langkah 2 — Jalankan benchmark
Jalankan ketujuh query beruntun terhadap Snowflake maupun ClickHouse dan bandingkan waktu jam dinding:
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse"
source .env && source .clickhouse_state
./scripts/run_benchmark.shOutput yang diharapkan:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
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
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━Angka-angka ini adalah satu eksekusi representatif, bukan jaminan — angka Anda sendiri akan bervariasi menurut ukuran warehouse, tier ClickHouse Cloud, dan apa pun yang berjalan terhadap kedua layanan pada saat itu.
Skrip menulis setiap eksekusi ke
workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse/scripts/benchmark_results_<timestamp>.csv.
Simpan file itu di disk — ia adalah salah satu dari dua file yang dibutuhkan asesmen modul
06, jadi jangan hapus selama teardown di Langkah 5.
Langkah 3 — Cutover ke ClickHouse
Jeda migrasi. Producer Snowflake sudah berjalan sepanjang lab ini, menulis kurang lebih
60 perjalanan per menit. Skrip migrasi modul 03 menangkap TRIPS_RAW seperti keadaannya saat
skrip itu berjalan — baris yang ditulis sejak itu hanya ada di Snowflake. Tutup jeda tersebut
sebelum men-cutover jalur tulis.
Jalankan keempat langkah di bawah, secara berurutan — urutannya itulah yang menjaga kedua sistem tetap konsisten:
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.shJangan lewati Langkah 2. --resume membaca max(pickup_at) yang sudah ada di ClickHouse
dan menambahkan filter WHERE PICKUP_DATETIME > <watermark> ke query Snowflake, jadi ia
hanya mentransfer baris yang ditulis selama dan setelah eksekusi migrasi awal modul 03 —
baris yang belum pernah dilihat ClickHouse. Lakukan cutover tanpanya dan ClickHouse akan
permanen kehilangan berapa pun jumlah perjalanan yang mendarat dalam jendela itu. Pemeriksaan
paritas di Langkah 4 dibangun untuk menangkap persis hal itu, tetapi hanya jika langkah ini
berjalan lebih dulu.
./scripts/03_cutover.sh meminta Type "cutover" to confirm, lalu mengulangi langkah 1-3
sebagai jaring pengamannya sendiri: ia menghentikan producer Snowflake lagi (tanpa efek jika
Anda sudah melakukannya), menjalankan satu dbt run lagi, lalu membangun dan menjalankan
producer ClickHouse (nyc_taxi_ch_producer). Tiga puluh detik setelah producer berjalan, ia
memastikan baris baru mendarat di default.trips_raw dan menjalankan dbt sekali lagi —
eksekusi yang akhirnya memberi agg_hourly_zone_trips baris pertamanya, menutup jeda yang
sengaja dibiarkan terbuka oleh modul 04.
Verifikasi:
-- 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 runningMenjaga lapisan analytics tetap segar. fact_trips dan agg_hourly_zone_trips adalah
model inkremental dbt — keduanya tidak menyegarkan diri sendiri. 03_cutover.sh menjalankan
dbt run satu kali setelah memastikan producer hidup, tetapi dashboard akan menjadi basi
seiring perjalanan baru menumpuk; jalankan ulang dbt run dari dbt/nyc_taxi_dbt_ch kapan
pun Anda ingin angka terkini (di produksi Anda akan menjadwalkannya — cron, Airflow, dbt
Cloud — tetapi on-demand sudah cukup untuk lab). analytics.mv_live_trip_feed, sebaliknya,
adalah materialized view yang refreshable — dbt run modul 04 sudah membangunnya dengan
engine = 'ReplacingMergeTree(refreshed_at)' — tetapi lab tidak pernah menyalakan interval
refresh-nya: pernyataan MODIFY REFRESH EVERY 30 SECOND yang akan membuatnya menjalankan
ulang dirinya sendiri hanya ada sebagai komentar di file model. Mengaktifkannya adalah satu
pernyataan ALTER TABLE yang Anda jalankan sendiri; tanpa itu, mv_live_trip_feed hanya
diperbarui satu kali saat dbt membangunnya.
Cutover balik, jika Anda perlu membatalkan langkah ini dan kembali ke producer 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 producerLangkah 4 — Verifikasi paritas
Sekarang setelah tahap penyusulan --resume berjalan dan producer ClickHouse aktif, kedua
sistem seharusnya berada dalam paritas. Pastikan hal itu:
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.shOutput yang diharapkan:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
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
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━Jumlah baris Anda sendiri akan berbeda; yang penting adalah baris paritasnya. Producer
Snowflake sudah dihentikan pada titik ini, jadi tidak ada baris baru yang mendarat di sana —
jumlahnya seharusnya sama persis, atau berbeda beberapa baris saja jika ada batch yang masih
dalam perjalanan selama tahap --resume, jauh di dalam ambang 0,01% yang diperiksa skrip.
Jika pemeriksaan paritas gagal (perbedaan lebih dari 0,01%), jeda belum tertutup sepenuhnya — jalankan tahap penyusulan lagi lalu periksa ulang:
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.shLangkah 5 — Bongkar semuanya
Sebelum membongkar apa pun, pastikan migrasi berada dalam kondisi akhir yang benar:
| Pemeriksaan | Perintah | Diharapkan |
|---|---|---|
| Paritas jumlah baris | bash scripts/01_verify_migration.sh | Kecocokan jumlah baris ≥ 99,9% |
| Tes dbt | dbt test (dari dbt/nyc_taxi_dbt_ch) | Semua tes lulus |
| Dashboard Superset | Buka http://localhost:8088 | 7 dashboard terlihat (3 SF + 4 CH) |
| Hasil benchmark | cat scripts/benchmark_results_<timestamp>.csv | Ketujuh query punya nilai percepatan |
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 testBegitu keempatnya lulus, dua file adalah segala yang dibutuhkan modul 06, dan keduanya
bertahan melewati teardown:
workshop_public/snowflake_migration_lab/02-plan-and-design/migration-plan.md dan
workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse/scripts/benchmark_results_<timestamp>.csv.
Modul 06 adalah asesmen tertulis open-book, kurang lebih 60 menit, dan tidak membutuhkan
apa pun selain itu — tidak ada alasan menjaga layanan ClickHouse Cloud berbayar tetap
berjalan selama ujian tertulis. Salin atau catat isi kedua file itu di tempat yang bisa Anda
jangkau, lalu bongkar semuanya:
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse"
source .env && ./teardown.shIni memusnahkan layanan ClickHouse Cloud (melalui terraform destroy) dan kontainer producer
perjalanan ClickHouse, jika cutover sudah dilakukan.
Sumber daya Snowflake dari Bagian 1 tidak dibongkar oleh skrip ini. Bongkar sisi Snowflake secara terpisah:
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/01-setup-snowflake"
source .env && ./teardown.shCara memverifikasi bahwa Anda sudah selesai
Sampai titik ini dalam modul, Anda seharusnya sudah memastikan, secara berurutan:
- Pemeriksaan paritas lulus —
01_verify_migration.shdi Langkah 4 melaporkan PASS dengan perbedaan jumlah baris di bawah 0,01%. - CSV benchmark ada di disk — Langkah 2 menulis
benchmark_results_<timestamp>.csvdengan ketujuh query membawa nilai percepatan, dan Anda menyimpannya sebelum teardown di Langkah 5. - 7 dashboard hadir — pemeriksaan Superset di Langkah 1 menampilkan 3 dashboard Snowflake
dan 4 dashboard
CH —berdampingan. - Producer ClickHouse menulis — blok verifikasi di Langkah 3 menunjukkan
default.trips_rawbertambah baris dannyc_taxi_ch_producerberjalan, sebelum Langkah 5 menghentikannya untuk teardown.
Jika salah satu dari hal itu tidak terpenuhi pada saatnya, kembalilah ke langkah yang bersangkutan alih-alih menjalankan ulang pemeriksaan ini sekarang — Langkah 5 sudah memusnahkan layanan ClickHouse Cloud dan, jika cutover terjadi, kontainer producer beserta layanan itu.
Kondisi akhir
Migrasi selesai dan terukur: 50 juta baris dipindahkan dari Snowflake ke ClickHouse dan diverifikasi berada dalam paritas, tujuh query di-benchmark berhadapan langsung dengan ClickHouse lebih cepat pada setiap query, lapisan BI dibangun ulang dengan 4 dashboard ClickHouse di samping 3 dashboard Snowflake yang asli, dan jalur tulis di-cutover dari Snowflake ke ClickHouse untuk selamanya. Kedua lingkungan cloud sudah dibongkar — tidak ada layanan ClickHouse Cloud, tidak ada kontainer producer ClickHouse, dan, setelah teardown Bagian 1 juga dijalankan, tidak ada warehouse Snowflake juga.
Dua file bertahan melewati teardown dan merupakan segala yang dibutuhkan modul 06:
workshop_public/snowflake_migration_lab/02-plan-and-design/migration-plan.md dan
workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse/scripts/benchmark_results_<timestamp>.csv.
Modul 06 adalah asesmen tertulis open-book — bawa kedua file itu dan tidak ada yang lain.
04 Bangun ulang pipeline dbt
Bangun ulang pipeline Medallion di ClickHouse dengan dbt-clickhouse — model inkremental delete_insert, ReplacingMergeTree, materialized view yang refreshable — dan buat dictionary zona.
06 Evaluasi
Selesaikan asesmen 20 pertanyaan pilihan ganda dan 4 pertanyaan terbuka, open-book, untuk meraih ClickHouse Migration Proficiency Badge.