Почему аналитика на ClickHouse MergeTree деградирует с ростом данных
В ClickHouse таблица MergeTree хранит данные не единым массивом, а набором отсортированных частей: строки раскладываются по ключу сортировки, и внутри каждой части лежит разреженный первичный индекс — метки через каждые несколько тысяч строк. Пока объём небольшой, планировщик по этим меткам быстро отсекает ненужные диапазоны, и запрос читает ровно то, что попросили. С ростом объёма данных механика ломается: если фильтры в отчётах идут не по ключу сортировки, ни первичный индекс, ни разбиение по месяцам не помогают, и каждый запрос превращается в чтение всей таблицы.
Второй эффект — фоновые слияния. Чем больше вставок мелкими пачками, тем больше частей нужно склеить, тем чаще сервер упирается в предел одновременных слияний и отвечает ошибкой о слишком большом числе частей. Деградация аналитических запросов идёт нелинейно: медленные отчёты вместо секунд начинают считать минуты, а тяжёлые агрегации с группировкой по высококардинальному ключу падают по памяти, когда промежуточный результат не помещается в лимит на запрос.
Узкое место хранилища здесь почти всегда одно: схема таблицы не совпадает с тем, как её спрашивают. Сырые события лежат одним слоем, каждое построение отчёта пересчитывает агрегаты заново, и рост объёма данных бьёт напрямую по времени ответа. Разбираем, как это закрывается.
Что даёт грамотная схема хранения: эффект в сроках и объёмах
В ClickHouse физический порядок строк задаёт MergeTree: ключ сортировки в ORDER BY определяет, какие блоки данных запрос вообще читает, а не только как быстро фильтрует. Когда отчёт ходит по времени и клиенту, ключ из этих двух полей превращает полное сканирование в чтение узкого диапазона. На типовой аналитике отчёт за сутки по крупной таблице ускоряется на порядок — с десятков секунд до долей секунды, и это не разовая оптимизация, а свойство схемы.
Партиционирование по месяцу вместе с политикой TTL делает хранение управляемым: устаревшие партиции удаляются целиком, без перезаписи таблицы и без просадки вставок. Колоночное сжатие плюс подбор кодека под характер колонок — отдельно числовые, отдельно словарные через LowCardinality — дают сжатие в 4–8 раз относительно исходного объёма. Реестр перестаёт расти линейно вместе с потоком событий.
Отдельный эффект — ушедший класс отказов. При неверном ключе растёт число мелких кусков, фоновые слияния не успевают за вставками, и загрузка начинает падать с ошибкой о слишком большом числе частей. Правильная грануляция партиций и ключа снимает этот сценарий: вставки идут стабильно, слияния укладываются в фоновое окно без конкуренции за диск.
Нагрузка на хранилище распределяется предсказуемо: запросы читают меньше данных, поэтому аналитика не блокирует загрузку. В сумме это и есть измеримый результат перестройки схемы — стабильное время ответа отчётов на растущем объёме и отсутствие деградации под пиковой записью.
Варианты организации аналитического хранения: сравнение подходов
Схема хранения в ClickHouse MergeTree определяет, где считается агрегат — на лету при каждой выборке или заранее в момент записи. От этого зависят время ответа отчётов и объём пересчёта, который понадобится при изменении метрики.
Ниже три схемы, между которыми реально выбирают на старте аналитического контура.
| Вариант | Когда подходит | Ограничения |
|---|---|---|
| Одна широкая таблица | Срезы ещё уточняются, состав метрик меняется часто | Тяжёлые выборки, дорогая детализация по мелким разрезам |
| Слои с агрегатами | Список отчётов стабилен, важна скорость ответа | Агрегаты пересобираются при правке логики |
| Отдельные таблицы под срезы | Несколько команд со своими разрезами и своим темпом загрузки | Больше объектов, дублируется загрузка, сложнее сверка |
На практике варианты комбинируют: широкая таблица хранит события, материализованные представления поддерживают агрегированные слои по частым срезам, партиции нарезаны по дате события, а срок жизни записей задан правилом очистки по горизонту хранения. Тогда новый срез — это добавление представления, а не перезаливка всего хранилища.
Ориентир при выборе такой: если запрос отчёта читает строк на порядок больше, чем возвращает, агрегат пора выносить в отдельный слой; если срезов больше пяти и они живут своей жизнью — разносить по таблицам. Партиционирование, ключ сортировки и срок жизни записей при этом остаются общими и настраиваются один раз для всего контура.
Этапы настройки хранилища аналитики
Настройка MergeTree начинается не с выбора движка, а с понимания того, какие запросы к хранилищу выполняются каждый день. Порядок работ практически одинаков для любого внедрения: сначала измеряем, затем проектируем, и только потом проверяем на нагрузке, близкой к реальной. Ниже — последовательность, по которой идут наши инженеры.
- Аудит запросов и профиля нагрузки. Собираем перечень регулярных отчётов и разовых выборок, смотрим замеры времени по каждому, отмечаем, какие поля фильтруются и группируются чаще всего. Результат — список запросов, которые действительно стоит оптимизировать, и тех, что можно не трогать.
- Проектирование схемы. Определяем состав таблиц, типы колонок и разделение по слоям: сырые данные, промежуточные и предрассчитанные агрегаты. Это убирает повторные тяжёлые вычисления на каждом обращении к панели отчётов.
- Подбор ключа сортировки. Порядок колонок в ключе решает, попадёт запрос в узкий диапазон чтения или будет сканировать таблицу целиком. Варианты проверяем на реальных выборках из аудита, а не по интуиции.
- Настройка хранения. Разбиваем данные по датам, задаём сроки жизни и отдельные правила для редко запрашиваемых периодов. Объём хранилища перестаёт расти бесконтрольно, при этом история за нужный горизонт остаётся доступной, а старые срезы читаются без деградации.
- Предрассчёт повторяющихся отчётов. Выносим самые частые агрегации в материализованные представления, чтобы панели открывались за секунды, а не пересчитывали миллионы строк при каждом обращении.
- Проверка на реальной нагрузке. Прогоняем типовые сценарии одновременно, как это будет в работе: смотрим время отклика, потребление памяти и диска, поведение при пиковой записи. Здесь же проверяем, что откат на прежнюю схему проходит без потери данных.
- Передача в эксплуатацию. Отдаём регламенты обслуживания, настройки мониторинга и документированные процедуры резервного копирования и восстановления, чтобы команда могла сопровождать хранилище самостоятельно.
Комплект артефактов: схемы таблиц, конфигурации, регламенты
Мы передаём не только работающий кластер, но и полный набор артефактов, по которым его можно развивать и обслуживать силами своей команды. Всё лежит в вашем репозитории и в системе контроля версий — с историей изменений, а не «устными договорённостями с прошлым подрядчиком».
- Схемы таблиц — DDL-скрипты с движком MergeTree: ключ сортировки, разбиение по партициям, правила TTL и материализованные представления. К каждой таблице комментарий, почему выбран именно такой ключ и что сломается при его смене.
- Конфигурации хранилища — параметры слияния кусков, лимиты памяти на запрос, настройки репликации и распределённых таблиц, включённые кодеки сжатия по колонкам.
- Политики доступа — роли и уровни прав: отдельные учётки для сервисов записи, аналитиков и дежурного инженера; квоты на тяжёлые выборки; для части команд — доступ только на чтение.
- Регламент обслуживания — расписание слияния партиций, контроль роста числа кусков, порядок действий при отставании реплики и правила отката на предыдущую схему.
- Схема потоков данных — откуда и с какой периодичностью приходят данные, где буферизуются, что делать при задержке источника или дублях.
- Мониторинг и оповещения — перечень метрик, пороговые значения и список тех, кто получает уведомление, с приоритетами.
- Инструкция по эксплуатации — типовые операции, разбор частых отказов и границы, за которыми нужно звать инженера.
- Журнал решений — какие варианты схемы и хранения рассматривались, какие отклонены и почему: это экономит время при следующей доработке.
Комплект рассчитан на то, чтобы новый инженер в команде разобрался в хранилище по документам, а не по переписке. Если понадобится расширение — приходим уже к готовой базе знаний.
Кейс: как ускорили аналитические запросы на потоковых данных
Продуктовый сервис с непрерывным потоком событий: десятки миллионов записей в сутки складывались в одну таблицу ClickHouse без внятного ключа сортировки. Отчёты по воронке собирались минутами, часть запросов обрывалась по времени ожидания, а реестр прирастал примерно на треть каждый месяц.
Точка, из которой начинали
- Время ответа отчёта — в 9 раз выше целевого: аналитики ждали результат и пересчитывали срезы вручную.
- Доля падавших запросов — 12% в часы пик, то есть отказы приходились ровно на самые нагруженные окна.
- Объём реестра рос быстрее самого потока событий: старые партиции никто не вычищал.
Что изменили
Переразметили таблицу: партиционирование по дате, ключ сортировки по арендатору и типу события, чтобы фильтры отчётов попадали в границы блоков и читалось меньше данных. Тяжёлые агрегаты вынесли в материализованные представления с предрасчётом, вставки перевели на пакетную запись, а для исторических данных повесили политику хранения с автоматической очисткой.
Что стало
Время ответа отчёта сократилось в 12 раз — выгрузка перестала быть отдельной задачей аналитика. Доля падавших запросов ушла к нулю, объём активного реестра уменьшился втрое, при этом откат к любой из последних партиций остаётся доступным. Новые типы событий добавляем по той же схеме, без перезаливки таблицы.
Чем MergeTree отличается от других движков ClickHouse?
В ClickHouse движок задаётся при создании таблицы и определяет формат хранения, доступные выборки и поведение при вставке. MergeTree — не один движок, а семейство с общей механикой: данные пишутся отсортированными кусками, по ключу сортировки строится разреженный индекс, а фоновые слияния укрупняют куски и схлопывают дубли.
Log-семейство (Log, TinyLog, StripeLog) устроено проще: колонка — отдельный файл, индекса и слияний нет. Такие таблицы дёшевы на запись и подходят для справочников и промежуточных буферов, но выборка по ключу читает файл целиком, а параллельная запись ограничена.
Репликация в ReplicatedMergeTree идёт через координатор: узлы обмениваются не файлами, а описанием частей и операций, поэтому отставшая реплика догоняет по журналу. Это не то же самое, что распределённый доступ: реплики — копии одних и тех же данных, а таблица Distributed раскладывает запрос по шардам и собирает результат.
Разница внутри семейства тоже существенна: ReplacingMergeTree оставляет последнюю версию строки, SummingMergeTree складывает значения при слиянии, CollapsingMergeTree помечает удаления. Выбор движка меняет не только скорость, но и то, какие данные вообще окажутся в таблице после слияний.
Практический критерий простой: нужны точечные чтения и слияния — берём MergeTree; нужна дедупликация при вставке — вариант с версиями; нужны данные на нескольких узлах — реплицированные таблицы под Distributed. Для мелких справочников Log-семейство честно дешевле.
Технические сомнения перед перестройкой хранилища
Перестройка схемы на работающем хранилище — вопрос не «сломается или нет», а порядка операций и точек отката. Ниже — ответы на три инженерных возражения, которые звучат перед стартом почти всегда.
Данные при перестройке таблиц потеряются?
Перестройка идёт через новые партиции: записи дублируются в целевую структуру, сверяются по контрольным суммам построчно, и только после сходимости переключается чтение. Старая структура остаётся на диске до конца проверки — откат занимает минуты. Миграция данных выполняется порциями, поэтому отдельное окно недоступности не требуется: хранилище продолжает отдавать запросы.
Придётся ли переписывать отчёты и выгрузки?
Обычно нет. Имена полей и типы сохраняем, а при смене гранулярности добавляем слой представлений с прежним контрактом для аналитики. Совместимость отчётов проверяем сравнением выгрузок до и после перестройки: расхождения сверх допуска ловим на тестовом контуре, а не на боевом.
Что будет с работающим продакшеном?
Перестройку ведём на реплике, переключение трафика — в окно минимальной нагрузки. Фоновые задачи ограничены по ресурсам, чтобы не забирать пропускную способность у живых запросов. Риски внедрения закрываем поэтапно: сначала партиционирование и сжатие, затем переход на целевую структуру, с заранее зафиксированным планом отката.
Обсудим задачу и вернёмся с оценкой и планом
Чтобы оценка внедрения была не «на глаз», а по делу, нужны ваши входные данные. Опишите задачу в двух словах — какая аналитика нужна, кто и как часто строит отчёты, — и приложите технический контекст по нагрузке и запросам. Пришлите то, что есть: черновик схемы, пример выгрузки, список полей.
- Откуда идут данные: очередь, сборщик логов, выгрузка изменений из операционной базы — с какой частотой и в каком формате приходят события.
- Профиль нагрузки: сколько строк в сутки и в месяц, средний и пиковый поток, требуемая глубина хранения.
- Типовые запросы: 5-10 самых частых с фильтрами, группировками и периодом — именно они определяют порядок сортировки в ключе и разбиение на партиции.
- Свежесть данных: допустимая задержка между событием и его появлением в отчёте — секунды или часы; от этого зависит периодичность вставок и набор встроенных агрегатов.
- Отказоустойчивость: нужны ли реплики, сколько копий данных, есть ли отдельные узлы для координации кластера.
- Текущее состояние: где будет развёрнуто, сколько места на дисках, что уже пробовали и какие узкие места видели — тяжёлые слияния, распухшее число частей, медленные соединения таблиц.
Дальше по этим вводным собираем план работ: схему таблиц, порядок ключей и партиций, политики хранения и прореживания, регламент обслуживания — и оценку внедрения по этапам. Следующий шаг — обсудим задачу и вернёмся с планом и сроками.







