Rodando ClickHouse em produção: ingestão, merges e custo com 2 bilhões de linhas
Nossa experiência rodando ClickHouse em produção: design de schema, otimização de consultas e como alcançamos analytics em menos de um segundo sobre 100M+ eventos de dispositivos.
Quando começamos a construir a camada de analytics da tracio.ai, precisávamos de um banco de dados capaz de lidar com nossa carga de trabalho específica: ingerir 50.000 eventos de identificação de dispositivo por segundo, armazenar 2+ bilhões de linhas e responder a consultas analíticas em menos de um segundo. Avaliamos o PostgreSQL (lento demais para agregações nessa escala), o Elasticsearch (caro demais para analytics de séries temporais) e o ClickHouse. O ClickHouse venceu de forma decisiva.
Por que ClickHouse
O ClickHouse é um banco de dados OLAP orientado a colunas projetado para analytics em tempo real. Sua principal vantagem para nossa carga de trabalho é que ele lê apenas as colunas necessárias para cada consulta. Quando um analista de fraude pergunta "mostre a taxa de fraude por país nos últimos 7 dias", o ClickHouse lê apenas as colunas country, timestamp e risk_score — ignorando as outras 40+ colunas da tabela de eventos. Em uma tabela de 2 bilhões de linhas, isso reduz o I/O em 95%.
O ClickHouse também comprime dados extremamente bem. Nossa tabela de eventos de 2 bilhões de linhas ocupa 340 GB em disco — cerca de 170 bytes por linha comprimida, contra 1,2 KB por linha sem compressão. A taxa de compressão de 7:1 significa que mais dados cabem na memória, o que se traduz diretamente em consultas mais rápidas.
Design de schema
Nossa tabela primária armazena uma linha por evento de identificação:
A tabela usa o engine MergeTree, ordenado por (workspace_id, toDate(timestamp), visitor_hash). Essa ordenação é crítica — significa que consultas filtradas por workspace e intervalo de datas leem o mínimo de dados. A coluna visitor_hash permite buscas rápidas por ID de visitante sem um índice secundário.
Escolhemos LowCardinality(String) para country, device_type, browser_family e os_family porque essas colunas têm menos de 10.000 valores distintos. O ClickHouse armazena colunas LowCardinality como inteiros codificados por dicionário, reduzindo o armazenamento em 80% em comparação com strings simples e acelerando operações GROUP BY.
Estratégia de sharding
Fazemos o sharding da tabela de eventos em 6 nós usando um hash de workspace_id. Isso garante que todos os eventos de um determinado cliente fiquem no mesmo shard, o que significa que a maioria das consultas (filtradas por workspace_id) atinge um único shard. Consultas cross-shard só são necessárias para analytics internos.
Cada shard tem 2 réplicas para alta disponibilidade. A replicação usa o engine ReplicatedMergeTree nativo do ClickHouse com coordenação por ZooKeeper. O failover é automático — se um shard cai, as consultas são roteadas para a réplica sem mudanças no lado do cliente.
Pipeline de ingestão
Os eventos fluem do nosso tópico Kafka para o ClickHouse através de um serviço Go customizado que agrupa os inserts em lotes. Inserimos em lotes de 10.000 linhas a cada 500ms — isso equilibra a latência de ingestão (menos de um segundo) com a eficiência de insert (o ClickHouse tem melhor desempenho com lotes grandes).
O serviço de ingestão lida com back-pressure de forma elegante. Se o ClickHouse está lento para aceitar inserts (durante merges ou carga pesada de consultas), o serviço armazena em buffer até 1 milhão de eventos na memória e aplica back-pressure ao consumidor Kafka. Em 18 meses de produção, nunca perdemos um evento.
Otimização de consultas
Views materializadas
Para consultas comuns de dashboard, usamos views materializadas que pré-agregam os dados. Nosso dashboard de taxa de fraude, por exemplo, lê de uma view materializada que agrega contagens de fraud_detected por workspace, país e hora. A view reduz os dados varridos por essa consulta de 2 bilhões de linhas para 5 milhões de linhas.
Ordenação por projection
As projections do ClickHouse permitem definir ordenações alternativas para uma tabela sem duplicar os dados. Adicionamos uma projection ordenada por (workspace_id, visitor_hash, timestamp) para consultas de linha do tempo de visitante. Sem a projection, essas consultas varriam intervalos de datas inteiros. Com ela, leem apenas os blocos que contêm o visitante-alvo.
Funções aproximadas
Para consultas de dashboard em que contagens exatas não são críticas, usamos as funções aproximadas do ClickHouse: uniqCombined para contagens distintas (margem de erro de 2%, 10x mais rápida que uniqExact) e quantileTDigest para cálculos de percentil. O dashboard de analytics de fraude usa funções aproximadas exclusivamente, o que mantém todas as consultas de dashboard abaixo de 200ms.
Números de desempenho
Aqui estão benchmarks de consulta representativos no nosso cluster de produção de 2 bilhões de linhas:
Taxa de fraude por país, últimos 7 dias: 120ms. Linha do tempo de visitante (50 eventos): 8ms. Visitantes únicos por dia, últimos 30 dias: 340ms. Distribuição de risk score, últimas 24 horas: 95ms. Top 100 dispositivos por contagem de eventos, últimos 30 dias: 210ms.
Esses números incluem o round-trip de rede dos nossos servidores de aplicação até o cluster ClickHouse. O tempo puro de execução da consulta costuma ser 30-50% menor.
Lições operacionais
Lição 1: monitore o atraso de merge
O engine MergeTree do ClickHouse mescla continuamente pequenos data parts em partes maiores. Se os merges ficam para trás (por alta taxa de insert ou contenção de I/O de disco), o desempenho das consultas degrada porque elas precisam varrer mais parts. Monitoramos a contagem de parts por partição e alertamos quando ela ultrapassa 300.
Lição 2: evite grandes operações ALTER TABLE
Adicionar uma coluna a uma tabela de 2 bilhões de linhas no ClickHouse é instantâneo (é apenas metadados). Mas mudar o tipo de uma coluna exige reescrever todos os data parts — um processo que levou 6 horas no nosso cluster. Agora tratamos o schema como append-only: novas colunas são adicionadas livremente, mas mudanças de tipo passam por uma tabela de migração.
Lição 3: TTL com cautela
O ClickHouse suporta expiração automática de dados via TTL. Definimos um TTL de 90 dias na nossa tabela de eventos. A pegadinha: a deleção por TTL acontece durante os merges, o que significa que dados deletados podem persistir por horas ou dias após a expiração do TTL. Para deleção crítica de conformidade, executamos consultas explícitas ALTER TABLE DELETE de forma agendada.
Custo
Nosso cluster ClickHouse de 6 nós (cada nó: 32 vCPU, 128 GB de RAM, 2 TB de NVMe) custa aproximadamente $8.400/mês em hospedagem bare metal. Isso armazena 2 bilhões de linhas com retenção de 90 dias e lida com 50 mil inserts/segundo mais 200 consultas de dashboard concorrentes. O custo por evento armazenado é de $0.0000042 — ordens de magnitude mais barato do que analytics comparáveis em bancos de dados gerenciados na nuvem.