05 Benchmark và cutover
Dựng lại các dashboard trên ClickHouse, benchmark cả bảy truy vấn trên cả hai engine, cutover producer, kiểm chứng tính tương đương, và dỡ bỏ.
Điểm khởi đầu
Module 04 đã hoàn tất: tầng analytics đã có dữ liệu và đã được test. analytics.fact_trips
chứa khoảng 50 triệu dòng; analytics.dim_taxi_zones, analytics.dim_payment_type,
analytics.dim_vendor và analytics.dim_date đã được nạp đầy đủ; dbt test pass từ đầu đến
cuối; và analytics.taxi_zones_dict đang hoạt động và trả về borough qua dictGet().
analytics.agg_hourly_zone_trips vẫn còn rỗng — theo thiết kế, không phải do lỗi — và giữ
nguyên như vậy cho đến bước cutover của module này. Producer Snowflake vẫn đang chạy, và
khoảng trống giữa Snowflake và ClickHouse vẫn còn mở. Hãy dự trù khoảng 45 phút.
Vì sao
Mọi module tính đến lúc này đều là chuẩn bị. Module 03 chứng minh ClickHouse có thể chứa 50 triệu dòng; module 04 chứng minh pipeline dbt chạy được trên nó. Bản thân từng cái, không cái nào là thứ một đối tác sẽ ký xác nhận là "đã di chuyển" — điều đó cần thêm hai thứ nữa: một con số, và một lần cutover.
Con số là bài benchmark ở Bước 2: cùng bảy truy vấn từ kế hoạch di chuyển của bạn, chạy liên tiếp trên Snowflake rồi trên ClickHouse, lấy trung vị của ba lượt cho mỗi bên. Đó là thứ biến "ClickHouse chắc là nhanh hơn" thành một mức tăng tốc cụ thể, có thể bảo vệ được, mà một đối tác có thể đặt trước mặt các bên liên quan của chính họ.
Bước cutover ở Bước 3 là nửa còn lại. Đến giờ mọi module đều giữ hai hệ thống chạy song song, Snowflake là hệ thống nguồn sự thật và ClickHouse chạy theo phía sau. Một cuộc di chuyển mà không bao giờ thực sự chuyển đường ghi thì là một bản copy, không phải một cuộc di chuyển. Bước 3 dừng producer Snowflake, đóng khoảng trống mà các lượt ghi liên tục của nó đã giữ mở từ module 01, và bắt đầu ghi các chuyến đi mới vào ClickHouse thay thế — khoảnh khắc ClickHouse trở thành hệ thống nguồn sự thật.
Đây cũng là module học viên cuối cùng trước bài đánh giá viết ở module 06, vốn được phép tra tài liệu dựa trên bất cứ thứ gì bạn tạo ra ở đây. Các dashboard, file CSV benchmark và phép kiểm tra tính tương đương đều cần là thật trước khi bạn dỡ bỏ bất cứ thứ gì ở Bước 5.
Khái niệm — bên dưới lớp vỏ
Tầng BI, về mặt cơ học. bash superset/add_clickhouse_connection.sh gọi trực tiếp Superset
REST API ở Bước 1 — không có bước bấm chuột thủ công nào trong UI của Superset. Nó đăng ký kết
nối ClickHouse, rồi import bản export dashboard đã commit, thêm bốn dashboard ClickHouse bên
cạnh ba dashboard Snowflake mà module 01 đã dựng (tổng cộng bảy):
| Dashboard | Đối ứng với | Nó minh họa điều gì |
|---|---|---|
| CH — Operations Command Center | Snowflake Dashboard 1 | Dữ liệu fact_trips trực tiếp (sau cutover); cùng KPI, truy vấn nhanh hơn |
| CH — Executive Weekly Report | Snowflake Dashboard 2 | QUALIFY được viết lại thành một subquery ROW_NUMBER() |
| CH — Driver & Quality Analytics | Snowflake Dashboard 3 | JSONExtractString thay cho LATERAL FLATTEN của Snowflake |
| CH — Capabilities Showcase | (mới — không có tương đương ở Snowflake) | Các hàm xấp xỉ, join qua dictionary, mệnh đề SAMPLE |
Bảy truy vấn benchmark rèn những gì. Bước 2 chạy cùng bảy truy vấn từ kế hoạch di chuyển của bạn trên cả hai engine và so sánh thời gian thực tế. Mỗi truy vấn nhắm vào một khoảng cách phương ngữ hoặc một tính năng engine cụ thể từ kế hoạch của module 02:
| Truy vấn | Rèn điều gì |
|---|---|
| Q1 | Doanh thu theo giờ theo borough |
| Q2 | Khoảng cách trung bình cuốn 7 ngày |
| Q3 | Top 10 chuyến đi — QUALIFY của Snowflake so với subquery ROW_NUMBER() của ClickHouse |
| Q4 | Đánh giá tài xế — LATERAL FLATTEN của Snowflake so với JSONExtractString của ClickHouse |
| Q5 | Giá tăng cao điểm — VARIANT của Snowflake so với String + JSONExtract* của ClickHouse |
| Q6 | Tổng hợp theo giờ — MERGE của Snowflake so với ReplacingMergeTree của ClickHouse |
| Q7 | Độ tươi của dữ liệu CDC / dữ liệu trực tiếp |
Khoảng trống khi cutover. Producer Snowflake đã ghi khoảng 60 chuyến mỗi phút từ module
01, và nó chưa bao giờ dừng. Script di chuyển của module 03 chụp lại TRIPS_RAW đúng như lúc
script đó chạy, và module 04 dựng pipeline dbt trên bản chụp đó. Mọi chuyến đi được ghi vào
Snowflake sau lô cuối cùng của script di chuyển chỉ tồn tại trong Snowflake — phần đuôi của
ClickHouse đang thiếu. Lượt bắt kịp --resume ở Bước 3 đóng đúng khoảng trống đó: nó đọc
max(pickup_at) đã có trong ClickHouse và chỉ lấy các dòng được ghi sau đó, nên một lượt đóng
khoảng trống mở từ module 01 chỉ mất vài giây đến vài phút, chứ không phải 40-50 phút như lượt
di chuyển khối lượng lớn ban đầu. Bỏ qua nó mà vẫn cutover, thì ClickHouse sẽ mất vĩnh viễn số
chuyến đi rơi vào khoảng trống đó — một lỗi tương đương âm thầm mà Bước 4 được dựng ra để bắt,
nhưng chỉ khi Bước 3 đã chạy theo đúng thứ tự.
agg_hourly_zone_trips được nạp dữ liệu ở đây, và chỉ ở đây. Nó đã rỗng từ module 03 theo
đúng thiết kế: filter incremental của nó là WHERE pickup_at >= now() - INTERVAL 2 HOUR, chỉ
khớp các dòng do một producer chạy trực tiếp ghi vào, và cho đến module này thì producer duy
nhất đang ghi là của Snowflake. Khi Bước 3 khởi động producer ClickHouse, các dòng mới cuối
cùng cũng rơi vào cửa sổ hai giờ đó, và bảng này — cùng mọi chart dashboard dựa trên nó — lần
đầu tiên trong lab không còn trống.
Bước 1 — Thêm các dashboard ClickHouse
Để dựng thủ công đầy đủ — tạo từng cái trong 7 dataset, 18 chart và 4 dashboard theo từng bước trong UI của Superset — hãy làm theo Superset trên ClickHouse. Còn nếu muốn bỏ qua các bước thủ công và import mọi thứ trong một lượt:
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.shFile superset/dashboards/dashboard_export_*.zip đã commit có host ClickHouse được ẩn thành
your-instance.clickhouse.cloud. Cách rút gọn ở trên vá URI từ .env trước khi import, nên
chuyện này diễn ra trong suốt — bạn sẽ không nhận ra phần bị ẩn. Nhưng nếu bạn tự import file
ZIP thủ công qua UI của Superset, kết nối database mà nó tạo ra sẽ không kết nối được — bạn
phải sửa kết nối đó sau đó để trỏ đến CLICKHOUSE_HOST và credential thật của bạn. Xem
Superset trên ClickHouse để biết
chính xác cách làm.
Kiểm chứng:
Mở http://localhost:8088 (admin / admin). Trong mục Dashboards, bạn sẽ thấy tổng cộng 7 —
3 dashboard Snowflake và 4 dashboard có tiền tố CH —.
Bước 2 — Chạy benchmark
Chạy cả bảy truy vấn liên tiếp trên cả Snowflake và ClickHouse rồi so sánh thời gian thực tế:
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse"
source .env && source .clickhouse_state
./scripts/run_benchmark.shOutput mong đợi:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
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
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━Các con số này là của một lượt chạy tiêu biểu, không phải một cam kết — số của bạn sẽ khác tùy theo kích thước warehouse, tier ClickHouse Cloud, và bất cứ thứ gì khác đang chạy trên một trong hai service lúc đó.
Script ghi mọi lượt chạy vào
workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse/scripts/benchmark_results_<timestamp>.csv.
Hãy giữ nó trên đĩa — nó là một trong hai file mà bài đánh giá của module 06 cần, nên đừng xóa
nó trong lúc dỡ bỏ ở Bước 5.
Bước 3 — Cutover sang ClickHouse
Khoảng trống di chuyển. Producer Snowflake đã chạy suốt lab này, ghi khoảng 60 chuyến mỗi
phút. Script di chuyển của module 03 chụp lại TRIPS_RAW đúng như lúc script đó chạy — các
dòng được ghi từ đó trở đi chỉ tồn tại trong Snowflake. Hãy đóng khoảng trống đó trước khi
chuyển đường ghi sang.
Hãy chạy cả bốn bước dưới đây, theo thứ tự — chính thứ tự là thứ giữ cho hai hệ thống nhất quán:
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.shĐừng bỏ qua Bước 2. --resume đọc max(pickup_at) đã có trong ClickHouse và thêm một
filter WHERE PICKUP_DATETIME > <watermark> vào truy vấn Snowflake, nên nó chỉ chuyển các dòng
được ghi trong và sau lượt di chuyển gốc của module 03 — những dòng ClickHouse chưa bao giờ
thấy. Cutover mà không có nó thì ClickHouse mất vĩnh viễn số chuyến đi rơi vào cửa sổ đó. Phép
kiểm tra tính tương đương ở Bước 4 được dựng ra để bắt đúng điều đó, nhưng chỉ khi bước này đã
chạy trước.
./scripts/03_cutover.sh sẽ hỏi Type "cutover" to confirm, rồi lặp lại các bước 1-3 như lưới
an toàn của riêng nó: nó dừng producer Snowflake lần nữa (không làm gì nếu bạn đã dừng), chạy
thêm một lượt dbt run, rồi build và khởi động producer ClickHouse
(nyc_taxi_ch_producer). Ba mươi giây sau khi producer khởi động, nó xác nhận các dòng mới
đang đến default.trips_raw và chạy dbt thêm một lần — lượt chạy cuối cùng cũng cho
agg_hourly_zone_trips những dòng đầu tiên, đóng lại khoảng trống mà module 04 đã cố tình để
mở.
Kiểm chứng:
-- 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 runningGiữ tầng analytics luôn tươi. fact_trips và agg_hourly_zone_trips là các model
incremental của dbt — chúng không tự refresh. 03_cutover.sh chạy dbt run một lần sau khi
xác nhận producer đang hoạt động, nhưng các dashboard sẽ trôi dần thành cũ khi các chuyến đi
mới tích tụ; hãy chạy lại dbt run từ dbt/nyc_taxi_dbt_ch bất cứ khi nào bạn muốn số liệu
hiện thời (ở production bạn sẽ lên lịch việc này — cron, Airflow, dbt Cloud — nhưng chạy theo
yêu cầu là ổn cho lab). Ngược lại, analytics.mv_live_trip_feed là một materialized view có
thể refresh — lệnh dbt run của module 04 đã dựng nó với engine = 'ReplacingMergeTree(refreshed_at)' — nhưng lab không bao giờ bật chu kỳ refresh của nó: câu
lệnh MODIFY REFRESH EVERY 30 SECOND vốn sẽ làm nó tự chạy lại chỉ tồn tại dưới dạng một
comment trong file model. Bật nó lên là một lệnh ALTER TABLE duy nhất mà bạn sẽ tự chạy; nếu
không có nó, mv_live_trip_feed chỉ cập nhật đúng một lần lúc dbt dựng nó.
Cutover ngược lại, nếu bạn cần hoàn tác bước này và quay về 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 producerBước 4 — Kiểm chứng tính tương đương
Giờ khi lượt bắt kịp --resume đã chạy và producer ClickHouse đang hoạt động, hai hệ thống lẽ
ra đã tương đương. Hãy xác nhận điều đó:
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 mong đợi:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
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
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━Số dòng của riêng bạn sẽ khác; điều quan trọng là dòng về tính tương đương. Producer Snowflake
đã dừng ở thời điểm này, nên không có dòng mới nào đến đó — số dòng phải khớp chính xác, hoặc
lệch vài dòng nếu một lô còn đang trên đường trong lúc lượt --resume chạy, vẫn nằm sâu trong
ngưỡng 0.01% mà script kiểm tra.
Nếu phép kiểm tra tính tương đương thất bại (lệch hơn 0.01%), khoảng trống chưa được đóng hoàn toàn — hãy chạy lại lượt bắt kịp và kiểm tra lại:
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.shBước 5 — Dỡ bỏ
Trước khi dỡ bỏ bất cứ thứ gì, hãy xác nhận cuộc di chuyển đang ở trạng thái cuối cùng đúng đắn:
| Phép kiểm tra | Lệnh | Mong đợi |
|---|---|---|
| Tương đương số dòng | bash scripts/01_verify_migration.sh | Khớp số dòng ≥ 99.9% |
| Các test dbt | dbt test (từ dbt/nyc_taxi_dbt_ch) | Mọi test đều pass |
| Dashboard Superset | Mở http://localhost:8088 | Thấy 7 dashboard (3 SF + 4 CH) |
| Kết quả benchmark | cat scripts/benchmark_results_<timestamp>.csv | Cả 7 truy vấn đều có giá trị tăng tốc |
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 testKhi cả bốn đều pass, hai file là tất cả những gì module 06 cần, và cả hai đều sống sót qua việc
dỡ bỏ: workshop_public/snowflake_migration_lab/02-plan-and-design/migration-plan.md và
workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse/scripts/benchmark_results_<timestamp>.csv.
Module 06 là một bài đánh giá trên giấy được phép tra tài liệu, khoảng 60 phút, và không cần
gì khác — không có lý do gì để giữ một service ClickHouse Cloud có phí chạy suốt thời gian làm
một bài thi viết. Hãy copy hoặc ghi lại nội dung của cả hai file vào chỗ nào bạn với tới
được, rồi dỡ bỏ mọi thứ:
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse"
source .env && ./teardown.shLệnh này phá hủy service ClickHouse Cloud (qua terraform destroy) và container producer
ClickHouse, nếu cutover đã được thực hiện.
Các tài nguyên Snowflake của Phần 1 không bị script này dỡ bỏ. Hãy dỡ bỏ phía Snowflake riêng:
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/01-setup-snowflake"
source .env && ./teardown.shCách kiểm tra bạn đã xong
Đến điểm này trong module, bạn phải đã xác nhận, theo thứ tự:
- Phép kiểm tra tính tương đương pass —
01_verify_migration.shở Bước 4 báo PASS với mức lệch số dòng dưới 0.01%. - File CSV benchmark trên đĩa — Bước 2 đã ghi
benchmark_results_<timestamp>.csvvới cả 7 truy vấn đều mang một giá trị tăng tốc, và bạn đã giữ nó trước khi dỡ bỏ ở Bước 5. - Có 7 dashboard — phép kiểm tra Superset ở Bước 1 cho thấy 3 dashboard Snowflake và 4
dashboard
CH —nằm cạnh nhau. - Producer ClickHouse đang ghi — khối kiểm chứng ở Bước 3 cho thấy
default.trips_rawđang tăng số dòng vànyc_taxi_ch_producerđang chạy, trước khi Bước 5 dừng nó để dỡ bỏ.
Nếu có điều nào trong số đó không đúng vào lúc ấy, hãy quay lại bước tương ứng thay vì chạy lại các phép kiểm tra này bây giờ — Bước 5 đã phá hủy service ClickHouse Cloud, và nếu cutover đã diễn ra thì cả container producer cũng đi cùng nó.
Trạng thái kết thúc
Cuộc di chuyển đã hoàn tất và được đo lường: 50 triệu dòng đã chuyển từ Snowflake sang ClickHouse và được kiểm chứng là tương đương, bảy truy vấn được benchmark đối đầu với ClickHouse nhanh hơn ở mọi truy vấn, tầng BI được dựng lại với 4 dashboard ClickHouse bên cạnh 3 dashboard Snowflake ban đầu, và đường ghi đã cutover từ Snowflake sang ClickHouse một cách dứt khoát. Cả hai môi trường cloud đã được dỡ bỏ — không còn service ClickHouse Cloud, không còn container producer ClickHouse, và khi phần dỡ bỏ của Phần 1 cũng đã chạy, cũng không còn warehouse Snowflake.
Hai file sống sót qua việc dỡ bỏ và là tất cả những gì module 06 cần:
workshop_public/snowflake_migration_lab/02-plan-and-design/migration-plan.md và
workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse/scripts/benchmark_results_<timestamp>.csv.
Module 06 là một bài đánh giá viết được phép tra tài liệu — hãy mang theo hai file đó và không
gì khác.
04 Dựng lại pipeline dbt
Dựng lại pipeline Medallion trên ClickHouse với dbt-clickhouse — các model incremental delete_insert, ReplacingMergeTree, materialized view có thể refresh — và tạo dictionary vùng.
06 Đánh giá
Hoàn thành bài đánh giá 20 câu trắc nghiệm và 4 câu tự luận, được phép tra tài liệu, để nhận ClickHouse Migration Proficiency Badge.