ClickHouse na produkcji: ingestia, scalanie i koszty przy 2 mld wierszy
Nasze doświadczenia z uruchamianiem ClickHouse na produkcji: projekt schematu, optymalizacja zapytań i jak osiągamy analitykę poniżej sekundy dla ponad 100 mln zdarzeń urządzeń.
Kiedy zaczynaliśmy budować warstwę analityczną tracio.ai, potrzebowaliśmy bazy danych, która poradzi sobie z naszym specyficznym obciążeniem: przyjmowaniem 50 000 zdarzeń identyfikacji urządzeń na sekundę, przechowywaniem ponad 2 miliardów wierszy i odpowiadaniem na zapytania analityczne w czasie poniżej jednej sekundy. Oceniliśmy PostgreSQL (zbyt wolny przy agregacjach na tę skalę), Elasticsearch (zbyt kosztowny dla analityki szeregów czasowych) oraz ClickHouse. ClickHouse wygrał zdecydowanie.
Dlaczego ClickHouse
ClickHouse to zorientowana kolumnowo baza OLAP zaprojektowana do analityki w czasie rzeczywistym. Jego kluczową zaletą dla naszego obciążenia jest to, że odczytuje tylko kolumny potrzebne dla danego zapytania. Kiedy analityk fraudu pyta „pokaż mi wskaźnik fraudu według kraju z ostatnich 7 dni”, ClickHouse odczytuje jedynie kolumny country, timestamp i risk_score — ignorując pozostałe ponad 40 kolumn w tabeli zdarzeń. Na tabeli 2 mld wierszy zmniejsza to I/O o 95%.
ClickHouse również niezwykle dobrze kompresuje dane. Nasza tabela zdarzeń z 2 mld wierszy zajmuje 340 GB na dysku — około 170 bajtów na wiersz po kompresji, wobec 1,2 KB na wiersz bez kompresji. Współczynnik kompresji 7:1 oznacza, że więcej danych mieści się w pamięci, co bezpośrednio przekłada się na szybsze zapytania.
Projekt schematu
Nasza główna tabela przechowuje jeden wiersz na każde zdarzenie identyfikacji:
Tabela używa silnika MergeTree, uporządkowanego według (workspace_id, toDate(timestamp), visitor_hash). To uporządkowanie jest kluczowe — oznacza, że zapytania filtrowane po workspace i zakresie dat odczytują minimalną ilość danych. Kolumna visitor_hash umożliwia szybkie wyszukiwanie po ID odwiedzającego bez indeksu wtórnego.
Wybraliśmy LowCardinality(String) dla kolumn country, device_type, browser_family i os_family, ponieważ mają one mniej niż 10 000 unikalnych wartości. ClickHouse przechowuje kolumny LowCardinality jako liczby całkowite zakodowane słownikowo, zmniejszając zajętość o 80% w porównaniu ze zwykłymi napisami i przyspieszając operacje GROUP BY.
Strategia shardingu
Tabelę zdarzeń dzielimy na shardy między 6 węzłów, używając hashu z workspace_id. Dzięki temu wszystkie zdarzenia danego klienta znajdują się na tym samym shardzie, co oznacza, że większość zapytań (filtrowanych po workspace_id) trafia w pojedynczy shard. Zapytania między shardami są potrzebne tylko dla analityki wewnętrznej.
Każdy shard ma 2 repliki dla wysokiej dostępności. Replikacja korzysta z wbudowanego silnika ReplicatedMergeTree ClickHouse z koordynacją przez ZooKeeper. Przełączanie awaryjne jest automatyczne — jeśli shard ulegnie awarii, zapytania są kierowane do repliki bez żadnych zmian po stronie klienta.
Potok ingestii
Zdarzenia płyną z naszego tematu Kafki do ClickHouse przez własną usługę w Go, która grupuje wstawienia w partie. Wstawiamy w partiach po 10 000 wierszy co 500 ms — to równoważy opóźnienie ingestii (poniżej sekundy) z wydajnością wstawiania (ClickHouse działa najlepiej przy dużych partiach).
Usługa ingestii z gracją obsługuje przeciążenie zwrotne (back-pressure). Jeśli ClickHouse wolno przyjmuje wstawienia (podczas scalania lub dużego obciążenia zapytaniami), usługa buforuje w pamięci do 1 miliona zdarzeń i stosuje przeciążenie zwrotne wobec konsumenta Kafki. Przez 18 miesięcy na produkcji nigdy nie utraciliśmy żadnego zdarzenia.
Optymalizacja zapytań
Zmaterializowane widoki
Dla typowych zapytań dashboardu używamy zmaterializowanych widoków, które wstępnie agregują dane. Nasz dashboard wskaźnika fraudu na przykład odczytuje ze zmaterializowanego widoku, który agreguje liczbę fraud_detected według workspace, kraju i godziny. Widok zmniejsza ilość danych skanowanych dla tego zapytania z 2 mld wierszy do 5 mln wierszy.
Porządkowanie projekcji
Projekcje ClickHouse pozwalają nam zdefiniować alternatywne porządki sortowania tabeli bez duplikowania danych. Dodaliśmy projekcję uporządkowaną według (workspace_id, visitor_hash, timestamp) dla zapytań o oś czasu odwiedzającego. Bez projekcji te zapytania skanowały całe zakresy dat. Z nią odczytują tylko bloki zawierające docelowego odwiedzającego.
Funkcje przybliżone
Dla zapytań dashboardu, w których dokładne liczby nie są krytyczne, używamy funkcji przybliżonych ClickHouse: uniqCombined dla zliczeń unikalnych (margines błędu 2%, 10x szybsze niż uniqExact) oraz quantileTDigest do obliczania percentyli. Dashboard analityki fraudu korzysta wyłącznie z funkcji przybliżonych, co utrzymuje wszystkie zapytania dashboardu poniżej 200 ms.
Liczby wydajności
Oto reprezentatywne wyniki benchmarków zapytań na naszym produkcyjnym klastrze 2 mld wierszy:
Wskaźnik fraudu według kraju, ostatnie 7 dni: 120 ms. Oś czasu odwiedzającego (50 zdarzeń): 8 ms. Unikalni odwiedzający dziennie, ostatnie 30 dni: 340 ms. Rozkład risk score, ostatnie 24 godziny: 95 ms. Top 100 urządzeń według liczby zdarzeń, ostatnie 30 dni: 210 ms.
Liczby te obejmują czas przejścia sieciowego z naszych serwerów aplikacyjnych do klastra ClickHouse. Sam czas wykonania zapytania jest zwykle o 30–50% niższy.
Lekcje operacyjne
Lekcja 1: Monitoruj opóźnienie scalania
Silnik MergeTree ClickHouse nieustannie scala małe party danych w większe. Jeśli scalanie zaczyna zostawać w tyle (z powodu wysokiego tempa wstawiania lub rywalizacji o I/O dysku), wydajność zapytań spada, ponieważ zapytania muszą skanować więcej partów. Monitorujemy liczbę partów na partycję i alarmujemy, gdy przekroczy ona 300.
Lekcja 2: Unikaj dużych operacji ALTER TABLE
Dodanie kolumny do tabeli 2 mld wierszy w ClickHouse jest natychmiastowe (dotyczy tylko metadanych). Ale zmiana typu kolumny wymaga przepisania wszystkich partów danych — proces, który na naszym klastrze zajął 6 godzin. Traktujemy teraz schemat jako tylko do dopisywania: nowe kolumny dodajemy swobodnie, ale zmiany typu przechodzą przez tabelę migracyjną.
Lekcja 3: TTL z rozwagą
ClickHouse obsługuje automatyczne wygasanie danych za pomocą TTL. Ustawiliśmy 90-dniowe TTL na naszej tabeli zdarzeń. Haczyk: usuwanie przez TTL następuje podczas scalania, co oznacza, że usunięte dane mogą przetrwać godziny lub dni po wygaśnięciu TTL. Dla usuwania krytycznego z punktu widzenia zgodności uruchamiamy jawne zapytania ALTER TABLE DELETE według harmonogramu.
Koszt
Nasz 6-węzłowy klaster ClickHouse (każdy węzeł: 32 vCPU, 128 GB RAM, 2 TB NVMe) kosztuje około 8400 $/miesiąc na hostingu bare metal. Przechowuje 2 mld wierszy z 90-dniową retencją i obsługuje 50 tys. wstawień/sekundę oraz 200 równoczesnych zapytań dashboardu. Koszt na zapisane zdarzenie wynosi 0,0000042 $ — o rzędy wielkości taniej niż porównywalna analityka na zarządzanych bazach w chmurze.