跳转到主内容
跳转到主内容

惰性物化

本文介绍了惰性物化的工作原理,以及它在 ClickHouse 更广泛的 I/O 优化栈中的作用。 文中给出了一个实际示例,演示惰性物化如何提升查询性能。

自 25.4 版本起提供

惰性物化在 ClickHouse 25.4 版本中引入,并默认启用。

概览

多年来,ClickHouse 引入了一系列分层优化来大幅减少 I/O。 这些技术构成了其高速与高效的基础:

OptimizationDescription
Columnar storage允许跳过查询不需要的整列数据,并通过将相似值分组来实现高压缩率,从而在数据加载过程中将 I/O 最小化。
Sparse primary indexes | secondary data-skipping indexes | projections通过识别哪些 granules(行块)可能匹配_已建立索引的列_上的过滤条件来裁剪无关数据。这些技术在 granule 级别工作,可以单独使用,也可以组合使用。
PREWHERE同样会检查_未建立索引_列上的过滤条件,以尽早跳过那些本来会被加载然后丢弃的数据。它可以独立工作,也可以在索引选出的 granule 基础上进一步细化,通过跳过那些不满足_所有_列过滤条件的行,来补充 granule 级别的裁剪。
Query condition cache通过记住上一次哪些 granule 匹配了全部过滤条件来加速重复查询。即使查询结构发生变化,ClickHouse 也可以跳过读取和过滤那些之前未匹配的 granule。

尽管上述 I/O 优化可以显著减少读取的数据量,但它们仍然默认:所有通过 WHERE 子句筛选的行,其所有列都必须在执行排序、聚合或 LIMIT 等操作之前被加载。 但如果有些列要到后续步骤才会用到,或者部分数据虽然通过了 WHERE 子句筛选,但实际上根本不会被用到,又该怎么办?

这就是 lazy materialization 发挥作用的地方。它是一个正交的增强功能,使 I/O 优化体系更加完整:

  • 索引与 PREWHERE 结合,确保只处理在 WHERE 子句中匹配列过滤条件的行。
  • Lazy materialization 在此基础上,通过将列读取推迟到查询执行计划实际需要它们时才进行。 即便在过滤之后,也只会立刻加载下一步操作(例如排序)所需的列。 其他列会被延后读取,并且由于 LIMIT 的存在,通常只会被部分读取,仅需足以产生最终结果的数据即可。 这使得 lazy materialization 对 Top N 查询尤其强大,因为最终结果可能只需要来自某些(通常很大)列的少量行。

一个完整示例

我们强烈推荐阅读博文 "ClickHouse gets lazier (and faster): Introducing lazy materialization",以深入了解惰性物化。下面的示例取自前述博文,并在此复现,用于演示在启用惰性物化后,如何将一个 ClickHouse 查询的执行时间从 219 秒减少到仅 139 毫秒(加速 1576×)。

为了从索引和 PREWHERE 中获益,查询需要包含过滤条件:对主键列的过滤用于索引,对任意列的过滤用于 PREWHERE。 惰性物化可以很自然地叠加在这些机制之上,但与前面提到的其他优化不同的是,它同样可以加速完全没有列过滤条件的查询。

考虑下列示例查询:在不考虑日期、产品、评分或验证状态的前提下,它查找获得最多 helpful votes 的 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 设为 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 列的读取推迟到排序和 LIMIT 之后才执行。