Создайте сводную таблицу с материализованными представлениями для быстрой аналитики временных рядов
Этот учебник показывает, как поддерживать предварительно агрегированные сводки из таблицы событий с высоким объемом с использованием материализованных представлений. Вы создадите три объекта: необработанную таблицу, сводную таблицу и материализованное представление, которое автоматически записывает данные в сводку.
Когда использовать эту схему
Используйте эту схему, когда:
- У вас есть только добавляемый поток событий (клики, просмотры страниц, IoT, логи).
- Большинство запросов - это агрегации по временным диапазонам (в минуту/час/день).
- Вы хотите постоянные чтения менее чем за секунду без повторного сканирования всех необработанных строк.
Создание таблицы необработанных событий
Примечания
PARTITION BY toYYYYMM(event_time)
оставляет партиции маленькими и легкими для удаления.ORDER BY (event_time, user_id)
поддерживает временные запросы + вторичную фильтрацию.LowCardinality(String)
экономит память для категориальных измерений.TTL
очищает необработанные данные через 90 дней (настраивайте в соответствии с вашими требованиями к хранению).
Проектирование сводной (агрегированной) таблицы
Мы будем предварительно агрегировать с почасовой разрешающей способностью. Выберите зерно, соответствующее наиболее распространенному окну анализа.
Мы храним состояния агрегатов (например, AggregateFunction(sum, ...)
), которые компактно представляют частичные агрегаты и могут быть объединены или завершены позже.
Создание материализованного представления, которое заполняет сводку
Это материализованное представление срабатывает автоматически при вставках в events_raw
и записывает состояния агрегатов в сводку.
Запрос сводки
Вы можете либо объединить состояния во время чтения, либо завершить их:
- Объединить во время чтения
- Завершить с -Final
Если вы ожидаете, что чтения всегда будут обращаться к сводке, вы можете создать второе материализованное представление, которое записывает завершенные числа в «обычную» таблицу MergeTree
с тем же зерном 1ч.
Состояния предоставляют большую гибкость, в то время как завершенные числа дают немного более простые чтения.
Фильтрация по полям в первичном ключе для лучшей производительности
Вы можете использовать команду EXPLAIN
, чтобы увидеть, как индекс используется для отсечения данных:
План выполнения запроса выше показывает три типа индексов, которые используются:
минмакс индекс, индекс партиции и первичный ключ.
Каждый индекс использует поля, указанные в нашем первичном ключе: (bucket_start, country, event_type)
.
Для наилучшей производительности фильтрации вам следует убедиться, что ваши запросы используют поля первичного ключа для отсечения данных.
Обычные изменения
- Разные зерна: добавьте дневную сводку:
Затем второе материализованное представление:
- Сжатие: применяйте кодеки к большим колонкам (например:
Codec(ZSTD(3))
) в необработанной таблице. - Контроль затрат: перемещайте тяжелое хранение в необработанную таблицу и сохраняйте долгоживущие сводки.
- Заполнение: при загрузке исторических данных вставляйте в
events_raw
и позволяйте материализованному представлению автоматически создавать сводки. Для существующих строк используйтеPOPULATE
при создании материализованного представления, если это подходит, илиINSERT SELECT
.
Очистка и удержание
- Увеличьте
TTL
необработанных данных (например, 30/90 дней), но сохраняйте сводки дольше (например, 1 год). - Вы также можете использовать TTL для перемещения старых частей в более дешевое хранилище, если включена многоуровневая система.
Устранение неполадок
- Материализованное представление не обновляется? Проверьте, что вставки идут в events_raw (а не в сводную таблицу), и что цель материализованного представления правильная (
TO events_rollup_1h
). - Медленные запросы? Убедитесь, что они проходят через сводку (запросите таблицу сводки напрямую) и что временные фильтры соответствуют зерну сводки.
- Неполадки с заполнением? Используйте
SYSTEM FLUSH LOGS
и проверяйтеsystem.query_log
/system.parts
, чтобы подтвердить вставки и объединения.