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
| Snowflake | ClickHouse Cloud | |
|---|---|---|
| Computação | Créditos (segundos de warehouse) | Unidades de computação (separadas do armazenamento) |
| Armazenamento | US$ 23/TB/mês | ~US$ 0,023/GB/mês (mais barato) |
| Redução a zero | Apenas suspensão automática | Suporte à redução total a zero |
| Transferência de dados | Entrada gratuita; saída cobrada | Tarifas 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
JSONdo ClickHouse? O tipoJSON(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 timeA 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 layerA 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
FINALnas 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.pylê todas as linhas históricas do Snowflake em lotes e as insere no ClickHouse - Depois, faça a virada do produtor —
scripts/03_cutover.shinterrompe 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)emtrips_rawtorna 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.
| Snowflake | ClickHouse | Observaçõ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 DAY | Ou addDays(ts, n) |
DATEDIFF('minute', t1, t2) | dateDiff('minute', t1, t2) | Nome da função em minúsculas |
CURRENT_DATE | today() | |
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:
DateTimedo ClickHouse tem precisão de segundos. UseDateTime64(3, 'UTC')para obter precisão de milissegundos (correspondente aTIMESTAMP_NTZdo Snowflake). O3é a escala inferior a um segundo;'UTC'é o fuso horário.
3. Opções para movimentação de dados
| Método | Quando usar | Observações |
|---|---|---|
Script de migração em Python (scripts/02_migrate_trips.py) | Carga em massa do Snowflake → ClickHouse | Conexão direta por snowflake-connector-python + clickhouse-connect; retomável; não exige serviços adicionais — usado neste laboratório |
| ClickPipes | Kafka, S3, Kinesis, CDC do PostgreSQL, CDC do MySQL | Conector gerenciado; não aceita o Snowflake como origem |
remoteSecure() | Carga ad hoc a partir de outro serviço do ClickHouse | Não se aplica a uma origem no Snowflake |
| Intermediação por armazenamento de objetos | Grandes cargas pontuais | Exporte do Snowflake → S3 → função de tabela S3 do ClickHouse; exige conta da AWS e configuração de IAM |
| JDBC/ODBC | Pipelines de ETL personalizados | Flexí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 Snowflake | ClickHouse (neste laboratório) | |
|---|---|---|
| Rastreamento de alterações | Objeto stream interno na tabela (TRIPS_CDC_STREAM) | Sem equivalente — após a virada, o produtor grava diretamente no ClickHouse |
| Eventos de alteração | METADATA$ACTION: INSERT/UPDATE/DELETE | INSERT direto do produtor do ClickHouse |
| Latência | Cronograma de tarefa configurável (mín. de 1 min) | Intervalo de lote configurável (padrão de 10 s) |
| Consumo | Uma tarefa SQL lê o stream e envia ao destino | Produtor Python (producer/producer.py) |
| Alterações de esquema | Coordenação manual | O 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.