Snowflake MigrationClickHouse Workshops

Snowflake versus ClickHouse

As diferenças entre os dois mecanismos em armazenamento, computação e dialeto SQL — e quais expressões idiomáticas do Snowflake não têm equivalente direto no ClickHouse.

Este documento é uma referência para parceiros que migram do Snowflake para o ClickHouse. Ele aborda as diferenças de arquitetura que orientam as decisões de projeto e as seis lacunas do dialeto SQL que você encontrará na carga de trabalho NYC Taxi.


1. Comparação das arquiteturas

Armazenamento

O Snowflake toma todas as decisões de armazenamento físico por você. Os dados são armazenados como micropartições colunares compactadas em armazenamento de objetos na nuvem. Você escolhe o tamanho do warehouse e a estrutura da tabela; o Snowflake cuida de todo o restante — clusterização, compactação e gerenciamento de arquivos são automáticos.

O ClickHouse exige que você tome explicitamente as decisões de armazenamento físico. Ao criar uma tabela, você especifica:

  • O mecanismo (que determina como os dados são armazenados, mesclados e desduplicados)
  • A ORDER BY (que se torna a ordem física de classificação e o índice primário)
  • Opcionalmente: PARTITION BY, TTL, SETTINGS (codec de compactação e comportamento das mesclagens)

Essas são decisões relacionadas à exatidão, não simples controles de ajuste de desempenho. O mecanismo errado pode produzir resultados de consulta silenciosamente incorretos. Uma ORDER BY errada pode fazer consultas que deveriam ser rápidas varrerem a tabela inteira.

Execução de consultas

O Snowflake usa MPP sem compartilhamento com warehouses virtuais. Um warehouse é um cluster de nós de computação que processa consultas. Você paga pelo warehouse enquanto ele está em execução — o tempo ocioso consome créditos. A suspensão automática ajuda, mas a inicialização a frio acrescenta latência.

O ClickHouse usa execução vetorizada. O ClickHouse Cloud escala automaticamente cada serviço de computação de forma independente e o reduz a zero quando fica ocioso. Vários serviços de computação podem compartilhar o mesmo armazenamento (por meio do SharedMergeTree) — esse é o modelo de separação entre computação e computação do ClickHouse Cloud, no qual cada serviço é uma camada de computação independente sobre uma camada comum de dados.

Modelo de simultaneidade

O Snowflake isola cargas de trabalho criando warehouses separados. O ETL usa TRANSFORM_WH; o analytics usa ANALYTICS_WH. Cada warehouse tem computação dedicada, portanto uma tarefa lenta de ETL não consegue privar uma consulta analítica de recursos.

O ClickHouse Cloud admite o mesmo padrão por meio da separação entre computação e computação: você pode provisionar vários serviços de computação que compartilham o mesmo armazenamento. Cada serviço é uma camada de computação independente e com escala automática — o ETL é executado em um serviço e o analytics interativo em outro, sem disputa por recursos entre eles. Dentro de um único serviço, as cargas são isoladas por meio de cotas flexíveis (max_threads, priority e max_memory_usage por usuário ou por consulta) e perfis de usuário com limites de recursos. Para a maioria das cargas de trabalho analíticas, cujas consultas terminam em milissegundos, um único serviço é suficiente, e as cotas por consulta são a opção mais leve.

Modelo de custos

SnowflakeClickHouse Cloud
ComputaçãoCréditos (segundos de warehouse)Unidades de computação (separadas do armazenamento)
ArmazenamentoUS$ 23/TB/mês~US$ 0,023/GB/mês (mais barato)
Redução a zeroApenas suspensão automáticaSuporte à redução total a zero
Transferência de dadosEntrada gratuita; saída cobradaTarifas padrão de saída da nuvem

A diferença mais significativa: no Snowflake, você paga pelo tempo do warehouse, haja ou não consultas em execução. No ClickHouse Cloud, a computação é reduzida a zero entre as consultas. Para cargas de trabalho analíticas em rajadas, o ClickHouse Cloud costuma ser de 3 a 8 vezes mais barato que uma configuração equivalente do Snowflake.


2. Lacunas do dialeto SQL

A carga de trabalho NYC Taxi contém seis construções que precisam ser traduzidas. Todas elas aparecem de Q1 a Q7 em 01-setup-snowflake/queries/.

Lacuna 1: QUALIFY

QUALIFY é uma extensão do Snowflake que filtra linhas pelo resultado de uma função de janela, de modo semelhante a HAVING, que filtra pelo resultado de uma agregação. Nesta migração, tratamos QUALIFY como uma lacuna de dialeto e o reescrevemos usando uma subconsulta — esse é o padrão universalmente portável que funciona em todos os mecanismos SQL.

-- Snowflake
SELECT
    trip_id,
    pickup_at,
    fare_amount,
    ROW_NUMBER() OVER (PARTITION BY pickup_location_id ORDER BY fare_amount DESC) AS fare_rank
FROM fact_trips
WHERE pickup_at >= CURRENT_DATE - 7
QUALIFY fare_rank <= 10;

-- ClickHouse: wrap in a subquery
SELECT trip_id, pickup_at, fare_amount, fare_rank
FROM (
    SELECT
        trip_id,
        pickup_at,
        fare_amount,
        ROW_NUMBER() OVER (PARTITION BY pickup_location_id ORDER BY fare_amount DESC) AS fare_rank
    FROM analytics.fact_trips
    WHERE pickup_at >= today() - 7
)
WHERE fare_rank <= 10;

Por que isso importa: QUALIFY aparece em Q3. A reescrita como subconsulta é o padrão seguro e portável — ela funciona independentemente do mecanismo SQL de destino e torna explícito o resultado da função de janela. O perigo de qualquer sintaxe específica do Snowflake é pressupor que ela será transferida sem problemas; sempre teste todas as consultas antes de declarar a migração concluída.

Lacuna 2: sintaxe de caminho com dois-pontos em VARIANT

O tipo VARIANT do Snowflake usa a notação de caminho com dois-pontos para acessar campos aninhados: column:field.subfield::TYPE. O ClickHouse armazena dados semiestruturados como String e os extrai no momento da consulta com funções JSONExtract*.

-- Snowflake
SELECT
    trip_metadata:driver.rating::FLOAT  AS driver_rating,
    trip_metadata:app.version::STRING   AS app_version,
    trip_metadata:surge_multiplier::FLOAT AS surge
FROM trips_raw;

-- ClickHouse
SELECT
    JSONExtractFloat(trip_metadata, 'driver', 'rating')   AS driver_rating,
    JSONExtractString(trip_metadata, 'app', 'version')    AS app_version,
    JSONExtractFloat(trip_metadata, 'surge_multiplier')   AS surge
FROM default.trips_raw;

A família completa de JSONExtract* inclui: JSONExtractFloat, JSONExtractInt, JSONExtractString, JSONExtractBool, JSONExtractKeys, JSONExtractArrayRaw e JSONExtractRaw. Use JSONExtractRaw quando precisar de um objeto ou array aninhado como string para processamento posterior.

Por que não usar o tipo JSON do ClickHouse? O tipo JSON (antes experimental) está disponível em versões recentes do ClickHouse, mas tem uma semântica diferente e ainda não está consolidado para produção em todos os casos de uso. Para um laboratório de migração, String + JSONExtract* é a opção segura e bem compreendida.

Lacuna 3: LATERAL FLATTEN

LATERAL FLATTEN do Snowflake transforma um array dentro de uma coluna VARIANT em linhas. O ClickHouse não possui um equivalente direto.

-- Snowflake: explode a VARIANT array into rows
SELECT t.trip_id, f.value:stop_name::STRING AS stop_name
FROM trips_raw t,
LATERAL FLATTEN(input => t.trip_metadata:route_stops) f;

-- ClickHouse Option 1: JSONExtract into Array, then arrayJoin
SELECT
    trip_id,
    arrayJoin(JSONExtract(trip_metadata, 'route_stops', 'Array(String)')) AS stop_name
FROM default.trips_raw;

-- ClickHouse Option 2: Pre-flatten the column during dbt staging
-- In stg_trips.sql, extract all array elements to separate columns
-- or use the dbt model to reshape the data at load time

A abordagem de desaninhar antecipadamente (Opção 2) é preferível quando o array tem um esquema delimitado e conhecido. arrayJoin (Opção 1) é preferível para consultas ad hoc ou quando o tamanho do array varia.

Lacuna 4: MERGE INTO

MERGE INTO do Snowflake é o principal mecanismo de upsert. O ClickHouse não possui uma instrução MERGE. O equivalente correto no ClickHouse depende do mecanismo da tabela.

-- Snowflake
MERGE INTO fact_trips t
USING staging_trips s ON t.trip_id = s.trip_id
WHEN MATCHED THEN UPDATE SET t.fare_amount = s.fare_amount, t.updated_at = s.updated_at
WHEN NOT MATCHED THEN INSERT VALUES (s.trip_id, s.pickup_at, ...);

-- ClickHouse with ReplacingMergeTree: just INSERT
-- RMT deduplicates by the ORDER BY key during background merges.
-- Use FINAL at query time to get the latest version:
INSERT INTO analytics.fact_trips SELECT * FROM staging_trips;

SELECT * FROM analytics.fact_trips FINAL WHERE trip_id = '...';

-- ClickHouse with dbt delete_insert incremental:
-- dbt handles the upsert by: DELETE WHERE key IN (new batch), then INSERT
-- This is the recommended approach for the analytics layer

A estratégia incremental delete_insert do dbt-clickhouse é o equivalente semântico mais próximo de MERGE INTO para modelos analíticos. Ela exclui as linhas existentes que correspondem a qualquer chave no lote recebido e, em seguida, insere todas as linhas recebidas — de forma atômica por partição.

Principal armadilha do ReplacingMergeTree: a desduplicação em segundo plano é assíncrona. Entre as mesclagens, as versões antiga e nova de uma linha existem na tabela. Sempre use FINAL nas consultas que precisam retornar exatamente uma linha por chave. Consulte Mecanismos MergeTree para conhecer toda a semântica de desduplicação.

Lacuna 5: Streams do Snowflake (CDC)

Os Streams do Snowflake rastreiam as alterações em nível de linha (INSERT, UPDATE e DELETE) de uma tabela. Eles expõem as colunas de sistema METADATA$ACTION, METADATA$ISUPDATE e METADATA$ROW_ID. O ClickHouse não possui um mecanismo interno equivalente.

Equivalente no ClickHouse: virada direta do produtor

O ClickHouse não possui um mecanismo interno de CDC equivalente aos Streams do Snowflake. Nesta migração, o padrão é mais simples que um conector de CDC:

  • Primeiro, faça a carga em massa — scripts/02_migrate_trips.py lê todas as linhas históricas do Snowflake em lotes e as insere no ClickHouse
  • Depois, faça a virada do produtor — scripts/03_cutover.sh interrompe o produtor do Snowflake e inicia um produtor do ClickHouse, que grava diretamente no ClickHouse Cloud
  • Nenhuma janela de CDC é necessária — o script de migração cuida da carga histórica e o produtor assume as gravações ativas; ReplacingMergeTree(_synced_at) em trips_raw torna idempotentes quaisquer repetições da migração ou do produtor

Após a virada, a estratégia delete_insert do dbt trata os upserts na camada de analytics. Os Streams e Tasks do Snowflake são totalmente desativados.

Lacuna 6: funções de data e hora

O Snowflake e o ClickHouse têm nomes diferentes para funções de data. A maioria das substituições é mecânica.

SnowflakeClickHouseObservações
DATE_TRUNC('hour', ts)toStartOfHour(ts)Também: toStartOfDay, toStartOfMonth, toStartOfWeek
DATE_TRUNC('day', ts)toDate(ts)
DATEADD('day', n, ts)ts + INTERVAL n DAYOu addDays(ts, n)
DATEDIFF('minute', t1, t2)dateDiff('minute', t1, t2)Nome da função em minúsculas
CURRENT_DATEtoday()
CURRENT_TIMESTAMP()now()
TO_TIMESTAMP(epoch, 9)fromUnixTimestamp64Nano(epoch)Unidades explícitas no CH
YEAR(ts)toYear(ts)
MONTH(ts)toMonth(ts)
EXTRACT(epoch FROM ts)toUnixTimestamp(ts)

DateTime versus DateTime64: DateTime do ClickHouse tem precisão de segundos. Use DateTime64(3, 'UTC') para obter precisão de milissegundos (correspondente a TIMESTAMP_NTZ do Snowflake). O 3 é a escala inferior a um segundo; 'UTC' é o fuso horário.


3. Opções para movimentação de dados

MétodoQuando usarObservações
Script de migração em Python (scripts/02_migrate_trips.py)Carga em massa do Snowflake → ClickHouseConexão direta por snowflake-connector-python + clickhouse-connect; retomável; não exige serviços adicionais — usado neste laboratório
ClickPipesKafka, S3, Kinesis, CDC do PostgreSQL, CDC do MySQLConector gerenciado; não aceita o Snowflake como origem
remoteSecure()Carga ad hoc a partir de outro serviço do ClickHouseNão se aplica a uma origem no Snowflake
Intermediação por armazenamento de objetosGrandes cargas pontuaisExporte do Snowflake → S3 → função de tabela S3 do ClickHouse; exige conta da AWS e configuração de IAM
JDBC/ODBCPipelines de ETL personalizadosFlexível, mas exige orquestração personalizada

Para este laboratório, o script de migração em Python é a escolha correta: não exige serviços adicionais de nuvem (nem S3 nem Kafka), permite depuração completa e usa pacotes (snowflake-connector-python, clickhouse-connect) que os parceiros já instalaram para outras etapas do laboratório.


4. Comparação das arquiteturas de CDC

Streams + Tasks do SnowflakeClickHouse (neste laboratório)
Rastreamento de alteraçõesObjeto stream interno na tabela (TRIPS_CDC_STREAM)Sem equivalente — após a virada, o produtor grava diretamente no ClickHouse
Eventos de alteraçãoMETADATA$ACTION: INSERT/UPDATE/DELETEINSERT direto do produtor do ClickHouse
LatênciaCronograma de tarefa configurável (mín. de 1 min)Intervalo de lote configurável (padrão de 10 s)
ConsumoUma tarefa SQL lê o stream e envia ao destinoProdutor Python (producer/producer.py)
Alterações de esquemaCoordenação manualO código do produtor controla o esquema

Após a migração, o produtor grava diretamente no ClickHouse — não são necessários Streams nem Tasks. A estratégia delete_insert do dbt trata os upserts na camada de analytics. A agregação periódica (Tasks do Snowflake) tem um substituto nativo do ClickHouse nas views materializadas atualizáveis — o projeto dbt deste laboratório inclui uma delas, analytics.mv_live_trip_feed, embora o laboratório não ative seu intervalo de atualização (consulte o módulo 05).


5. Análise detalhada do modelo de custos

Snowflake: baseado em créditos

Um crédito do Snowflake custa cerca de US$ 3 (Enterprise). Custo = tamanho_do_warehouse × tempo_em_execução. Um warehouse SMALL consome 1 crédito/hora; um MEDIUM consome 2. A suspensão automática mínima de 60 segundos significa que até uma única consulta custa ao menos 1/60 de hora.

Para o laboratório NYC Taxi (warehouse X-Small, 1 crédito/h):

  • Preparação da Parte 1: ~2–4 créditos (~US$ 6–12)
  • Uso contínuo por sessão de 8 horas: ~4–8 créditos/dia (~US$ 12–24)
  • O monitor de recursos de ANALYTICS_WH limita o uso a 50 créditos/mês (~US$ 150)

ClickHouse Cloud: computação e armazenamento separados

O ClickHouse Cloud cobra separadamente por computação e armazenamento:

  • Computação: o nível Development custa ~US$ 0,10/h quando está ativo e é reduzido a zero quando ocioso
  • Armazenamento: ~US$ 0,023/GB/mês (significativamente mais barato que os US$ 23/TB do Snowflake)
  • ClickPipes: incluído na assinatura do Cloud para origens compatíveis (Kafka, S3, Kinesis, CDC do PostgreSQL, CDC do MySQL — não o Snowflake)

Para o laboratório NYC Taxi:

  • 50 milhões de linhas × ~300 bytes/linha sem compactação = ~15 GB → ~8 GB compactados no ClickHouse
  • Custo de armazenamento: ~US$ 0,18/mês
  • Computação durante a Parte 3 ativa do laboratório (~2 h): ~US$ 0,20–0,40

Custo total da Parte 3: ~US$ 2–4, em comparação com ~US$ 6–12 no Snowflake para a mesma sessão.

A diferença de custo explica por que muitas organizações começam com o Snowflake (operações mais simples) e migram para o ClickHouse (custo menor e desempenho maior) à medida que suas cargas de trabalho analíticas crescem.

Nesta página

PT