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 для обчислення перцентилів. Дашборд фрод-аналітики використовує виключно наближені функції, що тримає всі запити дашборда під 200 мс.
Показники продуктивності
Ось репрезентативні бенчмарки запитів на нашому 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 відбувається під час злиттів, а це означає, що видалені дані можуть зберігатися ще години або дні після закінчення строку TTL. Для видалення, критичного для комплаєнсу, ми запускаємо явні запити ALTER TABLE DELETE за розкладом.
Вартість
Наш кластер ClickHouse із 6 вузлів (кожен вузол: 32 vCPU, 128 ГБ RAM, 2 ТБ NVMe) коштує приблизно $8 400/місяць на хостингу bare metal. Він зберігає 2 млрд рядків із 90-денним утриманням і обробляє 50K вставок/секунду плюс 200 одночасних запитів дашбордів. Вартість за одну збережену подію становить $0,0000042 — на порядки дешевше за порівнянну аналітику на керованих хмарних базах даних.