Ленивая материализация
В этой статье описывается, как работает ленивая материализация и как она вписывается в более широкий стек оптимизаций ввода-вывода ClickHouse. Приводится практический пример, демонстрирующий, как ленивая материализация повышает производительность запросов.
Ленивая материализация была представлена в версии 25.4 ClickHouse и включена по умолчанию.
Обзор
За прошедшие годы ClickHouse внедрил ряд многоуровневых оптимизаций для агрессивного снижения объёма операций ввода-вывода (I/O). Эти подходы составляют основу его скорости и эффективности:
| Optimization | Description |
|---|---|
| Columnar storage | Позволяет пропускать целые столбцы, которые не нужны для запроса, а также обеспечивает высокую степень сжатия за счёт группировки похожих значений, минимизируя I/O при загрузке данных. |
| Sparse primary indexes | secondary data-skipping indexes | projections | Отбрасывают нерелевантные данные, определяя, какие granules (блоки строк) могут соответствовать фильтрам по индексированным столбцам. Эти методы работают на уровне гранул и могут использоваться по отдельности или в комбинации. |
| PREWHERE | Также проверяет совпадения для фильтров по неиндексированным столбцам, чтобы как можно раньше пропускать данные, которые в противном случае были бы загружены и затем отброшены. Может работать независимо или уточнять гранулы, выбранные индексами, дополняя отсечение гранул пропуском строк, которые не соответствуют всем фильтрам по столбцам. |
| Query condition cache | Ускоряет повторяющиеся запросы, запоминая, какие гранулы в прошлый раз соответствовали всем фильтрам. Благодаря этому ClickHouse может пропускать чтение и фильтрацию гранул, которые не совпали, даже если форма запроса изменилась. |
Хотя перечисленные выше оптимизации I/O могут существенно сократить объём читаемых данных, они по-прежнему предполагают, что все столбцы для строк, прошедших условие WHERE, должны быть загружены до выполнения таких операций, как сортировка, агрегация или LIMIT. Но что, если некоторые столбцы не нужны до более позднего этапа, или часть данных, хотя и проходит условие WHERE, на самом деле никогда не требуется?
Здесь вступает в действие отложенная материализация (lazy materialization). Это ортогональное улучшение, завершающее стек оптимизаций I/O:
- Индексация вместе с
PREWHEREгарантирует, что обрабатываются только строки, соответствующие фильтрам по столбцам вWHERE. - Отложенная материализация развивает этот подход, откладывая чтение столбцов до тех пор, пока они действительно не понадобятся в соответствии с планом выполнения запроса.
Даже после фильтрации сразу загружаются только те столбцы, которые нужны для следующей операции — например, сортировки.
Остальные откладываются и, благодаря
LIMIT, часто читаются лишь частично — ровно настолько, насколько нужно для получения итогового результата. Это делает отложенную материализацию особенно эффективной для запросов класса Top N, когда для итогового результата может потребоваться всего несколько строк из некоторых, часто очень больших, столбцов.
Подробный пример
Мы настоятельно рекомендуем публикацию в блоге "ClickHouse gets lazier (and faster): Introducing lazy materialization" для детального разбора ленивой материализации. Приведённый ниже пример взят из упомянутого блог-поста и приведён здесь, чтобы продемонстрировать, как время выполнения запроса в ClickHouse может сократиться с 219 секунд до всего 139 миллисекунд (ускорение в 1576 раз) благодаря ленивой материализации.
Чтобы получить преимущества от индексации и PREWHERE, запросу нужны фильтры: по столбцам первичного ключа для индексации и по любым столбцам для PREWHERE.
Ленивая материализация органично дополняет эти механизмы, но, в отличие от других упомянутых ранее оптимизаций, она может ускорять запросы и вообще без фильтров по столбцам.
Рассмотрим следующий пример запроса, который находит отзывы Amazon с наибольшим числом голосов «полезно», независимо от даты, товара, оценки или статуса верификации, и возвращает топ-3 вместе с их заголовком, подзаголовком и полным текстом.
Сначала выполним запрос (с холодным файловым кэшем) при отключённой ленивой материализации (с использованием query_plan_optimize_lazy_materialization):
Затем запрос выполняется повторно (снова с холодным файловым кэшем), но теперь с включённой отложенной материализацией:
Обычно вам не нужно явно устанавливать query_plan_optimize_lazy_materialization = true, чтобы воспользоваться преимуществами ленивой материализации.
Этот параметр включён по умолчанию.
Рассмотрим разницу в производительности при выключенной и включённой отложенной материализации:
| Метрика | Ленивая материализация выключена | Ленивая материализация включена | Улучшение |
|---|---|---|---|
| Время выполнения | 219.071 sec | 0.139 sec | ~1576× быстрее |
| Объём прочитанных данных | 71.38 GB | 1.81 GB | ~40× меньше |
| Пиковое потребление памяти | 1.11 GiB | 3.80 MiB | ~300× меньше |
Как подтвердить ленивую материализацию в плане выполнения запроса
Вы можете убедиться, что для предыдущего запроса используется ленивая материализация, проанализировав его логический план выполнения с помощью предложения EXPLAIN:
Вы можете читать план операторов снизу вверх и увидеть, что ClickHouse откладывает чтение трёх больших столбцов типа String до выполнения сортировки и ограничения.