Перейти к основному содержимому
Перейти к основному содержимому

Ленивая материализация

В этой статье описывается, как работает ленивая материализация и как она вписывается в более широкий стек оптимизаций ввода-вывода ClickHouse. Приводится практический пример, демонстрирующий, как ленивая материализация повышает производительность запросов.

Доступно начиная с версии 25.4

Ленивая материализация была представлена в версии 25.4 ClickHouse и включена по умолчанию.

Обзор

За прошедшие годы ClickHouse внедрил ряд многоуровневых оптимизаций для агрессивного снижения объёма операций ввода-вывода (I/O). Эти подходы составляют основу его скорости и эффективности:

OptimizationDescription
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):

SELECT
    helpful_votes,
    product_title,
    review_headline,
    review_body
FROM amazon.amazon_reviews
ORDER BY helpful_votes DESC
LIMIT 3
FORMAT Vertical
SETTINGS
    query_plan_optimize_lazy_materialization = false;
Row 1:
──────
helpful_votes:   47524
product_title:   Kindle: Amazon's Original Wireless Reading Device (1st generation)
review_headline: Why and how the Kindle changes everything
review_body:     This is less a \"pros and cons\" review than a hopefully use...

Row 2:
──────
helpful_votes:   41393
product_title:   BIC Cristal For Her Ball Pen, 1.0mm, Black, 16ct (MSLP16-Blk)
review_headline: FINALLY!
review_body:     Someone has answered my gentle prayers and FINALLY designed ...

Row 3:
──────
helpful_votes:   41278
product_title:   The Mountain Kids 100% Cotton Three Wolf Moon T-Shirt
review_headline: Dual Function Design
review_body:     This item has wolves on it which makes it intrinsically swee...

# highlight-start
0 rows in set. Elapsed: 219.071 sec. Processed 150.96 million rows, 71.38 GB (689.08 thousand rows/s., 325.81 MB/s.)
Peak memory usage: 1.11 GiB.
# highlight-end

Затем запрос выполняется повторно (снова с холодным файловым кэшем), но теперь с включённой отложенной материализацией:

SELECT
    helpful_votes,
    product_title,
    review_headline,
    review_body
FROM amazon.amazon_reviews
ORDER BY helpful_votes DESC
LIMIT 3
FORMAT Vertical
SETTINGS
-- highlight-next-line
query_plan_optimize_lazy_materialization = true;
Совет

Обычно вам не нужно явно устанавливать query_plan_optimize_lazy_materialization = true, чтобы воспользоваться преимуществами ленивой материализации. Этот параметр включён по умолчанию.

Row 1:
──────
helpful_votes:   47524
product_title:   Kindle: Amazon's Original Wireless Reading Device (1st generation)
review_headline: Why and how the Kindle changes everything
review_body:     This is less a \"pros and cons\" review than a hopefully use...

Row 2:
──────
helpful_votes:   41393
product_title:   BIC Cristal For Her Ball Pen, 1.0mm, Black, 16ct (MSLP16-Blk)
review_headline: FINALLY!
review_body:     Someone has answered my gentle prayers and FINALLY designed ...

Row 3:
──────
helpful_votes:   41278
product_title:   The Mountain Kids 100% Cotton Three Wolf Moon T-Shirt
review_headline: Dual Function Design
review_body:     This item has wolves on it which makes it intrinsically swee...

# highlight-start
0 rows in set. Elapsed: 0.139 sec. Processed 150.96 million rows, 1.81 GB (1.09 billion rows/s., 13.06 GB/s.)
Peak memory usage: 3.80 MiB.
# highlight-end

Рассмотрим разницу в производительности при выключенной и включённой отложенной материализации:

МетрикаЛенивая материализация выключенаЛенивая материализация включенаУлучшение
Время выполнения219.071 sec0.139 sec~1576× быстрее
Объём прочитанных данных71.38 GB1.81 GB~40× меньше
Пиковое потребление памяти1.11 GiB3.80 MiB~300× меньше

Как подтвердить ленивую материализацию в плане выполнения запроса

Вы можете убедиться, что для предыдущего запроса используется ленивая материализация, проанализировав его логический план выполнения с помощью предложения EXPLAIN:

EXPLAIN actions = 1
SELECT
    helpful_votes,
    product_title,
    review_headline,
    review_body
FROM amazon.amazon_reviews
ORDER BY helpful_votes DESC
LIMIT 3
SETTINGS
    query_plan_optimize_lazy_materialization = true;
...
# highlight-next-line
Lazily read columns: review_headline, review_body, product_title
  Limit
    Sorting
      ReadFromMergeTree

Вы можете читать план операторов снизу вверх и увидеть, что ClickHouse откладывает чтение трёх больших столбцов типа String до выполнения сортировки и ограничения.