
本文字数:15616;估计阅读时间:40 分钟
作者:Tom Schreiber and Lionel Palacin

TL;DR
基于代理的工作负载对分析系统持续施压,使保持新鲜数据查询就绪的能力成为一个重要的成本考量。
ClickHouse 获取查询就绪数据的成本降低 22 倍,并且在写入侧的成本性能上提升了 28 倍,这里以 Snowflake 作为对比参照点。
这指的是成本性能的写入侧,而它通常很少被直接衡量。

大多数 云数据仓库基准测试都从查询开始时才进行衡量。
这对传统分析而言是合乎情理的。人类分析师打开仪表盘,编写 SQL 查询,等待结果,并可能提出后续问题。在这样的场景下,衡量查询运行时和查询成本足以说明大部分情况。
代理分析改变了工作负载的形态。
单个用户问题就能演变为数十个 SQL 查询:包括模式探索、候选查询生成、验证查询、重试、优化、下钻以及后续分析。
这些查询处于用户体验的关键路径上:如果数据库响应缓慢,与代理的交互就会显得迟缓。对于自主代理而言,压力甚至更高:由于没有人类输入延迟,每次循环的瓶颈主要在于 LLM 延迟和数据检索延迟。这使得基于新鲜且频繁变动的数据提供低延迟响应变得至关重要。
将这种模式推广到多个用户和代理,数据仓库不再只是处理偶发性查询,而是面对爆发性的并发分析查询,并且每个查询都对低延迟有严格要求。

这正是我们早期文章 [AI 如何重塑数据库市场](https://clickhouse.com/blog/ai-redrawing-database-market) 中所描述的转变:面向代理的分析使得内部分析工作负载更趋近于面向客户的应用模式——即高并发、低延迟和交互性。
这些查询越来越多地针对新鲜数据运行。
新事件源源不断地产生。数据表持续增长。代理不断提出查询。这意味着数据必须在不断变化中保持查询就绪状态。
这意味着查询的成本性能考量在查询实际发生之前就已经开始。
要确保查询快速响应,新摄入的数据必须先经过写入、排序、压缩,并为后续的数据修剪操作做好准备。
如果存储层能高效完成这项工作,查询就可以跳过更多数据,从而降低成本。反之,如果处理效率低下,系统则可能在后台消耗更多计算资源,或者在查询时读取更多数据,甚至两者兼有。
这体现了写入侧的成本效益 (cost-performance),但它很少被直接衡量。
因此,我们将基准测试的边界从首次查询转移至首次写入。
核心问题在于,一个系统能以何种效率,实时地让新鲜数据随时可供查询。
我们选用 Snowflake 作为对比对象,因为它代表了架构的另一极端:数据首先写入,随后才进行集群以建立数据局部性,从而使查询能够跳过更多数据。而 ClickHouse 则采取了不同的路径:它在数据写入路径中创建了有序布局,并在数据增长过程中通过存储引擎保持这种有序性。
BigQuery、Databricks 和 Redshift 又如何呢?
对所有主要云数据仓库进行全面的写入侧成本效益基准测试超出了本文的讨论范畴。我们重点关注 Snowflake 和 ClickHouse,因为它们代表了架构上的两个极端:Snowflake 在数据写入后才建立排序,而 ClickHouse 则将排序机制内置于写入路径。其他主要的云数据仓库介于这两者之间,但没有一个像 ClickHouse 那样,将排序机制直接融入写入路径。
BigQuery 在数据抵达时会将其写入已排序的存储块中,但新的插入操作会创建重叠的数据范围,这需要通过自动化的后台重新聚类 (reclustering) 操作来恢复数据顺序。这项重新聚类服务是免费的——Google 已将其成本纳入平台服务中——但自上次重新聚类以来写入的数据尚未完全组织妥当,不利于数据裁剪 (pruning)。
Databricks SQL Serverless 的模式与 Snowflake 最为接近。数据按照抵达顺序存入 Parquet 文件。其Liquid Clustering 功能通过 OPTIMIZE 作业在数据摄取后重新组织这些文件,该作业运行于独立的无服务器 (serverless) 计算资源上,并会因此产生费用。
Redshift 在数据摄取后,会通过一个在预置集群自身上运行的后台进程对数据进行排序。虽然没有单独的集群服务或额外收费,但新写入的行是未排序的,并且会一直保持这种状态,直到后台排序进程处理到它们。
原理类似:为实现数据剪枝(pruning)而维护数据的有序性是一项工作。根据系统不同,这项工作可能以独立收费、占用预置计算资源或被平台功能内嵌的形式体现,但它始终影响着确保新数据“查询可用(query-ready)”的成本效益。
为了具体说明这种差异,我们量化了在持续摄取约每秒 100 万行数据的情况下,保持相同数据集“查询可用(query-ready)”以实现快速分析的成本,并解释了为什么写入路径会成为影响成本与性能的关键因素。
这就引出了第一个问题:“查询可用(query-ready)”到底意味着什么?

对于分析查询而言,查询可用数据意味着查询引擎可以避免读取表中的大部分数据。
最快的分析查询往往是那些读取数据量最小的查询。
分析工作负载通常按连续的行范围进行过滤,例如,检索网络分析表中某个特定日期的所有事件,然后聚合结果,如下面的查询所示:
SELECTURL,COUNT(*) AS pageviews,COUNT(DISTINCT User) AS usersFROM hitsWHERE Day = 'D2'GROUP BY URL;
为了高效执行此类查询,分析型数据库依赖两个核心理念。
首先,列式存储(columnar storage)支持列级剪枝(pruning at the column level):尽管表可能包含数百列,但此查询仅涉及 Day、User 和 URL 列,所有其他列都会被跳过。
其次,数据以数据块(chunks)而非行进行处理。这实现了块级剪枝(pruning at the chunk level):整个数据块(包含多行)会被整体读取或跳过。
这些机制共同作用,能够减少 I/O 并支持高效的向量化执行。
然而,块级剪枝仅在数据根据过滤列进行排序存储时才有效。如果表是未排序的,数据块通常会包含混合的数据值:

在这个简化示例中(每个数据块包含四行),每个数据块都至少包含一个 D2 值,因此,对于我们 Day = D2 的查询谓词,没有任何数据块可以被跳过。
当相关的 Day 行连续存储时,数据块内部的值范围不再重叠,这样一来,对于查询谓词 Day = D2 不匹配的数据块就可以被完全跳过:

排序操作能够缩小数据块内部的值范围,从而实现有效的剪枝。
这种结构性要求使得分析查询能够跳过表中的大部分数据,即便是在应用预聚合等额外优化之前。
为什么不依赖物化视图 (materialized views)?
物化视图(https://clickhouse.com/docs/materialized-views) 是另一种常见的技术,它通过预聚合数据来加速分析,从而减少需要扫描的原始数据量。
然而,原始表仍然需要支持即席查询 (ad-hoc queries)、调试、新的查询模式、联接 (joins)、下钻 (drill-downs) 以及在视图创建时未曾预料到的可观测性查询 (observability queries)。
因此,即使是在使用物化视图的系统中,高效的数据排序对于可伸缩分析 (scalable analytics) 仍然至关重要。
这同样适用于物化视图本身:在大规模场景下,排序仍然决定着查询能否跳过物化视图中的大部分数据。
其他技术,例如 轻量级投影,可以进一步加速那些筛选条件与主要排序不一致的查询。
归根结底,同样的规则仍然成立:
跳过数据是分析实现规模化的唯一途径。
而排序,则是实现数据跳过的主要机制。
乍一看,Snowflake 和 ClickHouse Cloud 在这方面表现出惊人的相似性:两者都依赖列式存储 (columnar storage) 和块级剪枝 (chunk-level pruning) 来避免扫描不必要的数据,而这反过来又取决于相关行在物理层面的组织方式。
剪枝必须经受持续写入的考验
在现代实时分析中,数据并非一次写入,多次查询。
随着新数据的不断到来,表会持续增长。
在这种机制下,持续保持数据排序可确保查询成本取决于匹配项,而非存储的总数据量。

那么,关键在于,当新数据不断涌入时,如何以及何时创建和维护排序后的数据局部性——即具有相似键值的行被连续存储。
在持续数据写入的场景下,数据仓库在数据写入后如何组织数据,决定了其扩展效率。
接下来,我们将探讨 Snowflake 和 ClickHouse 在持续数据写入的情况下,如何创建和维护这种数据局部性。

核心区别在于数据局部性何时被创建:是在数据写入之后,还是在数据写入过程中。
Snowflake:写入后进行聚类
Snowflake 支持剪枝的数据块是 micro-partition,它是一个不可变、压缩的列式单元。
每个 micro-partition 通常存储 约 50-500 MB 数据(压缩前),并包含用于剪枝的各列最小/最大统计信息。
每次 INSERT 操作都会创建一个新的 micro-partition。
数据按照写入顺序写入,如下面三个插入操作示例所示:

(在上面的动画中,为清晰起见,我们仅展示了 Day 列的最小-最大值范围,但实际上所有列都会创建这些值。)
数据不会自动排序。
在持续数据写入的场景下,缺乏排序后的数据局部性会使得成本变得高昂。
每次插入都会产生一个新的独立写入的 micro-partition。随着时间的推移,不同分区中的值范围会发生重叠。当对 Day 列进行过滤时,许多分区会包含混合值,因此无法被跳过:

由于 Day 列的值范围在不同分区之间重叠,因此基于最小/最大元数据的剪枝对于此谓词的过滤将失效。
关于 Snowflake 中预排序的说明
预排序输入数据有助于减少单个插入批次内部的值范围重叠。
然而,每次 INSERT 仍然会创建独立的 micro-partition。
随着时间的推移,不同分区的值范围将再次重叠。
为了建立排序后的数据局部性,Snowflake 提供了聚类 (clustering) 功能。
当定义 聚簇键 (clustering key) 时(例如我们示例中的 Day 列),Snowflake 会在后台 重写现有微分区 (micro-partitions),并用将相似键值聚拢在一起的新分区取而代之。这缩小了最小/最大值的范围边界,从而实现了高效的数据剪枝 (pruning):

经过聚簇处理后,微分区包含的值范围变得更窄,不匹配的分区可以被完全跳过:

在 Snowflake 中,数据的排序局部性 (sorted locality) 并非在写入时创建。它是在数据摄取 (ingestion) 后,通过重写数据来建立的。
在持续的数据摄取场景下,聚簇操作必须持续运行,才能重新确立数据的排序局部性。
那么,对于事件按时间戳顺序到达的可观测性数据 (observability data) 又该如何呢?
一个合理的质疑是:如果可观测性事件大致按摄取顺序到达,每个新的微分区都将获得一个不重叠的时间戳范围,并且基于时间戳列的最小/最大剪枝无需聚簇也能奏效。这种说法部分正确——但仅限于纯粹基于时间戳、且摄取顺序完美的工作负载。
然而,在实践中,这种情况会在多个方面失效。首先,实际的可观测性管道并非完美有序:分布式采集器独立地进行缓冲和刷新,Kafka 消费者 (Kafka consumers) 会落后并追赶上来,重试操作会产生延迟到达的事件,而且跨服务时钟偏差 (clock skew) 也是司空见惯的。即使是偶然的重叠,也会重新导致分区扫描问题,并触发聚簇服务。
其次,也是更根本的一点是,可观测性查询很少仅限于时间戳这一个维度。典型的查询语句是 WHERE service = 'checkout' AND timestamp > now() - interval 1 hour,或者通过 trace_id、error_level 或 span_id 进行过滤。按照数据到达顺序的存储方式,会导致所有这些维度完全无序——对其进行剪枝操作,其效率与一般情况同样低下,无论时间戳的到达顺序多么规整。
第三,Snowflake 官方文档[警告](https://docs.snowflake.com/en/user-guide/tables-clustering-keys#strategies-for-selecting-clustering-keys),高基数时间戳列直接用作聚簇键 (clustering keys) 时效果不佳。其推荐的解决方法是将数据类型转换为日期,但这会将剪枝粒度降低到天级别,对于可观测性 (observability) 中常见的亚小时查询而言,这种粒度过于粗糙。
重新建立排序局部性 (sorted locality) 是一种方法,而持续维护它则是另一种。
ClickHouse:写入路径上的排序
ClickHouse 中可剪枝的数据块被称为 granule。
一个 granule 通常包含 约 8K 行 或 约 10 MB 数据(压缩前),它是不可变且已排序的 data part 内的一个逻辑单元。
与 Snowflake 不同,排序局部性在写入路径中生成。
每次 INSERT 操作都会生成一个新的 data part,它已根据表的 排序键 (sorting key)(在我们的示例中是 Day 列)进行排序,如下方三次 INSERT 操作的示例所示:

由于数据在写入时自动排序,因此能立即建立连续的键值范围。 稀疏主索引 (sparse primary index) 在 granule 级别记录范围边界信息(默认每个 granule 包含 8,192 行;在我们的示例中是 2 行),从而在查询执行期间实现高粒度的剪枝。
写入时排序难道不昂贵吗?
尽管排序操作看似开销很大,但在实践中它非常高效。
插入数据已在内存中以列式块的形式进行处理,而根据排序键进行排序是一个并行且具备缓存效率的步骤。
在我们的 数据摄取基准测试中,与解析和网络传输相比,服务器端排序带来的开销微乎其微。
这实现了与 Snowflake 最小/最大元数据相同的效果:即可以基于值范围跳过整个数据块。
差异体现在接下来发生的事情中。
在持续数据摄取(continuous ingest)场景下,保持这种数据局部性(locality)变得至关重要。
随着新的数据分块(parts)持续到来,ClickHouse 引擎会在后台持续地将较小的分块 合并为较大的分块:

由于所有数据分块都已按相同的排序键(key)完成排序,因此引擎只需执行一次线性合并过程(single linear merge pass),无需重新排序。
ClickHouse 合并操作为何能高效利用 CPU
ClickHouse 能够高效合并数据分块,原因在于所有分块均已按相同的排序键完成排序。
在合并数据分块时,引擎会执行一次线性合并过程(linear merge pass),这与 [归并排序(merge sort)](#) 的合并步骤类似:
分块数据被顺序读取
行数据即时进行比较
写入一个新的合并分块
无需重新排序、随机访问或使用大型临时缓冲区。
由于合并操作是在已排序的数据流上进行的,因此它们大部分是顺序的,且具有很高的缓存效率(cache-efficient)。
这种机制使得 ClickHouse 能够在后台持续整合数据,同时保持数据的排序局部性(sorted locality)。
每次合并后,相似的排序键(sorting key)值会更加紧密地排列在一起(co-located)。这会缩小稀疏主索引(sparse primary index)中记录的值范围,从而允许在针对排序键的谓词(predicates)查询时,跳过整个数据粒度(granules),如下图所示。

在查询阶段(query time),这实现了与 Snowflake 聚类(clustering)机制相同的范围剪枝(range-pruning)效果。
区别在于,ClickHouse 的这一机制直接内置于存储引擎中,而非后续应用。
查询时与 Snowflake 的对比
在查询阶段,数据剪枝(pruning)的工作方式没有根本区别。Snowflake 和 ClickHouse 都利用范围元数据(range metadata)来跳过大量数据块:
Snowflake 为每个微分区(micro-partition)存储明确的最小/最大统计信息(压缩前约为 50–500 MB)。
ClickHouse 在其稀疏主索引(sparse primary index)中,为每个 granule 存储第一个排序键(sorting-key)值(压缩前约为 10 MB)。
这使得 ClickHouse 能够以显著更精细的粒度进行剪枝。
由于 ClickHouse 数据在物理上已按排序键进行排序:
每个 granule 都隐式定义了一个连续的值范围。
下一个 granule 的第一个值实际上充当了前一个 granule 的上限。
从概念上看,这与 Snowflake 的 min/max metadata 实现了相同的效果:即可以根据值范围来排除(或“修剪”)整个数据块。其区别不在于 数据剪枝的工作方式,而在于这些值范围是如何随着时间推移被创建和维护的。
在 ClickHouse 中,排序在设计之初就已内置于存储引擎中。
每次合并操作,有序段 (sorted segments) 会变得更大。在持续数据注入 (continuous ingest) 的情况下,这种结构整合效应会持续增强。
在持续数据注入的场景下,排序机制的经济效益将变得至关重要。

本次基准测试扩展了我们 此前的读取性能与成本研究。该研究在五个云数据仓库 (cloud data warehouses) 上,使用相同的源自生产环境的匿名化 ClickBench 网络分析数据集,测量了分析查询的运行时和计算成本 (compute cost)。
值得注意的是,这是一项对照实验 (controlled experiment),而非数据注入速度比较。我们以每秒 100 万行的适中速率注入 ClickBench 数据集——该速率完全在两个系统的运行范围 (operational range) 之内——并测量在持续数据注入下,为实现快速分析查询而组织数据的计算成本。整个测试沿用了我们此前成本性能研究中相同的 定价方法论,并且读取基准测试 (read benchmark) 采用相同的 43 个 ClickBench 查询。所有基准测试代码和原始结果均已 [发布在 GitHub](https://github.com/ClickHouse/examples/tree/main/blog-examples/Bench2Cost) 上。
每秒 100 万行数据注入方式
Snowflake 通过单个连续数据流加载到表中,并使用了一个 Gen1 M 仓库 (Gen1 M warehouse)。
ClickHouse 通过 Python 脚本 使用 ClickHouse Connect客户端加载数据,模拟 170 个并行客户端,每个客户端发送由 20,000 行组成的连续数据批流。这项操作利用了 ClickHouse 的 异步插入 (async inserts) 功能——目前这是 ClickHouse 的 默认数据摄取模式——它在服务器端缓冲行,然后在刷新排序数据分区之前,从而在不导致分区爆炸的情况下实现高客户端并发。每个节点配置 2 GB 缓冲区,共 3 个节点,每个缓冲区大约每 3 秒刷新一次——此数据通过 part_log 系统表测量(平均:2.91 秒)。
两个系统都基于相同的键组织数据:其中 Snowflake 使用 聚簇键 (clustering key),ClickHouse 使用 排序键 (sorting key)。
隔离有序数据的成本
由于两个系统都提供 计算-计算分离 (compute-compute separation)功能,我们可以精确衡量获取有序数据的成本,并将其与 查询数据的成本分开计算。
我们将从两个高级动画开始介绍,这些动画展示了我们如何配置每个系统,将写入和排序计算与读取计算隔离开来,以及由此种设置所产生的成本。
Snowflake 基准测试设置:写入、聚簇、读取

Snowflake 设置将工作负载分离成三个计算层面:一个写入数据仓库 (write warehouse)、一个托管式聚簇服务 (managed clustering service) 和一个读取数据仓库 (read warehouse)。
① 按到达顺序写入
如前所述,Snowflake 会将传入的行数据按到达顺序写入 微分区 (micro-partitions)。
对于持续的写入工作负载,一个 Gen1 M warehouse (仓库) 足以每秒摄取 100 万个事件。按照 企业版定价,每 1000 亿行 ClickBench 数据将花费 336 美元。
② 后台集群(Background clustering)
排序操作稍后进行。
为了使表数据保持集群化以实现快速分析,Snowflake 采用了 自动集群(automatic clustering) 功能:这是一项托管的后台服务,负责在写入 warehouse (仓库) 之外重写微分区 (micro-partitions)。
在我们的测试中,这项功能使成本大致增加了 每 1000 亿行 2,500 美元。用户无法直接控制自动集群所消耗的计算资源量或重新集群化所需的时间。
③ 范围裁剪(Range pruning)
读取基准测试是独立运行的,在数据完成集群化之后。
一个 Gen2 4XL warehouse (仓库) 在 176 秒内 完成了对 1000 亿行有序数据执行 43 个 ClickBench 查询的任务,其企业级计算成本为 25 美元。
ClickHouse 采用了相同的基准测试划分方式,但写入端的工作有所不同:写入和排序同步进行。
ClickHouse 基准测试设置:写入、排序与读取

① 写入时排序(Write-time ordering)
如前所述,在 ClickHouse 中,传入的数据行会被写入已排序的数据分片中,因此可以即时建立连续的键值范围。在我们的设置中,一个由 3 个节点组成的写入与排序服务,每个节点配备 8 个 CPU 核心和 32 GiB RAM,可维持每秒 100 万个事件的写入速率。
按照 企业版定价,每 1000 亿行 ClickBench 数据将花费 131 美元——这比 Snowflake 在相同工作负载下写入和集群化的总成本降低了 95%。
② 保序后台合并(Order-preserving background merges)
排序通过在同一预置服务上运行的后台合并操作得以保持。
值得注意的是,这些合并操作将较小的有序数据片段组合成较大的有序数据片段。随着数据片段的增大,键范围变得更加连续,压缩效果提升,并且未来查询需要读取的数据量也会随之减少。
ClickHouse 没有独立的集群服务,也无需为此支付额外的集群费用。
③ 范围裁剪 (Range pruning)
读取基准测试独立运行,针对这些有序数据进行。
一个 40 节点的读取服务在 126 秒 内,以企业级计算成本 $16,完成了对 1000 亿条有序数据执行的 43 个 ClickBench 查询——比 Snowflake 在相同有序数据集上的表现快 28%,成本低 36%。
只有当写入和排序服务能够跟上数据摄取 (ingest) 速度时,ClickHouse 的这套方案才能奏效。下面的仪表板显示,它确实做到了这一点。
ClickHouse 写入和排序计算能力足以应对数据摄取需求
由 3 个节点组成的 ClickHouse 写入和排序服务,每个节点配备 8 个 CPU 核心和 32 GiB 内存,持续稳定地处理了工作负载,同时保持了表随时可供查询的状态。
下图所示的图表来自 ClickHouse Cloud 原生高级仪表板,展示了运行的前 24 小时数据,并从多个角度验证了这套配置的有效性。
① 行数/秒 (Rows/sec): 数据摄取量保持在每秒约 100 万行。
② 字节数/秒 (Bytes/sec): 数据摄取量也保持在约 800 MB/秒,或每个 CPU 核心约 33 MB/秒。这一点很重要,因为仅凭行数/秒可能会产生误导:每秒处理 1 亿条小行的数据需求不一定比每秒处理 100 万条宽行的数据需求更高。字节数/秒图表能更好地反映服务实际持续的写入吞吐量。
③ 数据块数量 (Part count): 后台合并操作将单个默认分区中的最大数据块数量控制在合理范围。该表没有进行显式分区。
④–⑤ CPU 和内存: 两者都得到了充分利用,但并未出现饱和状态。

那么,Snowflake 的 CPU 和内存指标又如何呢?
对于 ClickHouse,我们展示了 ClickHouse Cloud 原生高级仪表板中的 CPU 和内存数据,因为写入和排序服务直接暴露了资源利用率。
Snowflake 提供了仓库负载、查询历史、查询配置文件和积分使用情况,但没有为虚拟仓库提供对应的原生节点级 CPU 和内存仪表板。
因此,对于 Snowflake,我们通过结果和成本来验证配置:写入仓库(write warehouse)保持了每秒 100 万行的摄入速率,而消耗的仓库积分(warehouse credits)则反映了其成本。通过 Snowflake 的原生可观测性平台,无法获得类似的 CPU/内存饱和度图表。
在摄取(ingest)、分区数量(part counts)、CPU 和内存都得到有效控制后,一个关键问题是:新的数据何时才能真正准备好进行高效查询。
立即查询就绪 vs. 等待聚类 (Clustering)
这两种系统都旨在实现相同目标:即数据有序排列,以便能够高效地进行数据筛选(pruning)。区别在于新的数据何时能达到这种状态。
ClickHouse 不会等待后台进程使新数据可用
写入时(write-time)排序能够实现即时范围筛选(range pruning),而后台合并(background merges)则随着时间的推移逐步优化数据布局。在上述分区数量(part-count)图中,每个分区(partition)的最大数据块数(parts)大致维持在 100 到 150 之间,这表明对于此工作负载,在每个时间点都保持着健康且对查询高效的布局——因为数据摄取是持续的,所以合并过程永不“完成”,且也无需完成。查询会立即从排序中受益;合并只是随着时间的推移不断改进这种布局。
Snowflake 有一个不同的依赖:聚类(Clustering)必须跟上
在前 1000 亿行数据写入后,该表包含了大约 540K 个微分区(micro-partitions)。从一个空表开始,自动聚类(automatic clustering)进程 在数据摄取开始 1.3 小时后启动,并在 第 1000 亿行数据摄取完成后 6.7 小时才完成。
这种滞后对于实时分析至关重要:新的数据可能已经存在于表中,但尚未完全组织好以进行快速筛选(pruning)。
自动聚类的替代方案
作为自动聚类(automatic clustering)的替代方案,Snowflake 用户可以手动重构表,例如通过以下 SQL 语句:
CREATE TABLE sorted_table AS SELECT FROM unsorted_table ORDER BY sorting_column。
这种重写操作依赖于仓库计算资源,处理完整数据集,且不提供增量局部性维护。在持续数据摄取的情况下,重写必须反复执行,从而变成一项持续性任务。
这种方法适用于周期性的批量刷新,但对于持续增长的表而言,其运维负担将变得十分沉重。
上述设置已经为我们提供了所有必要条件。接下来,我们将结合1000亿、2000亿和3000亿行数据量下的写入与排序成本。

根据上方两个动画所示的配置,下方的图表展示了在数据量达到1000亿、2000亿和3000亿行时,为实现快速分析而获取有序数据的累计计算成本。
这两个系统均以每秒100万行的速度持续摄取 ClickBench 数据集,并基于相同的键组织数据:Snowflake 通过聚类(clustering)实现,ClickHouse 则通过写入时排序(write-time ordering)和保持顺序的后台合并(order-preserving background merges)实现。

综合来看,在所有三个检查点上,ClickHouse 实现查询就绪布局的成本大约比 Snowflake 低 22 倍。
我们如何测量 Snowflake 摄取和聚类成本
我们将 Snowflake 的写入侧成本分为两部分:用于数据摄取的仓库计算资源,以及用于获取有序数据的自动聚类计算资源。
在数据摄取方面,我们使用了 Gen1 M 仓库。根据企业定价,该仓库每小时消耗 4 个积分。在我们持续的、大约每秒 100 万行的摄取速率下,每 1000 亿行数据的摄取窗口大约需要 28 小时,因此每 1000 亿行的摄取成本为:
4 credits/hour × $3.00/credit × 28 hours = $336
对于聚类,我们直接从 snowflake.account_usage.automatic_clustering_history 中测量了 Snowflake 的排序成本,该成本是根据在已聚类的 ClickBench 表上自动聚类所报告的 credits_used(已用积分)计算得出的。SQL 查询及其结果可在此处[查看](https://pastila.nl/?012d03c1/0eeec86ab8137a015b204bd4a0ffd6ba#ekr45egH6HlKvdinmLHhEA==GCM)。
对于每个 1000 亿行数据的摄取窗口,我们汇总了自动聚类所消耗的积分,并根据每积分 $3 的企业定价进行了换算。
这三个被测量的集群窗口分别消耗了 849.84、916.09 和 853.59 credits,对应的集群计算成本分别为 $2,549.52、$2,748.27 和 $2,560.78。
图中所示的 Snowflake 数据摄取和排序总成本,是每个千亿行数据窗口的摄取仓库成本与自动集群成本之和。
ClickHouse 写入和排序成本的计算方法
对于 ClickHouse,写入和排序操作在同一预置的写入与排序服务上运行。在我们的配置中,该服务使用了 3 个节点,每个节点配备 8 个 CPU 核心。
根据 ClickHouse Cloud 的定价策略,每个 8 核节点对应 4 个 compute units。因此,这 3 个节点的服共消耗:
3 nodes × 4 compute units/node = 12 compute units
按照企业版定价,每小时每个 compute unit 成本为 $0.39。以我们大约 每秒 100 万行 的持续摄取速率计算,每个千亿行数据摄取窗口大约需要 28 小时。因此,每千亿行的写入和排序成本为:
12 compute units × $0.39/unit/hour × 28 hours = $131
这笔费用包含了数据摄取和排序维护两部分,因为 ClickHouse 在写入时会生成已排序的数据部分,并在此服务上通过后台合并操作来持续维持数据的顺序。
此外,正如前文所示,在 ClickHouse 中查询这些有序数据也更快、更经济。

在写入侧的每一美元投入中,如何获得最高的查询就绪性能?
为了回答这个问题,我们结合了达到查询结果的总成本以及在该数据上查询的运行时间,公式为:(获取查询就绪数据的成本 + 查询该数据的成本) × 该数据上的运行时间
该值越小越好。
这体现了写入侧成本性能的内在逻辑:
获取查询就绪数据成本越低的系统表现越好
查询成本越低的系统表现越好
运行时间越快的系统表现越好
成本和运行时间会叠加;低效率会相互放大。

总体而言,ClickHouse 的写入侧成本性能比 Snowflake 优秀 28 倍。
那么 Interactive Tables、Hybrid Tables 和 Dynamic Tables 这些特定表类型又如何呢?
Snowflake 提供了多种专门的表类型,但它们均无法改变大规模分析工作负载的写入侧成本分析结果。
交互式表 (Interactive Tables) 专为低延迟、高并发查找而设计。交互式仓库对 SELECT 查询强制执行 五秒的硬性查询超时限制,此限制无法提高。其设计目的在于保护缓存免受长时间运行扫描的影响。在1000亿有序行上,43个 ClickBench 查询总共用时176秒,其中许多单个查询的运行时间远超五秒截止点,会在返回结果前被取消。交互式表也针对简单、选择性查询进行了优化;官方文档明确建议避免使用大型连接、复杂聚合以及 时间戳等高基数聚簇键。
混合表 (Hybrid Tables) 采用基于行的存储方式,针对事务性点查找而非分析性范围扫描进行了优化。它们 每个 Snowflake 数据库的行存储数据上限为 2TB。在我们的基准测试中,Snowflake 在1000亿行数据上存储了3.11TB,这已经超出了混合表的配额。混合表也 不支持聚簇键、Snowpipe Streaming 或物化视图,并且查询不使用结果缓存。
动态表 (Dynamic Tables) 根据可配置的刷新延迟预物化转换后的查询结果。它们对于构建增量数据管道和预聚合视图很有用,但它们不会改变底层事件数据的存储或排序方式。基础表仍然需要聚簇,以便分析查询有效剪枝,并且每次刷新都在仓库计算资源上运行,并据此计费。动态表解决的是一个不同的问题——它们并不能降低保持原始数据随时可供查询的写入端成本。
排序成本只是写入端成本性能的一部分。

这种排序所产生的存储布局也会影响存储的数据量、后续需要读取的数据量以及未来查询执行的 I/O 量。
Snowflake:只重新分布不整合
Snowflake 的微分区 (micro-partitions) 大小始终是有限制的,通常在压缩前为 50–500 MB。
因此,聚类 (clustering) 操作只是在不同分区之间重新分配行,而不会在结构上整合数据集。
连续的值序列无法扩展超过微分区 (micro-partition) 的边界,并且压缩改进也受限于分区大小:

ClickHouse:数据随时间逐步整合
在 ClickHouse 中,数据会随着时间的推移逐步进行整合。
后台合并 (Background merges) 会持续不断地将较小的数据分块合并为更大的分块,最终生成大约 ~150 GB 压缩后大小 的数据分块。根据不同的压缩比,这可能相当于 1 TB 甚至更多的未压缩数据量。
这些合并操作不仅仅是重新组织数据行,更重要的是,它们还会整合数据集。
每次合并后,连续的值序列都会变得更长,从而使压缩效果在表中越来越大的数据段上得到持续改进:

重要的是,数据整合并不会降低剪枝 (pruning) 的粒度。ClickHouse 仍然会基于稀疏主索引在粒度 (granule) 级别进行剪枝(通常是压缩前大小约 ~10 MB 的行块),即使在已经合并的大数据分块内部也是如此。
存储占用随规模增长而分化
在向无限制的表持续写入 (ingest) 数据时,这种结构上的差异会随着时间的推移而不断加剧。
沿用上一节中持续摄入的网络分析数据,这些数据在两个系统中均已按相同键进行集群)和排序。现在,我们将考察当表数据量分别增长到 10 亿、100 亿和 1000 亿行时,压缩存储空间如何产生差异(存储量通过 Snowflake TABLE\_STORAGE\_METRICS 和 ClickHouse system.parts 衡量):

存储与计算成本说明
在许多分析型工作负载中,计算成本远超原始存储开销。然而,更大的物理存储占用空间意味着:
每次查询需要加载更多数据
更低的缓存驻留率
更高的 I/O 压力
增加对象存储的延迟风险
换句话说,压缩直接影响查询执行行为,而不仅仅是每月的存储开销。
即使启用了集群并使用相同的排序键,Snowflake 在 ClickBench 数据集上存储的压缩数据量也比 ClickHouse 多 5 到 15 倍,这增加了存储成本和后续查询的 I/O。
至此,我们对两种现代云数据仓库的存储层如何随时间组织和维护摄入数据进行了探讨。

智能体分析给数据库的每一层都带来了压力。
新数据源源不断地涌入,永不停止。用户和智能体期待立即从这些数据中获得快速、复杂的洞察,例如欺诈检测、运营警报、AI 分析师工作流和实时调查。系统必须在查询开始之前就能跟上这些需求。
这是成本-性能在写入侧的表现。
在此基准测试中,ClickHouse 在 1000 亿、2000 亿和 3000 亿行检查点上,以大约 22 倍低于 Snowflake 的成本实现了查询就绪布局。结合其在有序数据上实现的更快的查询运行时,ClickHouse 提供了 28 倍更优的写入侧成本-性能。
这得益于其架构设计。ClickHouse 在数据写入时,通过其存储层对数据进行排序组织,并随后通过后台合并操作逐步优化数据布局。它无需独立的聚类服务(clustering service),不产生额外的聚类费用,并且查询新数据时,也无需等待后台进程完成即可利用剪枝(pruning)优化。
这一点在实际部署中尤为重要。我们的测试仅使用了单表、单一稳定摄取流(ingest stream),以及每秒 100 万行的适中摄取速率。当将其乘以众多事件流、更高的摄取速率和海量数据表时,保持数据可查询的成本将成为平台核心经济效益的关键组成部分。
此外,还有另一个复合效应:存储占用(storage footprint)。由于 ClickHouse 会逐步整合已排序的数据,因此随着表的增长,其压缩率也会相应提高。在我们的测试中,即使在相同键上启用了聚类功能,Snowflake 在相同的 ClickBench 数据集上存储的数据量也比 ClickHouse 多出 15 倍。这意味着写入路径(write-path)的差异最终也会体现为更高的存储成本和更多的下游查询 I/O。
结合我们先前研究中发现的读取端(read-side)数量级优势,ClickHouse 在分析管道(analytics pipeline)的两端都表现出领先地位。
ClickHouse 专为大规模、高成本效益的实时分析而构建。在智能体时代(agentic era),这一切都始于写入路径。
/END/
试用阿里云 ClickHouse企业版
轻松节省30%云资源成本?阿里云数据库ClickHouse 云原生架构全新升级,首次购买ClickHouse企业版计算和存储资源组合,首月消费不超过99.58元(包含最大16CCU+450G OSS用量)了解详情:https://t.aliyun.com/Kz5Z0q9G
征稿启示
面向社区长期正文,文章内容包括但不限于关于 ClickHouse 的技术研究、项目实践和创新做法等。建议行文风格干货输出&图文并茂。质量合格的文章将会发布在本公众号,优秀者也有机会推荐到 ClickHouse 官网。请将文章稿件的 WORD 版本发邮件至:Tracy.Wang@clickhouse.com
夜雨聆风