ClickHouse in produzione: ingestion, merge e costi a 2 miliardi di righe
La nostra esperienza con ClickHouse in produzione: progettazione dello schema, ottimizzazione delle query e come otteniamo analisi in meno di un secondo su oltre 100M di eventi dei dispositivi.
Quando abbiamo iniziato a costruire il livello analitico di tracio.ai, ci serviva un database in grado di gestire il nostro workload specifico: ingerire 50.000 eventi di identificazione dei dispositivi al secondo, memorizzare oltre 2 miliardi di righe e rispondere a query analitiche in meno di un secondo. Abbiamo valutato PostgreSQL (troppo lento per le aggregazioni a questa scala), Elasticsearch (troppo costoso per le analisi time-series) e ClickHouse. ClickHouse ha vinto nettamente.
Perché ClickHouse
ClickHouse è un database OLAP orientato alle colonne, progettato per le analisi in tempo reale. Il suo vantaggio chiave per il nostro workload è che legge solo le colonne necessarie per ciascuna query. Quando un analista antifrode chiede «mostrami il tasso di frode per paese negli ultimi 7 giorni», ClickHouse legge solo le colonne country, timestamp e risk_score, ignorando le altre oltre 40 colonne della tabella degli eventi. Su una tabella da 2 miliardi di righe, questo riduce l'I/O del 95%.
ClickHouse comprime anche i dati in modo estremamente efficace. La nostra tabella di eventi da 2 miliardi di righe occupa 340 GB su disco — circa 170 byte per riga compressa, contro 1.2 KB per riga non compressa. Il rapporto di compressione 7:1 significa che più dati entrano in memoria, il che si traduce direttamente in query più veloci.
Progettazione dello schema
La nostra tabella principale memorizza una riga per ogni evento di identificazione:
La tabella usa il motore MergeTree, ordinato per (workspace_id, toDate(timestamp), visitor_hash). Questo ordinamento è cruciale: significa che le query filtrate per workspace e intervallo di date leggono una quantità minima di dati. La colonna visitor_hash consente ricerche rapide per visitor ID senza un indice secondario.
Abbiamo scelto LowCardinality(String) per country, device_type, browser_family e os_family perché queste colonne hanno meno di 10.000 valori distinti. ClickHouse memorizza le colonne LowCardinality come interi codificati a dizionario, riducendo lo spazio di archiviazione dell'80% rispetto alle stringhe semplici e velocizzando le operazioni GROUP BY.
Strategia di sharding
Suddividiamo la tabella degli eventi (sharding) su 6 nodi usando un hash di workspace_id. Questo garantisce che tutti gli eventi di un determinato cliente si trovino sullo stesso shard, il che significa che la maggior parte delle query (filtrate per workspace_id) colpisce un singolo shard. Le query cross-shard servono solo per le analisi interne.
Ogni shard ha 2 repliche per l'alta disponibilità. La replica usa il motore ReplicatedMergeTree integrato in ClickHouse con il coordinamento di ZooKeeper. Il failover è automatico: se uno shard cade, le query vengono instradate alla replica senza modifiche lato client.
Pipeline di ingestion
Gli eventi passano dal nostro topic Kafka a ClickHouse attraverso un servizio Go personalizzato che raggruppa gli inserimenti in batch. Inseriamo in batch da 10.000 righe ogni 500ms: questo bilancia la latenza di ingestion (sotto il secondo) con l'efficienza degli inserimenti (ClickHouse rende al meglio con batch di grandi dimensioni).
Il servizio di ingestion gestisce la contropressione (back-pressure) in modo controllato. Se ClickHouse è lento ad accettare gli inserimenti (durante i merge o sotto un carico di query elevato), il servizio bufferizza fino a 1 milione di eventi in memoria e applica contropressione al consumer Kafka. In 18 mesi di produzione, non abbiamo mai perso un evento.
Ottimizzazione delle query
Viste materializzate
Per le query comuni delle dashboard usiamo viste materializzate che pre-aggregano i dati. La nostra dashboard del tasso di frode, per esempio, legge da una vista materializzata che aggrega i conteggi di fraud_detected per workspace, paese e ora. La vista riduce i dati scansionati per questa query da 2 miliardi di righe a 5 milioni di righe.
Ordinamento delle proiezioni
Le proiezioni di ClickHouse ci permettono di definire ordinamenti alternativi per una tabella senza duplicare i dati. Abbiamo aggiunto una proiezione ordinata per (workspace_id, visitor_hash, timestamp) per le query sulla timeline dei visitatori. Senza la proiezione, queste query scansionavano interi intervalli di date. Con essa, leggono solo i blocchi che contengono il visitatore di interesse.
Funzioni approssimate
Per le query delle dashboard in cui i conteggi esatti non sono critici, usiamo le funzioni approssimate di ClickHouse: uniqCombined per i conteggi distinti (margine di errore del 2%, 10 volte più veloce di uniqExact) e quantileTDigest per il calcolo dei percentili. La dashboard di analisi antifrode usa esclusivamente funzioni approssimate, il che mantiene tutte le query delle dashboard sotto i 200ms.
Numeri sulle prestazioni
Ecco alcuni benchmark rappresentativi delle query sul nostro cluster di produzione da 2 miliardi di righe:
Tasso di frode per paese, ultimi 7 giorni: 120ms. Timeline di un visitatore (50 eventi): 8ms. Visitatori unici al giorno, ultimi 30 giorni: 340ms. Distribuzione del punteggio di rischio, ultime 24 ore: 95ms. Prime 100 device per numero di eventi, ultimi 30 giorni: 210ms.
Questi numeri includono il round-trip di rete dai nostri server applicativi al cluster ClickHouse. Il tempo di esecuzione puro delle query è di norma inferiore del 30-50%.
Lezioni operative
Lezione 1: monitorare il ritardo dei merge
Il motore MergeTree di ClickHouse fonde di continuo i piccoli part di dati in part più grandi. Se i merge restano indietro (a causa di un elevato tasso di inserimento o della contesa sull'I/O del disco), le prestazioni delle query si degradano perché le query devono scansionare più part. Monitoriamo il numero di part per partizione e generiamo un avviso quando supera 300.
Lezione 2: evitare grandi operazioni ALTER TABLE
Aggiungere una colonna a una tabella da 2 miliardi di righe in ClickHouse è istantaneo (riguarda solo i metadati). Ma cambiare il tipo di una colonna richiede di riscrivere tutti i part di dati, un processo che sul nostro cluster ha richiesto 6 ore. Ora trattiamo lo schema come append-only: le nuove colonne si aggiungono liberamente, ma i cambi di tipo passano attraverso una tabella di migrazione.
Lezione 3: TTL con cautela
ClickHouse supporta la scadenza automatica dei dati tramite TTL. Abbiamo impostato un TTL di 90 giorni sulla nostra tabella di eventi. L'insidia: l'eliminazione TTL avviene durante i merge, il che significa che i dati eliminati possono persistere per ore o giorni dopo la scadenza del TTL. Per le eliminazioni critiche per la conformità, eseguiamo query ALTER TABLE DELETE esplicite in modo pianificato.
Costi
Il nostro cluster ClickHouse a 6 nodi (ogni nodo: 32 vCPU, 128 GB RAM, 2 TB NVMe) costa circa $8.400/mese su hosting bare metal. Memorizza 2 miliardi di righe con una retention di 90 giorni e gestisce 50K inserimenti/secondo più 200 query concorrenti dalle dashboard. Il costo per evento memorizzato è di $0.0000042: ordini di grandezza più economico di analisi comparabili su database cloud gestiti.