ClickHouse у production: інжест, злиття та вартість на 2 млрд рядків
Наш досвід експлуатації ClickHouse у production: проєктування схеми, оптимізація запитів і те, як ми досягаємо субсекундної аналітики на 100M+ подіях пристроїв.
Коли ми почали будувати аналітичний рівень tracio.ai, нам була потрібна база даних, здатна впоратися з нашим специфічним навантаженням: інжест 50 000 подій ідентифікації пристроїв на секунду, зберігання 2+ мільярдів рядків і відповіді на аналітичні запити менш ніж за одну секунду. Ми оцінили PostgreSQL (надто повільний для агрегацій у такому масштабі), Elasticsearch (надто дорогий для time-series аналітики) та ClickHouse. ClickHouse переміг переконливо.
Чому ClickHouse
ClickHouse — це колонкова OLAP-база даних, розроблена для аналітики в реальному часі. Її ключова перевага для нашого навантаження полягає в тому, що вона читає лише ті колонки, які потрібні для кожного запиту. Коли фрод-аналітик запитує «покажи мені рівень фроду за країнами за останні 7 днів», ClickHouse читає лише колонки country, timestamp і risk_score — ігноруючи інші 40+ колонок у таблиці подій. На таблиці з 2 млрд рядків це зменшує I/O на 95%.
ClickHouse також надзвичайно добре стискає дані. Наша таблиця подій на 2 млрд рядків займає 340 ГБ на диску — близько 170 байтів на рядок у стисненому вигляді проти 1,2 КБ на рядок без стиснення. Коефіцієнт стиснення 7:1 означає, що більше даних поміщається в пам'ять, а це напряму дає швидші запити.
Проєктування схеми
Наша основна таблиця зберігає один рядок на кожну подію ідентифікації:
Таблиця використовує движок MergeTree, упорядкований за (workspace_id, toDate(timestamp), visitor_hash). Це впорядкування критичне — воно означає, що запити, відфільтровані за workspace і діапазоном дат, читають мінімум даних. Колонка visitor_hash уможливлює швидкий пошук за visitor ID без вторинного індексу.
Ми обрали LowCardinality(String) для country, device_type, browser_family та os_family, тому що ці колонки мають менш ніж 10 000 унікальних значень. ClickHouse зберігає колонки LowCardinality як цілі числа зі словниковим кодуванням, зменшуючи обсяг зберігання на 80% порівняно зі звичайними рядками та пришвидшуючи операції GROUP BY.
Стратегія шардування
Ми шардуємо таблицю подій на 6 вузлах, використовуючи хеш workspace_id. Це гарантує, що всі події для конкретного клієнта знаходяться на одному шарді, а отже більшість запитів (відфільтрованих за workspace_id) звертаються до одного шарда. Міжшардові запити потрібні лише для внутрішньої аналітики.
Кожен шард має 2 репліки для високої доступності. Реплікація використовує вбудований у ClickHouse движок ReplicatedMergeTree з координацією через ZooKeeper. Failover автоматичний — якщо шард виходить з ладу, запити маршрутизуються до репліки без жодних змін на боці клієнта.
Конвеєр інжесту
Події надходять з нашого топіка Kafka до ClickHouse через власний Go-сервіс, що збирає вставки в пакети. Ми вставляємо пакетами по 10 000 рядків кожні 500 мс — це балансує затримку інжесту (субсекундну) з ефективністю вставки (ClickHouse працює найкраще з великими пакетами).
Сервіс інжесту коректно обробляє back-pressure. Якщо ClickHouse повільно приймає вставки (під час злиттів або важкого навантаження запитами), сервіс буферизує до 1 мільйона подій у пам'яті та застосовує back-pressure до консюмера Kafka. За 18 місяців у production ми жодного разу не втратили подію.
Оптимізація запитів
Materialized views
Для типових запитів дашбордів ми використовуємо materialized views, які попередньо агрегують дані. Наш дашборд рівня фроду, наприклад, читає з materialized view, що агрегує кількість fraud_detected за workspace, країною та годиною. Ця view зменшує обсяг даних, що скануються для цього запиту, з 2 млрд рядків до 5 млн рядків.
Упорядкування проєкцій
Проєкції ClickHouse дають змогу визначати альтернативні порядки сортування для таблиці без дублювання даних. Ми додали проєкцію, упорядковану за (workspace_id, visitor_hash, timestamp), для запитів таймлайну відвідувачів. Без проєкції ці запити сканували цілі діапазони дат. З нею вони читають лише блоки, що містять цільового відвідувача.
Наближені функції
Для запитів дашбордів, де точні підрахунки не критичні, ми використовуємо наближені функції ClickHouse: uniqCombined для підрахунку унікальних значень (похибка 2%, у 10 разів швидше за uniqExact) та quantileTDigest для обчислення перцентилів. Дашборд фрод-аналітики використовує виключно наближені функції, що тримає запити дашборда у межах секунди.
Показники продуктивності
Ось репрезентативні бенчмарки запитів на нашому production-кластері з 2 млрд рядків:
Рівень фроду за країнами, останні 7 днів: 120 мс. Таймлайн відвідувача (50 подій): 8 мс. Унікальні відвідувачі за день, останні 30 днів: 340 мс. Розподіл risk score, останні 24 години: 95 мс. Топ-100 пристроїв за кількістю подій, останні 30 днів: 210 мс.
Ці цифри включають мережевий round-trip від наших серверів застосунку до кластера ClickHouse. Час чистого виконання запиту зазвичай на 30-50% нижчий.
Операційні уроки
Урок 1: Стежте за відставанням злиття
Движок MergeTree у ClickHouse безперервно зливає малі парти даних у більші. Якщо злиття відстають (через високу швидкість вставки або конкуренцію за дисковий I/O), продуктивність запитів деградує, бо запитам доводиться сканувати більше партів. Ми моніторимо кількість партів на партицію та піднімаємо алерт, коли вона перевищує 300.
Урок 2: Уникайте великих операцій ALTER TABLE
Додавання колонки до таблиці на 2 млрд рядків у ClickHouse миттєве (це лише метадані). Але зміна типу колонки потребує перезапису всіх партів даних — процес, що зайняв 6 годин на нашому кластері. Тепер ми ставимося до схеми як до append-only: нові колонки додаються вільно, але зміни типу проходять через міграційну таблицю.
Урок 3: TTL з обережністю
ClickHouse підтримує автоматичне закінчення строку дії даних через TTL, і на таблиці подій спершу стояв 90-денний. Ми його прибрали — з причини, яку варто передати далі: видалення за TTL відбувається під час злиттів, тож рядки живуть ще години або дні після закінчення строку, і ця затримка нічим підконтрольним вам не обмежена. Це робить TTL інструментом прибирання, а не гарантією видалення: якщо треба пообіцяти клієнтові, що запис зникне до певної дати, вираз у визначенні таблиці такої обіцянки не дотримає. Видалення, яке має справджуватися, мусить бути явним, запланованим і перевірюваним.
Вартість
Наш кластер ClickHouse із 6 вузлів (кожен вузол: 32 vCPU, 128 ГБ RAM, 2 ТБ NVMe) коштує приблизно $8 400/місяць на хостингу bare metal. Він зберігає 2 млрд рядків і обробляє 50K вставок/секунду плюс 200 одночасних запитів дашбордів. Вартість за одну збережену подію становить $0,0000042 — на порядки дешевше за порівнянну аналітику на керованих хмарних базах даних.