05 Benchmark y transición
Reconstruye paneles sobre ClickHouse, compara siete consultas, transfiere el productor, verifica paridad y desmonta.
Punto de partida
Módulo 04 completado: la capa de analytics está poblada y probada.
analytics.fact_trips contiene aproximadamente 50 millones de filas;
analytics.dim_taxi_zones, analytics.dim_payment_type, analytics.dim_vendor y
analytics.dim_date están completamente cargadas; dbt test pasa de principio a fin;
y analytics.taxi_zones_dict está activo y devuelve distritos mediante dictGet().
analytics.agg_hourly_zone_trips sigue vacía —por diseño, no por un defecto— y
permanecerá así hasta la transición de este módulo. El productor de Snowflake continúa
en ejecución y el desfase entre Snowflake y ClickHouse sigue abierto. Reserva unos
45 minutos.
Por qué
Todos los módulos anteriores han sido una preparación. El módulo 03 demostró que ClickHouse puede almacenar 50 millones de filas; el módulo 04, que la canalización dbt funciona sobre ellas. Ninguno de los dos, por sí solo, bastaría para que un participante aprobara la migración como terminada: todavía hacen falta dos cosas, una cifra y una transición.
La cifra es el benchmark del Paso 2: las mismas siete consultas de tu plan de migración, ejecutadas consecutivamente en Snowflake y ClickHouse, con la mediana de tres ejecuciones en cada motor. Esto convierte «ClickHouse debería ser más rápido» en una aceleración concreta y defendible que un participante puede presentar a sus propios responsables.
La transición del Paso 3 es la otra mitad. Hasta ahora, ambos sistemas han funcionado en paralelo: Snowflake como sistema de registro y ClickHouse recuperando el retraso. Una migración que nunca mueve realmente la ruta de escritura es una copia, no una migración. El Paso 3 detiene el productor de Snowflake, cierra el desfase que sus escrituras continuas mantienen abierto desde el módulo 01 y empieza a escribir los viajes nuevos en ClickHouse. Ese es el momento en que ClickHouse se convierte en el sistema de registro.
También es el último módulo del participante antes de la evaluación escrita del módulo 06, que es a libro abierto y se basa en lo que produzcas aquí. Los paneles, el CSV del benchmark y la comprobación de paridad deben ser reales antes de desmontar nada en el Paso 5.
Conceptos internos
Funcionamiento mecánico de la capa BI. En el Paso 1,
bash superset/add_clickhouse_connection.sh llama directamente a la API REST de
Superset; no hay que recorrer manualmente la interfaz. Registra la conexión de
ClickHouse y después importa la exportación de paneles incluida en el repositorio,
añadiendo cuatro paneles de ClickHouse junto a los tres de Snowflake que creó el módulo
01, siete en total:
| Panel | Refleja | Demuestra |
|---|---|---|
| CH — Operations Command Center | Panel Snowflake 1 | Datos en vivo de fact_trips después de la transición; mismos KPI, consultas más rápidas |
| CH — Executive Weekly Report | Panel Snowflake 2 | QUALIFY reescrito como subconsulta de ROW_NUMBER() |
| CH — Driver & Quality Analytics | Panel Snowflake 3 | JSONExtractString en lugar del LATERAL FLATTEN de Snowflake |
| CH — Capabilities Showcase | (nuevo, sin equivalente en Snowflake) | Funciones aproximadas, uniones mediante diccionario y cláusula SAMPLE |
Qué ejercitan las siete consultas del benchmark. El Paso 2 ejecuta las mismas siete consultas de tu plan de migración en ambos motores y compara el tiempo transcurrido. Cada una se dirige a una diferencia de dialecto o a una característica del motor que aparecía en el plan del módulo 02:
| Consulta | Ejercita |
|---|---|
| Q1 | Ingresos horarios por borough |
| Q2 | Media móvil de distancia a 7 días |
| Q3 | Los diez viajes principales: QUALIFY de Snowflake frente a una subconsulta ROW_NUMBER() de ClickHouse |
| Q4 | Valoraciones de conductores: LATERAL FLATTEN de Snowflake frente a JSONExtractString de ClickHouse |
| Q5 | Tarificación dinámica: VARIANT de Snowflake frente a String + JSONExtract* de ClickHouse |
| Q6 | Agregación horaria: MERGE de Snowflake frente a ReplacingMergeTree de ClickHouse |
| Q7 | CDC y frescura de los datos en vivo |
El desfase de la transición. El productor de Snowflake ha escrito aproximadamente
60 viajes por minuto desde el módulo 01 y nunca se ha detenido. El script de migración
del módulo 03 capturó TRIPS_RAW tal como estaba cuando se ejecutó, y el módulo 04
construyó la canalización dbt sobre esa instantánea. Cada viaje escrito en Snowflake
desde el último lote del script existe únicamente allí: falta el final de los datos en
ClickHouse. La pasada de recuperación --resume del Paso 3 cierra exactamente ese
desfase: lee el max(pickup_at) ya presente en ClickHouse y recupera solo las filas
escritas después. Así, cerrar un desfase abierto desde el módulo 01 tarda de segundos a
minutos, y no los 40–50 minutos de la migración masiva original. Si omites la pasada y
realizas la transición de todos modos, ClickHouse perderá definitivamente todos los
viajes que llegaron durante el desfase: un fallo silencioso de paridad que el Paso 4
está diseñado para detectar, pero solo si el Paso 3 se ejecuta en orden.
agg_hourly_zone_trips se llena aquí y solo aquí. Lleva vacía desde el módulo 03 por
diseño: su filtro incremental es WHERE pickup_at >= now() - INTERVAL 2 HOUR, que solo
coincide con filas escritas por un productor en vivo, y hasta este módulo el único
productor que escribía era el de Snowflake. Cuando el Paso 3 inicia el productor de
ClickHouse, por fin llegan filas nuevas dentro de esa ventana de dos horas, y tanto la
tabla como todos los gráficos que dependen de ella dejan de estar vacíos por primera vez
en el laboratorio.
Paso 1 — Añade los paneles ClickHouse
Para realizar la construcción manual completa —crear uno a uno los 7 conjuntos de datos, los 18 gráficos y los 4 paneles en la interfaz de Superset—, sigue Superset sobre ClickHouse. Si prefieres omitir esos pasos e importarlo todo de una vez:
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.shEn el archivo superset/dashboards/dashboard_export_*.zip incluido en el repositorio,
el host de ClickHouse se ha ocultado como your-instance.clickhouse.cloud. El atajo
anterior corrige la URI a partir de .env antes de importar, de modo que el cambio es
transparente y no notarás el valor oculto. Sin embargo, si importas el ZIP manualmente
desde la interfaz de Superset, la conexión de base de datos que se crea no funcionará:
tendrás que editarla después para que apunte al CLICKHOUSE_HOST real y utilice tus
credenciales. Superset sobre ClickHouse
explica el procedimiento exacto.
Verificación:
Abre http://localhost:8088 (admin / admin). En Dashboards debes ver 7 paneles en
total: 3 de Snowflake y 4 cuyo nombre comienza por CH —.
Paso 2 — Ejecuta el benchmark
Ejecuta consecutivamente las siete consultas en Snowflake y ClickHouse y compara el tiempo transcurrido:
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse"
source .env && source .clickhouse_state
./scripts/run_benchmark.shSalida esperada:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
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
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━Estas cifras corresponden a una ejecución representativa, no son una garantía. Tus resultados variarán en función del tamaño del warehouse, el nivel de ClickHouse Cloud y cualquier otra carga que se ejecute en cualquiera de los dos servicios.
El script escribe cada ejecución en
workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse/scripts/benchmark_results_<timestamp>.csv.
Consérvalo en disco: es uno de los dos archivos que necesita la evaluación del módulo
06, por lo que no debes eliminarlo durante el desmontaje del Paso 5.
Paso 3 — Transfiere a ClickHouse
Desfase de migración. El productor de Snowflake ha permanecido activo durante todo
el laboratorio y escribe aproximadamente 60 viajes por minuto. El script de migración
del módulo 03 capturó TRIPS_RAW tal como estaba en el momento de su ejecución; las
filas escritas desde entonces solo existen en Snowflake. Cierra ese desfase antes de
trasladar la ruta de escritura.
Ejecuta los cuatro pasos siguientes en este orden: ese orden es lo que mantiene la coherencia entre los dos sistemas.
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.shNo omitas el Paso 2. --resume lee el max(pickup_at) ya presente en ClickHouse y
añade a la consulta de Snowflake un filtro
WHERE PICKUP_DATETIME > <watermark>. De este modo, solo transfiere las filas escritas
durante y después de la migración original del módulo 03: aquellas que ClickHouse nunca
ha visto. Si realizas la transición sin esa pasada, ClickHouse perderá definitivamente
todos los viajes que llegaron durante la ventana. La comprobación de paridad del Paso 4
está diseñada para detectar precisamente ese problema, pero solo si este paso se ha
ejecutado antes.
./scripts/03_cutover.sh muestra la solicitud Type "cutover" to confirm y después
repite los Pasos 1–3 como medida de seguridad propia: vuelve a detener el productor de
Snowflake, una operación sin efecto si ya estaba detenido, ejecuta otro dbt run y
construye e inicia el productor de ClickHouse (nyc_taxi_ch_producer). Treinta segundos
después de iniciarlo, confirma que están llegando filas nuevas a default.trips_raw y
ejecuta dbt una vez más: esta es la ejecución que por fin introduce las primeras filas
en agg_hourly_zone_trips y cierra el desfase que el módulo 04 dejó abierto por diseño.
Verificación:
-- 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 runningMantener actualizada la capa de analytics. fact_trips y
agg_hourly_zone_trips son modelos incrementales de dbt: no se actualizan de forma
automática. 03_cutover.sh ejecuta dbt run una vez después de confirmar que el
productor está activo, pero los paneles quedan desactualizados a medida que se acumulan
nuevos viajes. Vuelve a ejecutar dbt run desde dbt/nyc_taxi_dbt_ch siempre que
necesites cifras actuales. En producción programarías esta ejecución con cron, Airflow o
dbt Cloud; para el laboratorio basta con hacerlo bajo demanda. En cambio,
analytics.mv_live_trip_feed es una vista materializada actualizable: el dbt run
del módulo 04 ya la construyó con engine = 'ReplacingMergeTree(refreshed_at)', pero el laboratorio nunca activa su intervalo de
actualización. La instrucción MODIFY REFRESH EVERY 30 SECOND que haría que se
reexecutara sola solo aparece como comentario en el archivo del modelo. Puedes activarla
tú mismo con una única instrucción ALTER TABLE; sin ella, mv_live_trip_feed solo se
actualiza cuando dbt la construye.
Transición inversa, si necesitas deshacer este paso y volver al productor de 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 producerPaso 4 — Verifica paridad
Ahora que se ha ejecutado la pasada de recuperación --resume y el productor de
ClickHouse está activo, ambos sistemas deberían estar en paridad. Confírmalo:
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.shSalida esperada:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
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
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━Tu propio número de filas será distinto; lo que importa es la línea de paridad. A estas
alturas, el productor de Snowflake está detenido y allí ya no llegan filas nuevas. Los
recuentos deberían coincidir exactamente o diferir en unas pocas filas si había un lote
en curso durante la pasada --resume, siempre dentro del umbral del 0,01 % que comprueba
el script.
Si falla la comprobación de paridad, con una diferencia superior al 0,01 %, el desfase no se cerró por completo. Ejecuta de nuevo la pasada de recuperación y vuelve a comprobar:
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.shPaso 5 — Desmonta
Antes de desmontar nada, confirma que la migración se encuentra en un estado final correcto:
| Control | Comando | Esperado |
|---|---|---|
| Paridad del número de filas | bash scripts/01_verify_migration.sh | Coincidencia del número de filas ≥ 99,9 % |
| Pruebas de dbt | dbt test (desde dbt/nyc_taxi_dbt_ch) | Todas las pruebas pasan |
| Paneles de Superset | Abre http://localhost:8088 | 7 paneles visibles (3 SF + 4 CH) |
| Resultados del benchmark | cat scripts/benchmark_results_<timestamp>.csv | Las 7 consultas tienen un valor de aceleración |
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 testCuando pasen las cuatro comprobaciones, dos archivos contienen todo lo que necesita el
módulo 06 y ambos sobreviven al desmontaje:
workshop_public/snowflake_migration_lab/02-plan-and-design/migration-plan.md y
workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse/scripts/benchmark_results_<timestamp>.csv.
El módulo 06 es una evaluación escrita a libro abierto de unos 60 minutos y no
necesita nada más. No hay motivo para mantener un servicio de ClickHouse Cloud de pago
durante un examen escrito. Copia o anota el contenido de ambos archivos en un lugar al
que puedas acceder y después desmonta todo:
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse"
source .env && ./teardown.shEsto destruye el servicio de ClickHouse Cloud mediante terraform destroy y el
contenedor del productor de viajes de ClickHouse, si ya se realizó la transición.
Este script no desmonta los recursos Snowflake de la primera parte. Desmonta el lado Snowflake por separado:
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/01-setup-snowflake"
source .env && ./teardown.shCómo verificar la finalización
En este punto del módulo debes haber confirmado, en este orden:
- Comprobación de paridad aprobada: el
01_verify_migration.shdel Paso 4 informó PASS con una diferencia de recuento inferior al 0,01 %. - CSV del benchmark en disco: el Paso 2 escribió
benchmark_results_<timestamp>.csvcon un valor de aceleración para las 7 consultas, y lo conservaste antes del desmontaje del Paso 5. - 7 paneles presentes: la comprobación de Superset del Paso 1 mostró juntos 3
paneles de Snowflake y 4 paneles
CH —. - Productor de ClickHouse escribiendo: el bloque de verificación del Paso 3 mostró
que
default.trips_rawseguía aumentando y quenyc_taxi_ch_producerestaba activo, antes de que el Paso 5 lo detuviera para el desmontaje.
Si alguna condición no se cumplía en ese momento, vuelve al paso correspondiente en vez de intentar repetir ahora estas comprobaciones: el Paso 5 ya ha destruido el servicio de ClickHouse Cloud y, si se realizó la transición, también el contenedor del productor.
Estado final
La migración está terminada y medida: se trasladaron 50 millones de filas de Snowflake a ClickHouse y se verificó su paridad; las siete consultas se compararon directamente y ClickHouse fue más rápido en todas; la capa BI se reconstruyó con 4 paneles de ClickHouse junto a los 3 paneles originales de Snowflake; y la ruta de escritura pasó definitivamente de Snowflake a ClickHouse. Ambos entornos cloud están desmontados: no queda ningún servicio de ClickHouse Cloud ni contenedor del productor de ClickHouse y, una vez ejecutado también el desmontaje de la primera parte, tampoco ningún warehouse de Snowflake.
Dos archivos sobreviven al desmontaje y contienen todo lo que necesita el módulo 06:
workshop_public/snowflake_migration_lab/02-plan-and-design/migration-plan.md y
workshop_public/snowflake_migration_lab/03-migrate-to-clickhouse/scripts/benchmark_results_<timestamp>.csv.
El módulo 06 es una evaluación escrita a libro abierto: lleva únicamente esos dos
archivos.
04 Reconstrucción de la canalización dbt
Reconstruye Medallion en ClickHouse con dbt-clickhouse —incrementales delete_insert, ReplacingMergeTree y vistas actualizables— y crea el diccionario de zonas.
06 Evaluación
Completa una evaluación abierta de 20 preguntas de opción múltiple y 4 abiertas para obtener la insignia ClickHouse Migration Proficiency.