ClickHouse в продакшене: ingestion, merge и стоимость на 2 млрд строк
Наш опыт эксплуатации ClickHouse в продакшене: проектирование схемы, оптимизация запросов и как мы держим субсекундную аналитику по 100M+ событий устройств.
Когда мы начинали строить аналитический слой tracio.ai, нам была нужна база данных, способная справиться с нашей специфической нагрузкой: принимать 50 000 событий идентификации устройств в секунду, хранить 2+ миллиарда строк и отвечать на аналитические запросы менее чем за секунду. Мы оценивали PostgreSQL (слишком медленный для агрегаций в таком масштабе), Elasticsearch (слишком дорогой для аналитики временных рядов) и 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 позволяет быстро искать по ID посетителя без вторичного индекса.
Для country, device_type, browser_family и os_family мы выбрали LowCardinality(String), потому что у этих колонок меньше 10 000 различных значений. ClickHouse хранит колонки LowCardinality как словарно закодированные целые числа, что снижает объём хранения на 80% по сравнению с обычными строками и ускоряет операции GROUP BY.
Стратегия шардирования
Мы шардируем таблицу событий на 6 узлах, используя хэш от workspace_id. Это гарантирует, что все события конкретного клиента находятся на одном шарде, а значит большинство запросов (с фильтром по workspace_id) попадают в один шард. Межшардовые запросы нужны только для внутренней аналитики.
У каждого шарда есть 2 реплики для высокой доступности. Репликация использует встроенный в ClickHouse движок ReplicatedMergeTree с координацией через ZooKeeper. Отказоустойчивость автоматическая — если шард выходит из строя, запросы направляются на реплику без изменений на стороне клиента.
Пайплайн ingestion
События поступают из нашего топика Kafka в ClickHouse через собственный Go-сервис, который батчит вставки. Мы вставляем батчами по 10 000 строк каждые 500 мс — это балансирует задержку ingestion (субсекундную) с эффективностью вставки (ClickHouse лучше всего работает с большими батчами).
Сервис ingestion аккуратно обрабатывает back-pressure. Если ClickHouse медленно принимает вставки (во время merge или высокой нагрузки запросами), сервис буферизует до 1 миллиона событий в памяти и применяет back-pressure к consumer'у Kafka. За 18 месяцев в продакшене мы ни разу не потеряли ни одного события.
Оптимизация запросов
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 мс.
Показатели производительности
Вот репрезентативные бенчмарки запросов на нашем продакшен-кластере в 2 млрд строк:
Фрод-рейт по странам за последние 7 дней: 120 мс. Таймлайн посетителя (50 событий): 8 мс. Уникальные посетители в день за последние 30 дней: 340 мс. Распределение risk score за последние 24 часа: 95 мс. Топ-100 устройств по числу событий за последние 30 дней: 210 мс.
Эти цифры включают сетевой round-trip от наших серверов приложений до кластера ClickHouse. Чистое время выполнения запроса обычно на 30–50% ниже.
Операционные уроки
Урок 1: следите за отставанием merge
Движок MergeTree в ClickHouse непрерывно сливает мелкие части данных в более крупные. Если merge начинают отставать (из-за высокой скорости вставки или конкуренции за дисковый I/O), производительность запросов деградирует, потому что запросам приходится сканировать больше частей. Мы мониторим количество частей на партицию и оповещаем, когда оно превышает 300.
Урок 2: избегайте крупных операций ALTER TABLE
Добавление колонки в таблицу на 2 млрд строк в ClickHouse мгновенно (это только метаданные). Но смена типа колонки требует перезаписи всех частей данных — процесс, который на нашем кластере занял 6 часов. Теперь мы относимся к схеме как к append-only: новые колонки добавляются свободно, а смена типа идёт через миграционную таблицу.
Урок 3: TTL с осторожностью
ClickHouse поддерживает автоматическое истечение срока данных через TTL. Мы задали TTL в 90 дней на нашей таблице событий. Подвох: удаление по TTL происходит во время merge, а значит удалённые данные могут сохраняться часами или днями после истечения TTL. Для удаления, критичного к соответствию требованиям, мы запускаем явные запросы ALTER TABLE DELETE по расписанию.
Стоимость
Наш кластер ClickHouse из 6 узлов (каждый узел: 32 vCPU, 128 ГБ RAM, 2 ТБ NVMe) стоит примерно $8 400 в месяц на bare metal-хостинге. Он хранит 2 млрд строк с 90-дневным сроком хранения и обрабатывает 50K вставок/с плюс 200 одновременных запросов дашбордов. Стоимость хранения одного события — $0,0000042, на порядки дешевле сопоставимой аналитики на managed облачных базах данных.