ClickHouse în producție: ingestie, merge-uri și cost la 2 miliarde de rânduri
Experiența noastră cu ClickHouse în producție: proiectarea schemei, optimizarea interogărilor și cum obținem analize sub o secundă pentru peste 100M de evenimente de dispozitiv.
Când am început să construim nivelul de analiză al tracio.ai, aveam nevoie de o bază de date care să facă față volumului nostru specific de lucru: să ingereze 50.000 de evenimente de identificare a dispozitivelor pe secundă, să stocheze peste 2 miliarde de rânduri și să răspundă la interogări analitice în mai puțin de o secundă. Am evaluat PostgreSQL (prea lent pentru agregări la această scară), Elasticsearch (prea scump pentru analize de tip time-series) și ClickHouse. ClickHouse a câștigat detașat.
De ce ClickHouse
ClickHouse este o bază de date OLAP orientată pe coloane, proiectată pentru analize în timp real. Avantajul său cheie pentru volumul nostru de lucru este că citește doar coloanele necesare fiecărei interogări. Când un analist antifraudă cere „arată-mi rata de fraudă pe țară pentru ultimele 7 zile”, ClickHouse citește doar coloanele country, timestamp și risk_score — ignorând celelalte peste 40 de coloane din tabelul de evenimente. Pe un tabel de 2 miliarde de rânduri, acest lucru reduce I/O-ul cu 95%.
De asemenea, ClickHouse comprimă datele extrem de bine. Tabelul nostru de evenimente cu 2 miliarde de rânduri ocupă 340 GB pe disc — aproximativ 170 de octeți per rând comprimat, față de 1,2 KB per rând necomprimat. Rata de compresie de 7:1 înseamnă că mai multe date încap în memorie, ceea ce se traduce direct în interogări mai rapide.
Proiectarea schemei
Tabelul nostru principal stochează câte un rând per eveniment de identificare:
Tabelul folosește motorul MergeTree, ordonat după (workspace_id, toDate(timestamp), visitor_hash). Această ordonare este esențială — înseamnă că interogările filtrate după workspace și interval de date citesc un volum minim de date. Coloana visitor_hash permite căutări rapide după ID-ul vizitatorului, fără un index secundar.
Am ales LowCardinality(String) pentru country, device_type, browser_family și os_family, deoarece aceste coloane au mai puțin de 10.000 de valori distincte. ClickHouse stochează coloanele LowCardinality ca numere întregi codificate prin dicționar, reducând stocarea cu 80% față de string-urile simple și accelerând operațiile GROUP BY.
Strategia de sharding
Împărțim tabelul de evenimente pe 6 noduri folosind un hash al workspace_id. Astfel ne asigurăm că toate evenimentele unui anumit client se află pe același shard, ceea ce înseamnă că majoritatea interogărilor (filtrate după workspace_id) ating un singur shard. Interogările cross-shard sunt necesare doar pentru analizele interne.
Fiecare shard are 2 replici pentru disponibilitate ridicată. Replicarea folosește motorul ReplicatedMergeTree încorporat în ClickHouse, cu coordonare prin ZooKeeper. Failover-ul este automat — dacă un shard cade, interogările sunt redirecționate către replică, fără modificări pe partea clientului.
Pipeline-ul de ingestie
Evenimentele curg din topicul nostru Kafka în ClickHouse printr-un serviciu Go personalizat care grupează inserările în loturi. Inserăm în loturi de 10.000 de rânduri la fiecare 500 ms — acest lucru echilibrează latența de ingestie (sub o secundă) cu eficiența inserării (ClickHouse funcționează cel mai bine cu loturi mari).
Serviciul de ingestie gestionează elegant contrapresiunea (back-pressure). Dacă ClickHouse acceptă inserările lent (în timpul merge-urilor sau la o încărcare mare de interogări), serviciul stochează în memorie până la 1 milion de evenimente și aplică contrapresiune consumatorului Kafka. În 18 luni de producție, nu am pierdut niciodată vreun eveniment.
Optimizarea interogărilor
Materialized views
Pentru interogările frecvente de dashboard, folosim materialized views care preagregă datele. Dashboard-ul nostru pentru rata de fraudă, de exemplu, citește dintr-un materialized view care agregă numărul de fraud_detected după workspace, țară și oră. View-ul reduce datele scanate pentru această interogare de la 2 miliarde de rânduri la 5 milioane de rânduri.
Ordonarea prin projections
Projections din ClickHouse ne permit să definim ordini de sortare alternative pentru un tabel fără a duplica datele. Am adăugat o projection ordonată după (workspace_id, visitor_hash, timestamp) pentru interogările de tip timeline al vizitatorului. Fără projection, aceste interogări scanau intervale întregi de date. Cu ea, citesc doar blocurile care conțin vizitatorul țintă.
Funcții aproximative
Pentru interogările de dashboard unde numărătorile exacte nu sunt critice, folosim funcțiile aproximative din ClickHouse: uniqCombined pentru numărători de valori distincte (marjă de eroare de 2%, de 10 ori mai rapid decât uniqExact) și quantileTDigest pentru calculul percentilelor. Dashboard-ul de analiză antifraudă folosește exclusiv funcții aproximative, ceea ce menține toate interogările de dashboard sub 200 ms.
Cifre de performanță
Iată benchmark-uri reprezentative pentru interogări pe clusterul nostru de producție cu 2 miliarde de rânduri:
Rata de fraudă pe țară, ultimele 7 zile: 120 ms. Timeline-ul vizitatorului (50 de evenimente): 8 ms. Vizitatori unici pe zi, ultimele 30 de zile: 340 ms. Distribuția scorului de risc, ultimele 24 de ore: 95 ms. Top 100 de dispozitive după numărul de evenimente, ultimele 30 de zile: 210 ms.
Aceste cifre includ round-trip-ul de rețea de la serverele noastre de aplicație către clusterul ClickHouse. Timpul pur de execuție a interogării este de obicei cu 30-50% mai mic.
Lecții operaționale
Lecția 1: monitorizează întârzierea merge-urilor
Motorul MergeTree din ClickHouse combină în mod continuu parts mici de date în altele mai mari. Dacă merge-urile rămân în urmă (din cauza unei rate mari de inserare sau a contenției de I/O pe disc), performanța interogărilor scade, deoarece interogările trebuie să scaneze mai multe parts. Monitorizăm numărul de parts per partiție și declanșăm alerte când depășește 300.
Lecția 2: evită operațiile mari de ALTER TABLE
Adăugarea unei coloane într-un tabel de 2 miliarde de rânduri în ClickHouse este instantanee (ține doar de metadate). Dar schimbarea tipului unei coloane necesită rescrierea tuturor parts de date — un proces care a durat 6 ore pe clusterul nostru. Acum tratăm schema ca fiind append-only: coloanele noi se adaugă liber, dar schimbările de tip trec printr-un tabel de migrare.
Lecția 3: TTL cu prudență
ClickHouse acceptă expirarea automată a datelor prin TTL. Am setat un TTL de 90 de zile pe tabelul nostru de evenimente. Capcana: ștergerea prin TTL are loc în timpul merge-urilor, ceea ce înseamnă că datele șterse pot persista ore sau zile după expirarea TTL. Pentru ștergerea critică din punct de vedere al conformității, rulăm interogări explicite ALTER TABLE DELETE conform unui program.
Cost
Clusterul nostru ClickHouse de 6 noduri (fiecare nod: 32 vCPU, 128 GB RAM, 2 TB NVMe) costă aproximativ 8.400 $/lună pe găzduire bare metal. Acesta stochează 2 miliarde de rânduri cu o retenție de 90 de zile și gestionează 50K inserări/secundă plus 200 de interogări de dashboard concurente. Costul per eveniment stocat este de 0,0000042 $ — cu ordine de mărime mai ieftin decât analizele comparabile pe baze de date cloud gestionate.