Производительность запросов по временным рядам
После оптимизации хранилища следующим шагом является улучшение производительности запросов.
В этом разделе мы рассмотрим два ключевых подхода: оптимизацию ключей ORDER BY и использование материализованных представлений.
Мы увидим, как эти подходы позволяют сократить время выполнения запросов с секунд до миллисекунд.
Оптимизация ключей ORDER BY
Прежде чем переходить к другим оптимизациям, следует оптимизировать ключи ORDER BY, чтобы ClickHouse выдавал максимально быстрые результаты.
Выбор правильного ключа во многом зависит от запросов, которые вы планируете выполнять. Предположим, что большинство наших запросов фильтруют данные по столбцам project и subproject.
В этом случае имеет смысл добавить их в ключ сортировки, а также столбец time, так как мы также выполняем запросы по времени.
Создадим ещё одну версию таблицы с теми же типами столбцов, что и в wikistat, но с сортировкой по (project, subproject, time).
Теперь давайте сравним несколько запросов, чтобы оценить, насколько выбранное выражение ключа сортировки влияет на производительность. Обратите внимание, что мы не применяли предыдущие оптимизации типов данных и кодеков, поэтому любые различия в производительности запросов обусловлены только порядком сортировки.
| Запрос | (time) | (project, subproject, time) |
|---|---|---|
| 2.381 с | 1.660 с | |
| 2.148 с | 0.058 с | |
| 2.192 с | 0.012 с | |
| 2.968 с | 0.010 с |
Материализованные представления
Другой вариант — использовать материализованные представления для агрегирования и хранения результатов популярных запросов. Эти результаты можно запрашивать вместо исходной таблицы. Предположим, что следующий запрос в нашем случае выполняется довольно часто:
Создание материализованного представления
Мы можем создать следующее материализованное представление:
Заполнение целевой таблицы задним числом
Эта целевая таблица будет заполняться только при вставке новых записей в таблицу wikistat, поэтому нам нужно выполнить заполнение задним числом.
Самый простой способ сделать это — использовать оператор INSERT INTO SELECT для непосредственной вставки данных в целевую таблицу материализованного представления с использованием SELECT‑запроса (трансформации) этого представления:
В зависимости от кардинальности исходного набора данных (у нас 1 миллиард строк!), этот подход может быть очень требовательным к памяти. В качестве альтернативы можно использовать вариант, который требует минимального объёма памяти:
- Создание временной таблицы с движком Null
- Подключение копии обычно используемого материализованного представления к этой временной таблице
- Использование запроса
INSERT INTO SELECTдля копирования всех данных из исходного набора данных во временную таблицу - Удаление временной таблицы и временного материализованного представления.
При таком подходе строки из исходного набора данных копируются блоками во временную таблицу (которая при этом не сохраняет сами строки), и для каждого блока строк вычисляется промежуточное состояние и записывается в целевую таблицу, где эти состояния постепенно объединяются в фоновом режиме.
Далее мы создадим материализованное представление, которое будет читать из wikistat_backfill и записывать в wikistat_top
И наконец, мы заполним wikistat_backfill из исходной таблицы wikistat:
Когда запрос завершится, мы можем удалить таблицу бэкфилла и материализованное представление:
Теперь вместо исходной таблицы мы можем выполнять запросы к материализованному представлению:
Улучшение производительности здесь колоссальное. Раньше на вычисление результата этого запроса уходило чуть больше 2 секунд, а теперь требуется всего 4 миллисекунды.