ClickHouse 与 Parquet(第一篇) :构建快速 Lakehouse 分析的坚实基础

图片

本文字数:13159;估计阅读时间:33 分钟

作者:Tom Schreiber

本文在公众号【ClickHouselnc】首发

图片

简要摘要:ClickHouse 天然适配 Parquet 格式。Parquet 是 Apache Iceberg 和 Delta Lake 等开放表格式背后的核心存储形式,ClickHouse 多年来持续针对其进行了深入优化。

本文将深入解析 ClickHouse 查询引擎的内部机制,展示其如何无需数据导入即可直接查询 Parquet 文件,并且查询性能往往超过很多系统在自有格式下的表现。我们还将介绍未来的一些优化方向。

这是我们新系列的开篇文章,聚焦 ClickHouse 如何从底层能力出发,构建起高性能的 Lakehouse 分析能力。

值得提前指出的是:ClickHouse 并不是“正在为 Lakehouse 做准备”,它早已准备就绪。

从偶然到有意:为 Lakehouse 而生的引擎

有时候,技术的发展方向恰好与既有路径不谋而合。

尽管 ClickHouse 的设计初衷并非为 Lakehouse 架构服务(当 Iceberg 和 Delta Lake 出现时,ClickHouse 已是一个成熟的数据库系统),但它恰好拥有高度契合的能力。ClickHouse 对 Parquet 提供一等支持,并支持直接查询文件格式,早已原生支持了许多 Lakehouse 模式。

ClickHouse 查询引擎始终将“支持多种格式、支持远程位置的数据访问”视为核心功能。无论是先导入再查询,还是直接查询外部数据,这种灵活性是 ClickHouse 一贯的优势。

如今所谓的 Lakehouse 架构,其核心理念——任意部署、任意查询、灵活访问数据——正是 ClickHouse 多年来不断强化的能力体现:

任意部署:ClickHouse 引擎可灵活部署于本地服务器、云平台、独立模式,甚至嵌入式运行环境中。

图片

任意查询:除了为 MergeTree 原生表格式进行了极致优化,ClickHouse 还能直接查询外部格式,无需预先导入。大多数数据库在运行查询前都必须将 Parquet 等文件加载转换为内部格式,而 ClickHouse 可以跳过这个步骤。查询引擎原生支持超过 70 种文件格式,并提供完整 SQL 功能:包括 JOIN、窗口函数和 160 多种聚合函数,均可在原始文件上直接执行。支持的格式包括 Parquet、JSON、CSV、Arrow 等。

图片

灵活访问数据:ClickHouse 提供 80 多种内置集成,可与各种外部系统和对象存储平台无缝连接,例如 S3、GCP、Azure 等。

这些能力使 ClickHouse 成为 Lakehouse 场景的理想技术基础。ClickHouse 能够直接查询存储在对象存储上的 Apache Iceberg 表,这些表大多采用 Parquet 格式保存数据。

图片

同时,ClickHouse 查询引擎支持多种运行模式,可灵活部署在各种场景中:无论是靠近对象存储的计算节点、多租户 SaaS 系统内部、嵌入在使用 pandas 的 Jupyter Notebook 中,还是作为无状态函数运行于 AWS Lambda 环境中,都可以胜任。

引擎剖析:ClickHouse 如何高效查询 Parquet 文件

本文将深入介绍 ClickHouse 如何处理 Lakehouse 架构中关键的数据存储格式之一:Parquet。

  • ClickHouse 目前查询 Parquet 文件的性能究竟如何?

  • 与其原生的 MergeTree 表相比表现如何?

  • 在 ClickHouse 支持的 70 多种外部格式中,还有哪些更快的选择?

  • 未来还有哪些优化方向?

这些问题将在本文中一一解答。文章也开启了一个全新系列,聚焦 ClickHouse 作为高性能 Lakehouse 引擎的技术演进。本篇将从基础的数据格式 Parquet 入手,该格式正是 Apache Iceberg 和 Delta Lake 等开放表格式的底层支撑。

我们将先解析当前版本中 Parquet 读取器的实现机制,并探讨其性能优势。随后,我们将在真实分析场景中对其进行基准测试,展示当前表现,并预告未来的改进方向。

正如前文所述,ClickHouse 查询引擎支持直接查询包括 Parquet 在内的 70 多种文件格式,完全无需预先导入数据。针对不同格式的读取器可以作为插件集成进引擎,工作流程如下:

图片

1. 读取器读取外部文件并解析内容;

2. 将解析后的数据转换为 ClickHouse 的内存格式;

3. 查询引擎对其执行查询逻辑;

4. 输出最终结果。

本节将重点聚焦 Parquet 读取器,它与 ClickHouse 查询引擎的协同,是实现高性能文件查询的核心组件。

当前 Parquet 读取器的机制与演进方向

值得一提的是,虽然 MergeTree 依然是 ClickHouse 的原生表引擎,但我们早在三年前就已着手优化对 Parquet 的支持。这一努力旨在将 ClickHouse 打造为全球最快的 Parquet 查询引擎。

目前的 Parquet 读取器使用 Arrow 库将文件解析为 Arrow 格式,再转换为 ClickHouse 的原生内存格式,以供执行查询。在下文中,我们将具体分析这一实现的能力边界。

一个全新的原生 Parquet 读取器正在开发中,目标是完全移除 Arrow 中间层,直接将文件加载进 ClickHouse 内存结构。同时,该实现将带来更强的并行性和更高的 I/O 效率。项目名称是 “Yet Another Parquet Reader”(YAPR)——顾名思义,这是 ClickHouse 的第三代 Parquet 读取器。第一代(input_format_parquet_use_native_reader)未完成;第二代(v2)虽有 Pull Request,但未正式上线;而第三代 v3 正在积极推进中。

我们本可以等新版本完成后再发布本篇文章,但当前版本的基准测试为后续性能对比提供了可靠的参考基线。在后续的文章中,我们将重点展示新读取器带来的提升。

当前 Parquet 查询性能主要取决于两个因素:

  1. 并行度:ClickHouse 可同时读取的文件数量,以及文件内部可并发处理的非重叠区块越多,整体吞吐量和查询速度就越高;

  2. I/O 降低程度:系统跳过不相关数据的能力越强,查询完成所需时间就越短。

接下来的两个章节将详细解析 ClickHouse 查询引擎与当前 Parquet 读取器如何协同实现这两点优化,并介绍即将发布的原生读取器的性能改进。同时也会涵盖一些配置参数,帮助用户调优性能表现。

并行性解析:ClickHouse 查询引擎如何扩展性能

在讲解 ClickHouse 如何高效并行处理 Parquet 文件查询之前,我们先简要了解一下 Parquet 文件在磁盘中的物理数据布局。Parquet 的组织结构直接决定了数据是否易于被拆分为独立的处理单元,也决定了查询执行时能够实现的并行度有多高。

下图展示了 Web 分析数据集(后文用于基准测试)被存储为 Parquet 文件时在磁盘上的组织方式示意:

图片

在 Parquet 格式中,数据集从逻辑上来看是由行和列组成的,而实际存储时则划分为一个或多个文件。每个文件采用以下三层结构组织数据:

① 行组(Row Groups):数据首先被水平划分为多个行组。默认情况下,ClickHouse 写入的每个 Parquet 行组包含约 100 万行或 500 MB 数据(压缩前)。

② 列块(Column Chunks):每个行组被垂直划分为多个列块,每列对应一个列块,存储该列在该行组中所有行的值。

③ 数据页(Data Pages):在列块中,数据被进一步划分为多个数据页。默认情况下,ClickHouse 写入的数据页大小为 1 MB(压缩前),每页包含一批已编码和压缩的列值,其具体数量依赖于列的数据类型和编码方式。

说明:为简化展示,上图中每个行组仅显示了 6 行,每列的数据页中展示了 3 个值。

理清数据布局后,我们再来看 ClickHouse 查询引擎如何与 Parquet 读取器协同,在多核 CPU 上并行处理数据,最大化查询性能。

ClickHouse 不只是“支持任意环境部署”和“查询任意数据”,它还在执行过程中实现了几乎所有操作的并行化,特别是在处理 Parquet 查询时。下图展示了查询执行过程中,Parquet 读取器与 ClickHouse 查询引擎如何在多个层次上协同实现并行处理:

图片

① 行组预取线程(Parallel prefetch threads)  

在单个 Parquet 文件内部,ClickHouse 可并行预取多个行组(row group)。默认启用了四个预取线程(由 max_download_threads 参数控制)。当解析任务已达到最大并行度,或遇到如网络加载数据等可能阻塞解析的场景时,预取机制将被激活。

② 解析线程(Parallel parsing threads)

多个解析线程可以并行读取同一文件内不同行组的数据。若启用了预取功能,这些线程将从预取缓冲区中读取数据;否则会直接从文件中读取。解析线程的总数量由 max_parsing_threads 控制,默认值等于系统中可用的 CPU 核心数。

③ 文件流并行(Parallel file streams) 

多个 Parquet 文件可同时被处理,每个文件流拥有独立的预取和解析线程。这种跨文件并行化能显著提升整体吞吐量。文件流的数量会在查询编译阶段动态确定。

④ 处理通道并行(Parallel processing lanes)  

从 Parquet 读取器传入查询引擎的数据以流式块的形式处理。过滤、聚合、排序等操作会在多个并行处理通道中执行,以充分利用 CPU 并发能力。处理通道数量由 max_threads 控制,默认同样等于可用 CPU 核心数。

无论你是在处理大量小文件,还是单个大文件,ClickHouse 都能自动平衡资源分配,保障查询执行的高效性。在编译查询时,ClickHouse 会解析匹配 Parquet 文件路径的表达式(如使用 file 表函数),根据实际匹配到的文件数量(num_files)自动构建最优的物理执行计划:

  • 多文件场景:每个小文件分配一个线程,并在多个文件流中并发处理;

  • 大文件场景:对单个文件使用多个线程并行解析其行组。

底层调度遵循以下两条简单规则:

  • 文件流数量 = min(max_threads, num_files)

  • 每个文件的解析线程数 = max(max_parsing_threads / num_files, 1)。其中 `/` 表示向下取整,max() 保证每个文件至少有一个解析线程。

接下来我们将通过三个具体示例,进一步说明文件流与线程资源的实际分配效果。

并行性实战演示:三个典型示例

本节通过三个实际查询示例,结合 EXPLAIN 子句展示 ClickHouse 查询 Parquet 文件时的并行处理机制。我们使用 ClickBench 的第 11 号查询对 Web 分析数据集进行操作,该查询包含过滤、聚合和排序,并在最后使用 LIMIT 限制返回结果数。通过启用 graph 选项,ClickHouse 可以以 DOT 图形格式输出查询计划,并可使用 Graphviz 工具渲染为 PDF 以便分析。

示例一:单个 Parquet 文件 + 4 核心

在第一个示例中,我们将 max_threads 和 max_parsing_threads 设置为 4(模拟一个四核系统,便于观察计划),并使用仅匹配一个 Parquet 文件的路径。

clickhouse local --query "
EXPLAIN Pipeline graph = 1, compact = 0
SELECT MobilePhoneModel, COUNT(DISTINCT UserID) AS u
FROM file('./output/Parquet/100000000/1000000/sorted/zstd/chunk_00.parquet')
WHERE MobilePhoneModel <> ''
GROUP BY MobilePhoneModel
ORDER BY u DESC LIMIT 10
SETTINGS max_threads = 4, max_parsing_threads = 4;
" | dot -Tpdf > pipeline.pdf

图片

查询引擎根据公式 min(max_threads = 4, num_files = 1),创建了一个文件流,并使用四个并行处理通道(max_threads = 4)对该文件进行处理。

查询管道从左到右依次执行。第一个 Resize 操作符将解析后的数据均匀分发到 4 个处理通道,各通道内执行过滤和部分聚合。第二个 Resize 阶段负责再平衡数据流,以应对过滤条件选择性不同带来的负载不均问题。这种动态调整机制可确保资源利用最大化——更快的通道可协助处理慢通道的数据,从而提升整体性能。排序操作分三步完成:

  1. PartialSortingTransform:每个通道内对块内数据进行局部排序;

  2. MergeSortingTransform:在通道内通过二路合并维护有序数据流;

  3. MergingSortedTransform:跨通道执行 k 路归并,并在最终阶段执行 LIMIT 操作输出结果。

虽然当前无法通过日志直接观察每个文件的解析线程数,但根据 max(max_parsing_threads / num_files, 1) 的规则,在单文件场景下,max_parsing_threads = 4 时,会有多个行组被 4 个线程并行处理。

示例二:两个 Parquet 文件 + 4 核心

在第二个示例中,我们仍设置 max_threads = 4 和 max_parsing_threads = 4,但此次路径模式匹配了两个 Parquet 文件。

clickhouse local --query "
EXPLAIN Pipeline graph = 1, compact = 0
SELECT MobilePhoneModel, COUNT(DISTINCT UserID) AS u
FROM file('./output/Parquet/100000000/1000000/sorted/zstd/chunk_0{0..1}.parquet')
WHERE MobilePhoneModel <> ''
GROUP BY MobilePhoneModel
ORDER BY u DESC LIMIT 10
SETTINGS max_threads = 4, max_parsing_threads = 4;
" | dot -Tpdf > pipeline.pdf

图片

根据并行调度规则,查询引擎会启动两个并行文件流(min(4, 2)),并继续使用 4 个并行处理通道(max_threads = 4)来执行查询。

每个文件流所分配的解析线程数则变为 2(max(4 / 2, 1)),确保两个文件可被并行解析,同时充分利用系统资源。

示例三:100 个 Parquet 文件 + 32 核 CPU

在第三个示例中,我们不再限制 max_threads 和 max_parsing_threads 的取值。在拥有 32 核心的测试机器上,这两个参数默认都设置为 32。文件路径模式匹配了整个示例数据集中 100 个 Parquet 文件,实现全量并行读取。

clickhouse local --query "
EXPLAIN Pipeline graph = 1, compact = 0
SELECT MobilePhoneModel, COUNT(DISTINCT UserID) AS u
FROM file('./output/Parquet/100000000/1000000/sorted/zstd/chunk_*.parquet')
WHERE MobilePhoneModel <> ''
GROUP BY MobilePhoneModel
ORDER BY u DESC LIMIT 10;
" | dot -Tpdf > pipeline.pdf

图片

横向扩展:集群级并行执行

前面的示例聚焦在单机层面。ClickHouse 还支持在集群模式下横向扩展,将 Parquet 查询任务分发至多个节点,在整个集群范围内并发处理。

图片

查询发起节点(即接收 SQL 的主节点)首先解析文件路径表达式,然后连接其他节点的引擎实例,将待处理的 Parquet 文件动态分配给各节点。各个节点处理完各自的任务后,会继续向主节点请求新的文件任务,直至所有文件处理完毕。

下一阶段的性能优化:更智能、粒度更细的并行机制

新一代原生 Parquet 读取器不仅移除了对 Arrow 库的依赖,避免了中间内存拷贝带来的性能损耗,还引入了更精细、更具弹性的并行机制:

  • 行组内列级并行:不仅支持在文件层和行组层并行处理,还能在同一行组中并行读取多个列。这一机制在行组数量有限时,能显著提高 CPU 利用率。

  • I/O 请求合并优化:通过预先注册所需的数据块范围,读取器可以将临近的小型 I/O 请求合并为更大、更连续的读操作,特别适用于高延迟存储环境(如远程对象存储)。

  • 并行调度感知机制:引擎可根据查询执行阶段及内存占用动态调节并行度。例如,对 Bloom Filter 等轻量结构采用高并发,而对大型列数据则降低并发度,以避免内存资源争用。

这些优化意在更全面地挖掘硬件性能潜力,确保无论数据量大小、查询复杂度如何,系统都能保持稳定的高吞吐。

在并行机制之后,影响 Parquet 查询性能的另一个核心因素是 I/O 减少。减少无关数据的扫描、解析和处理,是加快查询的关键。接下来我们将介绍 ClickHouse 当前已实现的 I/O 优化技术,以及新版本中原生读取器即将引入的进一步改进。

I/O 优化:跳过哪些内容,以及背后的机制

在 ClickHouse 中提升 Parquet 查询性能的另一个关键手段,是尽可能减少无效的数据读取。得益于 Parquet 的列式结构和丰富的元数据,ClickHouse 可以利用多种机制智能地跳过无关数据块。

列裁剪(Column Projection) 

Parquet 是列式存储格式,因此 ClickHouse 查询引擎仅访问查询中真正需要的列,未被引用的列数据将完全跳过。

压缩与编码优化

Parquet 支持按列压缩和多种编码方式(如字典编码、游程编码),显著减少磁盘读写量。ClickHouse 可结合使用这些机制,仅解压和解析查询所需的部分数据。通过元数据,甚至可以跳过整段列数据而无需解压。

在页级(page-level),Parquet 还支持:

  • 页编码(Page encoding):包括字典编码、游程编码等;

  • 页压缩(Page compression):支持 Snappy、LZ4、ZSTD 等压缩算法。

谓词下推(Predicate Pushdown)

Parquet 通过在多个层级嵌入元数据,实现了较粗粒度的谓词下推能力。前文未展示这些元数据信息,下图对其进行了补充说明,依然采用我们的 Web 分析数据集进行示意。为便于阅读,图中每个行组仅包含六行,每列的数据页仅展示三个值。

图片

接下来我们将详细解释图中新增的各类元数据结构及其作用。

① 列块级字典过滤

低基数列通常采用字典编码,每个列块都包含一个唯一值字典。当查询条件为 WHERE status = 'cancelled' 这类值匹配时,读取器可预先查字典判断是否需要加载该列块。

② 页级最小/最大值过滤 

数据页中可记录列数据的最小值与最大值。当使用范围条件如 WHERE amount > 1000 时,若页内所有值都小于 1000,该页可直接跳过。该方法在已排序数据中尤其高效。

③ 列块级 Bloom 过滤器 

可选地,Parquet 为每列生成 Bloom filter,快速判断某值是否可能存在。若确认不存在,该列数据即被跳过。ClickHouse 当前已支持此功能,可通过 input_format_parquet_bloom_filter_push_down 启用。

④ 列块级最小/最大值过滤 

在行组层面记录每列的 min/max 统计信息。若数据范围与查询条件不符,可跳过整列块。该功能在 ClickHouse 中通过 input_format_parquet_filter_push_down 控制。

让我们展望一下当前的支持情况与未来。

目前,ClickHouse Parquet 读取器已支持上述第③与第④种优化机制。

正在开发的原生读取器将进一步引入:

① 字典过滤

② 页级 min/max 过滤

除此之外,ClickHouse 的新读取器还将结合自身的独有优化机制,包括PREWHERE 子句执行提前裁剪数据,延迟物化(Lazy materialization)避免不必要列读取。这些机制将更深度地减少读取量,进一步提升分析型查询的执行效率。

至此,我们已全面回顾了当前 Parquet 读取器在 ClickHouse 中如何通过并行处理与 I/O 优化提升查询性能,并展望了即将到来的增强能力。接下来,我们将通过一组真实的分析型查询,看看其在实际场景中的表现如何。

Parquet 查询性能实测:基准测试结果解析

在之前的 FastFormats 基准测试中,我们评估了各种数据格式在“写入”阶段的性能——也就是当客户端将数据推送至 ClickHouse 时,不同格式在表导入过程中的效率。

而这次,我们将焦点转向“读取”端:测试 ClickHouse 查询引擎在无需数据导入的情况下,直接对不同格式执行查询的性能表现。

图片

测试环境配置

本次基准测试复用了 FastFormats 相同的测试环境,具体如下:

  • 硬件:AWS EC2 m6i.8xlarge(32 核 vCPU,128 GiB 内存,1 TiB gp3 SSD)

  • 数据集:匿名化 Web 分析数据集(与 ClickBench 中使用的数据一致)

  • ClickHouse 版本:25.4.1,操作系统为 Ubuntu Linux 24.04

测试数据生成方式

我们基于同一套基准逻辑,自动生成包含不同组合的文件样本,维度包括:

  • 文件大小 / 数量:将 1 亿条记录划分为 100 个文件,每个文件含 100 万行;

  • 数据格式:如 Parquet、JSON、Arrow 等;

  • 预排序选项:是否按原始表排序键进行排序;

  • 压缩方式:无压缩、LZ4、ZSTD 三种。

每种格式对应 6 个版本(排序 × 压缩 组合),确保覆盖常见使用场景。

查询执行方式:ClickBench 在文件上的运行

我们对基准框架进行了扩展,使其能够在每组测试文件上依次运行 ClickBench 的全部 43 条官方查询指令。每条查询均单独执行三次,并在第一次运行前清空操作系统页缓存。第一轮用于测量冷启动性能;第二、三轮中取最短耗时代表热启动性能。值得注意的是,我们通过 file 表函数直接在文件数据上执行查询,未将数据导入 ClickHouse 表。

查询模式选择

为了获取比传统延迟和内存更细致的运行时指标,我们采用 ClickHouse 的服务器模式运行查询:通过 clickhouse-client 连接 clickhouse-server 实例。这种方式允许我们借助系统表 query_log 收集丰富的查询统计数据。虽然本地也可以使用 clickhouse-local 执行文件查询,但服务器模式能提供更完整的运行时观测能力。需要说明的是,除用于对比的 MergeTree 测试外,本轮所有查询均未将文件数据导入数据库——实际执行逻辑与本地模式完全一致,使用的都是同一个查询引擎与底层代码路径。

测量指标与评估维度

在本次基准测试中,我们通过查询日志系统(query_log)跟踪并记录了每条查询的以下关键性能指标:

  • 运行时长:单次查询从开始到完成所耗费的总时间;

  • 内存占用:执行过程中出现的最大内存使用量;

  • 读取行数与字节数:从底层数据源中实际读取的行数与数据总量;

  • 参与线程数与峰值并发线程数:表示查询过程中有多少线程参与执行,以及并发执行的最大线程数;

  • 磁盘读取等待时间(DiskReadElapsedMicroseconds):系统层面用于 I/O 读取的总耗时(以微秒计)。

Parquet 实战表现:与其它格式的性能对比

接下来我们将展示 ClickHouse 的 Parquet 读取器如何将其在并行化与 I/O 优化方面的优势,转化为实际分析型查询中的性能成果。首先从一条具有代表性的 ClickBench 查询出发,逐步扩展到全量 43 条查询的整体表现。

关于对比基准:Parquet vs MergeTree

为了提供参考,我们也在相同硬件配置下,使用原生 MergeTree 表执行了相同查询。

需要指出的是,将 Parquet 与 MergeTree 进行性能对比并不完全对等:Parquet 是通用文件格式,而 MergeTree 是 ClickHouse 内部深度优化的专用表引擎,历经多年调优与集成。因此,Parquet 在当前阶段尚不具备超越 MergeTree 的预期。

更公平的对比对象应是 Parquet 与基于它构建的开放表格式,如 Iceberg 或 Delta Lake,因为这些格式会引入更复杂的元数据与索引机制。我们将在后续文章中深入探讨这一类对比。本篇则专注于底层能力,重点是评估 ClickHouse 当前对 Parquet 文件的原生查询能力表现如何,并识别出优化空间。随着 ClickHouse 在 Lakehouse 架构上的持续投入,这种性能差距正逐步缩小。

查询 41 表现概览:并行处理与 I/O 效率

下图展示了 8 种输入格式在执行 ClickBench 查询 41 时的核心性能指标对比,包括冷启动与热启动下的查询耗时;查询过程中参与的最大并发线程数(通过 peak_threads_usage 字段采集);读取的数据量(以字节为单位)。

查询 41 是典型的分析型工作负载,包含筛选、聚合、排序和限制返回条目等操作。为了保证格式对比的公平性,所有文件均采用预排序 + ZSTD 压缩方式存储。

同时,我们也将 MergeTree 表在相同数据布局下的表现作为参考基准纳入图表,尽管这属于“异构对比”,但能帮助我们更好地观察当前 Parquet 查询能力的定位。

图片

本图重点展示了各输入格式在执行查询时,ClickHouse 实际处理的数据量。图表按照冷启动耗时从快到慢进行排序,反映了并行度与 I/O 优化带来的直接影响。主要结论如下:

1. MergeTree(原生表格式)

作为 ClickHouse 内部高度优化的存储引擎,MergeTree 能够发挥最强的 I/O 跳过能力,全面利用包括 PREWHERE 与延迟物化(lazy materialization)在内的多项优化机制。本例中,尽管数据总量为 10 GiB,但实际只读取了约 19 MiB,有效利用稀疏索引跳过了大量无关数据块。主键索引按磁盘物理顺序快速定位目标行组,使得查询延迟极低:冷启动 30 毫秒,热启动仅 10 毫秒,完全符合对高度调优表引擎的性能预期。

2. Parquet(文件格式)

ClickHouse 在直接读取 Parquet 文件时,已可通过谓词下推机制(min/max 统计值与 Bloom 过滤器)有效减少约一半的 I/O 开销。尽管目前还未支持 PREWHERE 和 lazy materialization,优化仍有空间,但整体表现已非常优秀:本次查询处理了 14 GiB 数据中的 7 GiB,在高达 85 个线程的并行加持下,冷启动耗时 170 毫秒,热启动 140 毫秒。相较 MergeTree 约慢 5 倍,但对于无需导入的数据文件格式而言,已经非常高效。

3. 其他格式(如 CSV、JSON、Arrow、Native) 

这些格式设计上主要用于数据交换或加载,不是面向高效查询场景。ClickHouse 尚未对它们启用如列裁剪、统计过滤等 I/O 减少机制。因此,查询需扫描整个文件,导致冷启动耗时 30–43 秒,热启动也需 29–42 秒,处理数据量可达 21 GiB。这也说明,它们并不适合作为大规模分析型查询的直接数据源。

全部查询性能概览:43 条 ClickBench 查询总耗时

为全面展示性能差异,下图对比了在相同格式配置下运行 **全部 43 条 ClickBench 查询** 所耗费的总时间。为保持简洁,图中仅展示运行时间总和。与此前图表一致,所有文件格式均采用“预排序 + ZSTD 压缩”配置——这是所有压缩/排序组合中表现最优的组合(相比无压缩、LZ4 等)。

我们同样将原生 MergeTree 表的结果纳入参考,其数据布局与文件格式保持一致。

如果需要查看不同格式组合(压缩算法、是否排序等)的详细对比,我们已将完整结果集发布,并附有冷启动总耗时的汇总图表。

图片

在执行全部 43 条 ClickBench 查询后,冷启动总耗时的结果也验证了我们从单条查询中观察到的性能趋势:

1. MergeTree  

作为 ClickHouse 的原生表引擎,MergeTree 以仅 113 GiB 的读取数据完成了所有查询,总耗时仅 28 秒。凭借 PREWHERE、lazy materialization 和索引跳过等优化手段,它在所有格式中展现出最优的 I/O 减少效果,这一表现完全在预期之中。

2. Parquet 

尽管当前 Parquet 读取器尚未支持 PREWHERE 和 lazy materialization,但其整体性能依然不俗。借助元数据下推(min/max 统计信息与 Bloom filter)以及出色的并行执行(某些查询使用高达 162 个线程),Parquet 在冷启动下完成全部 43 条查询仅耗时 56 秒,总读取量为 468 GiB——远低于理论最大值 602 GiB(43 次完整读取每次 14 GiB)。虽然整体比 MergeTree 慢近一倍,但作为通用文件格式,其执行效率已远超多数同类工具。

3. 其他文件格式(CSV、JSON、Arrow 等)

这些格式多为数据交换或加载而设计,缺乏索引与谓词下推支持,ClickHouse 也未在其上实现类似 I/O 减少机制。因此,它们在本次测试中耗时显著增加:冷启动执行全量查询需要 9–27 分钟,慢于 Parquet 10–30 倍,落后 MergeTree 更达 20–60 倍。

尽管未在图中展示,但我们观察到:即使在直接处理 Parquet 文件时,ClickHouse 的查询性能已显著优于许多主流系统(如 Postgres、Elasticsearch、MongoDB、MySQL)对其原生格式的处理表现,且硬件条件相同。

结语:ClickHouse,真正的 Lakehouse 引擎

本文深入剖析了 ClickHouse 如何高效查询 Parquet 格式——这是一种构建 Iceberg、Delta Lake 等开放表格式的核心列式存储方案。

ClickHouse 并非最近才“为 Lakehouse 做准备”,事实上,它从一开始就具备了关键能力:无需导入即可直接查询外部文件数据。这种特性在过去几年不断演进,如今已经成熟。

我们多年来持续对 Parquet 优化,目标明确:打造全球最快的 Parquet 查询引擎。

当前版本已在读取、解析、过滤、排序、聚合等多个执行阶段实现并行化,同时通过元数据跳过大量无效数据。这些优化让 ClickHouse 在文件格式中表现出色,甚至超过不少系统对原生表的处理性能。

更重要的是,我们还在不断进化。下一代 Parquet 原生读取器即将发布,届时将支持字典过滤、页级 min/max 过滤、PREWHERE 与 lazy materialization 等能力。

我们还首次系统性地将 Parquet 与其他文件格式、甚至与 MergeTree 表进行了性能对比。虽然这不是“格式之间的公平较量”,但结果清晰地表明:ClickHouse 查询 Parquet 的性能已非常接近其自身最优的表引擎 MergeTree。ClickHouse,不仅适合 Lakehouse,它本身已经是 Lakehouse 架构的坚实基石。

这篇文章是我们全新系列的第一篇。下一篇,我们将深入介绍 ClickHouse 如何支持 Lakehouse 更高层的数据表格式和元数据管理机制。Lakehouse 的各个层级在 ClickHouse 中,早已就绪——敬请期待。

图片

征稿启示

面向社区长期正文,文章内容包括但不限于关于 ClickHouse 的技术研究、项目实践和创新做法等。建议行文风格干货输出&图文并茂。质量合格的文章将会发布在本公众号,优秀者也有机会推荐到 ClickHouse 官网。请将文章稿件的 WORD 版本发邮件至:Tracy.Wang@clickhouse.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值