Введение
- Полное переупорядочивание
- Подмножество исходной таблицы в другом порядке
- Предварительно вычисленная агрегация (аналогично materialized view), но с порядком, согласованным с агрегацией.
Как работают проекции?
- Правильное использование первичных индексов
- Предварительное вычисление агрегатов
Более эффективное хранение с _part_offset
_part_offset в
проекциях, что позволяет по-новому определять проекцию.
Теперь есть два способа задать проекцию:
- Хранить полные столбцы (исходное поведение): Проекция содержит полные данные, и их можно читать напрямую, что обеспечивает более высокую производительность, когда фильтры соответствуют порядку сортировки проекции.
-
Хранить только ключ сортировки +
_part_offset: Проекция работает как индекс. ClickHouse использует первичный индекс проекции, чтобы находить совпадающие строки, но читает фактические данные из базовой таблицы. Это уменьшает накладные расходы на хранение ценой немного большего объема операций ввода-вывода при выполнении запроса.
_part_offset.
Когда использовать проекции?
- Проекции не позволяют использовать разные TTL для исходной таблицы и (скрытой) целевой таблицы, тогда как materialized view позволяют задавать разные TTL.
- Легковесные обновления и удаления не поддерживаются для таблиц с проекциями.
- Materialized view можно выстраивать в цепочку: целевая таблица одной materialized view может быть исходной таблицей другой materialized view, и так далее. С проекциями это невозможно.
- Определения проекций не поддерживают JOIN, а materialized view — поддерживают. Однако запросы к таблицам с проекциями могут свободно использовать JOIN.
- Определения проекций не поддерживают фильтры (условие
WHERE), а materialized view — поддерживают. Однако в запросах к таблицам с проекциями фильтры можно использовать без ограничений.
- Требуется полное переупорядочивание данных. Хотя выражение в
проекции теоретически может использовать
GROUP BY,materialized view лучше подходят для поддержки агрегатов. Кроме того, оптимизатор запросов с большей вероятностью будет использовать проекции, в которых применяется простое переупорядочивание, то естьSELECT * ORDER BY x. В этом выражении можно выбрать подмножество столбцов, чтобы уменьшить объем хранилища. - Пользователей устраивает связанное с этим потенциальное увеличение объема хранилища и накладные расходы из-за двукратной записи данных. Проверьте влияние на скорость вставки и оцените дополнительные затраты на хранение.
Примеры
Фильтрация по столбцам, не входящим в первичный ключ
pickup_datetime.
Давайте напишем простой запрос, чтобы найти все идентификаторы поездок, в которых пассажиры
оставили водителю чаевые свыше $200:
Обратите внимание: поскольку мы фильтруем по tip_amount, которого нет в ORDER BY, ClickHouse
пришлось выполнить полное сканирование таблицы. Давайте ускорим этот запрос.
Чтобы сохранить исходную таблицу и результаты, мы создадим новую таблицу и скопируем данные с помощью INSERT INTO SELECT:
ALTER TABLE вместе с оператором ADD PROJECTION:
MATERIALIZE PROJECTION,
чтобы данные в ней были физически упорядочены и перезаписаны в соответствии
с указанным выше запросом:
system.query_log:
Использование проекций для ускорения запросов к UK price paid
town, ни price не входили в оператор ORDER BY при
создании таблицы:
INSERT INTO SELECT:
prj_oby_town_price, которая формирует
дополнительную (скрытую) таблицу с первичным индексом, упорядоченную по городу и цене, чтобы
оптимизировать запрос, выводящий графства для указанного города по самым
высоким ценам покупки:
mutations_sync
используется для принудительного синхронного выполнения.
Мы создаём и заполняем проекцию prj_gby_county — дополнительную (скрытую) таблицу,
которая инкрементально предвычисляет агрегатные значения avg(price) для всех 130 существующих
графств Великобритании:
Если в проекции используется предложение
GROUP BY, как в проекции prj_gby_county
выше, то движок нижележащего хранилища (скрытой) таблицы
становится AggregatingMergeTree, а все агрегатные функции преобразуются в
AggregateFunction. Это обеспечивает корректную инкрементальную агрегацию данных.uk_price_paid_with_projections
и двух её проекций:
Если теперь снова выполнить запрос, который выводит районы Лондона с тремя
самыми высокими ценами продажи, мы увидим улучшение производительности запроса:
Аналогично — для запроса, который выводит округа Великобритании с тремя самыми высокими
средними ценами продажи:
Обратите внимание, что оба запроса обращаются к исходной таблице и оба
приводят к полному сканированию таблицы (все 30,03 миллиона строк считываются с диска) до того, как мы
создали две проекции.
Также обратите внимание, что запрос, который выводит графства Лондона для трёх самых высоких
цен, считывает 2,17 миллиона строк. Когда мы напрямую использовали вторую таблицу,
оптимизированную для этого запроса, с диска было считано всего 81,92 тысячи строк.
Причина этого различия заключается в том, что в настоящее время оптимизация optimize_read_in_order,
упомянутая выше, не поддерживается для проекций.
Мы проверяем таблицу system.query_log, чтобы увидеть, что ClickHouse
автоматически использовал две проекции для двух приведённых выше запросов (см.
столбец проекции ниже):
Дополнительные примеры
CREATE AS и INSERT INTO SELECT.
Создадим проекцию
toYear(date), district и town:
optimize_use_projections, которая включена по умолчанию.
Запрос 1. Средняя цена по годам
Запрос 2. Средняя цена за год в Лондоне
Запрос 3. Самые дорогие районы
toYear(date) >= 2020):
И снова результат тот же, но обратите внимание на рост производительности у второго запроса.
Объединение проекций в одном запросе
_part_offset, появившуюся в
предыдущей версии, ClickHouse теперь может использовать несколько проекций для ускорения
одного запроса с несколькими фильтрами.
Важно, что ClickHouse по-прежнему читает данные только из одной проекции (или базовой таблицы),
но может использовать первичные индексы других проекций, чтобы отсечь ненужные части до чтения.
Это особенно полезно для запросов с фильтрацией по нескольким столбцам, каждый из
которых потенциально может соответствовать разной проекции.
В настоящее время этот механизм отсекает только части целиком. Отсечение на уровне гранул пока не поддерживается.Чтобы продемонстрировать это, мы определим таблицу (с проекциями, использующими столбцы
_part_offset)
и вставим пять строк для примера, соответствующих приведённым выше диаграммам.
Примечание: в этой таблице для наглядности используются нестандартные настройки, например гранулы по одной строке
и отключённые слияния частей, которые не рекомендуются для использования в production.
- Пять отдельных частей (по одной на каждую вставленную строку)
- Одну запись в первичном индексе на строку (в базовой таблице и в каждой проекции)
- Каждая часть содержит ровно одну строку
region и user_id.
Поскольку первичный индекс базовой таблицы строится по event_date и id, в данном случае
он не помогает, поэтому ClickHouse использует:
region_proj, чтобы отсечь части по регионуuser_id_proj, чтобы дополнительно отсечь части поuser_id
EXPLAIN projections = 1, который показывает,
как ClickHouse выбирает и применяет проекции.
EXPLAIN (показанный выше) показывает логический план запроса сверху вниз:
В итоге из базовой таблицы читается всего 1 из 5 частей.
За счет объединения анализа индексов нескольких проекций ClickHouse значительно уменьшает объем сканируемых данных,
повышая производительность при низких накладных расходах на хранение.