架构概览
这是我们 VLDB 2024 学术论文 的网页版。我们还在博客中介绍了这篇论文的背景和研究历程,可参考这篇 博文,并推荐观看由 ClickHouse CTO 兼创建者 Alexey Milovidov 在 VLDB 2024 上的报告:
ABSTRACT
在过去的几十年中,所存储和分析的数据量呈指数级增长。各行各业的企业开始依赖这些数据来改进产品、评估性能,并做出对业务至关重要的决策。然而,随着数据量日益达到互联网规模,企业需要以具有成本效益且可扩展的方式管理历史数据和新数据,同时在高并发查询的情况下进行分析,并满足对实时级延迟的期望(例如根据具体用例低于一秒)。
本文概述了 ClickHouse,这是一款流行的开源 OLAP 数据库,专为在 PB 级数据集上进行高性能分析并支持高摄取速率而设计。其存储层将基于传统日志结构合并(LSM)树的数据格式,与在后台持续地对历史数据进行转换处理(例如聚合、归档)的新技术相结合。查询使用一种便捷的 SQL 方言编写,由最先进的向量化查询执行引擎处理,并可选支持代码编译。ClickHouse 大量使用剪枝技术,以避免在查询中处理无关数据。其他数据管理系统可以在表函数、^^table engine^^ 或数据库引擎层面进行集成。现实世界的基准测试表明,ClickHouse 是当前市场上速度最快的分析型数据库之一。
1 引言
本文介绍 ClickHouse,这是一款列式 OLAP 数据库,专为在包含数万亿行、数百列的数据表上执行高性能分析查询而设计。ClickHouse 于 2009 年作为一个用于 Web 规模日志数据的过滤与聚合算子起步,并于 2016 年开源。图 1 展示了本文所述主要特性在 ClickHouse 中被引入的时间。
ClickHouse 旨在解决现代分析型数据管理中的五大关键挑战:
-
在高摄取速率下处理海量数据集。在 Web 分析、金融、电商等行业,许多数据驱动型应用的特点是数据量巨大且持续增长。为处理超大规模数据集,分析型数据库不仅需要提供高效的索引与压缩策略,还必须支持将数据分布到多个节点上(横向扩展),因为单机服务器的存储容量通常仅限于数十 TB。此外,相较于历史数据,最新数据往往对实时洞察更为重要。因此,分析型数据库必须能够以持续稳定的高速度或突发的高峰速率摄取新数据,同时还要能够持续“降级处理”(例如聚合、归档)历史数据,而不会拖慢并行执行的报表查询。
-
大量并发查询且期待低延迟。查询一般可以分为临时查询(例如探索性数据分析)和周期性查询(例如定期更新仪表盘的查询)。用例越偏向交互式,对查询延迟的要求就越低,从而在查询优化和执行方面带来挑战。周期性查询还为根据工作负载调整物理数据库布局提供了机会。因此,数据库应提供剪枝(pruning)技术,以优化高频查询。根据查询优先级的不同,数据库还必须在大量查询同时运行时,为 CPU、内存、磁盘和网络 I/O 等共享系统资源提供公平或优先的访问。
-
多样化的数据存储系统、存储位置与格式。为了与现有数据架构集成,现代分析型数据库应当具有高度开放性,能够在任意系统、位置或格式中读写外部数据。
-
方便易用且支持性能分析的查询语言。OLAP 数据库在真实世界的使用中还存在一些额外的“软性”需求。例如,相比于小众的编程语言,用户通常更倾向于通过具备嵌套数据类型以及丰富的常规函数、聚合函数和窗口函数的表达力强的 SQL 方言来与数据库交互。分析型数据库还应提供复杂完善的工具,用于分析系统整体或单条查询的性能。
-
工业级鲁棒性与灵活的部署方式。由于通用硬件并不可靠,数据库必须提供数据副本,以应对节点故障并保证鲁棒性。同时,数据库应能运行在任何硬件上,从老旧笔记本电脑到高性能服务器。最后,为避免 JVM 程序中的垃圾回收开销并实现接近裸机的性能(例如利用 SIMD),数据库理想情况下应以原生二进制形式部署在目标平台上。

图 1:ClickHouse 时间线。
2 ARCHITECTURE

图 2:ClickHouse 数据库引擎的总体架构。
如图 2 所示,ClickHouse 引擎被划分为三个主要层次:查询处理层(在第 4 节中描述)、存储层(第 3 节)以及集成层(第 5 节)。除此之外,还有一个访问层,通过不同协议管理用户会话并与应用程序通信。系统还包含用于线程、缓存、基于角色的访问控制、备份以及持续监控的正交组件。ClickHouse 使用 C++ 构建为一个无外部依赖的静态链接单一二进制文件。
查询处理遵循传统范式:解析入站查询、构建并优化逻辑与物理查询计划,然后执行。ClickHouse 采用类似 MonetDB/X100 [11] 的向量化执行模型,并结合机会式代码编译 [53]。查询可以使用功能丰富的 SQL 方言、PRQL [76] 或 Kusto 的 KQL [50] 来编写。
存储层由不同的表引擎组成,这些表引擎封装了表数据的格式和位置。表引擎分为三类:第一类是 ^^MergeTree^^* 系列表引擎,它们代表了 ClickHouse 中的主要持久化格式。基于 LSM 树 [60] 的思想,表被拆分为按水平方向划分且有序的 ^^parts^^,这些 ^^parts^^ 会由后台进程持续合并。各个 ^^MergeTree^^* 表引擎在合并其输入 ^^parts^^ 的行方式上有所不同。例如,可以对行进行聚合,或在过期时进行替换。
第二类是用于加速或分布查询执行的专用表引擎。该类别包括称为字典(dictionary)的内存键值表引擎。dictionary 会缓存周期性针对内部或外部数据源执行的查询结果。在允许一定数据陈旧度的场景下,这能显著降低访问时延。其他专用表引擎的例子包括用于临时表的纯内存引擎,以及用于透明数据分片的 ^^Distributed table^^ 引擎(见下文)。
第三类表引擎是用于与外部系统进行双向数据交换的虚拟表引擎,例如关系型数据库(如 PostgreSQL、MySQL)、发布/订阅系统(如 Kafka、RabbitMQ [24]),或键值存储(如 Redis)。虚拟引擎还可以与数据湖(如 Iceberg、DeltaLake、Hudi [36])或对象存储中的文件(如 AWS S3、Google GCP)交互。
ClickHouse 支持在多个 ^^cluster^^ 节点之间对表进行分片和复制,以实现可扩展性和高可用性。分片根据分片表达式将一张表划分为一组表分片。各个分片是相互独立的表,通常位于不同的节点上。客户端可以直接读写分片,即将其视为独立表,或者使用 Distributed 特殊 ^^table engine^^ 来提供所有表分片的全局视图。分片的主要目的是处理超出单个节点容量的数据集(通常为数十 TB 的数据)。分片的另一用途是将某张表的读写负载分散到多个节点上,即实现负载均衡。与此正交的是,一个 ^^shard^^ 可以在多个节点之间复制,以容忍节点故障。为此,每个 Merge-Tree* ^^table engine^^ 都有对应的 ReplicatedMergeTree* 引擎,它基于 Raft 共识 [59](由 Keeper 实现,这是用 C++ 编写的 Apache Zookeeper 的可直接替换实现)采用多主协调方案,以保证每个 ^^shard^^ 在任何时刻都拥有可配置数量的副本。第 3.6 节将详细讨论复制机制。作为示例,图 2 展示了一张具有两个分片且每个分片复制到两个节点的表。
最后,ClickHouse 数据库引擎可以在本地部署、云端、独立(standalone)或进程内(in-process)模式下运行。在本地部署模式中,用户将 ClickHouse 部署在本地,作为单机服务器或带有分片和/或复制的多节点 ^^cluster^^。客户端通过原生协议、MySQL 的二进制协议、PostgreSQL 的二进制协议,或 HTTP REST API 与数据库通信。云端模式由 ClickHouse Cloud 提供,它是一个完全托管并支持自动伸缩的数据库即服务(DBaaS)产品。尽管本文聚焦于本地部署模式,我们计划在后续的文章中介绍 ClickHouse Cloud 的架构。standalone 模式 将 ClickHouse 变成用于分析和转换文件的命令行工具,使其成为类似 Unix 工具 cat 和 grep 的、基于 SQL 的替代方案。虽然这种模式不需要任何预先配置,但 standalone 模式仅限于单台服务器。最近,一种名为 chDB 的进程内模式 [15] 已被开发,用于诸如在 Jupyter notebooks [37] 中结合 Pandas DataFrame [61] 这类交互式数据分析场景。受 DuckDB [67] 启发,chDB 将 ClickHouse 作为高性能 OLAP 引擎嵌入到宿主进程中。与其他模式相比,这种方式允许在数据库引擎与应用程序之间高效地传递源数据和结果数据,而无需数据拷贝,因为它们运行在同一地址空间内。
3 存储层
本节将 ^^MergeTree^^* 表引擎作为 ClickHouse 的原生存储格式进行讨论。我们将描述其在磁盘上的存储形式,并介绍 ClickHouse 中的三种数据剪枝技术。随后,我们说明在不影响并发写入的情况下持续转换数据的合并策略。最后,我们解释更新和删除是如何实现的,以及数据去重、数据复制和 ACID 特性。
3.1 磁盘格式
每个 ^^MergeTree^^* ^^表引擎^^ 的表都被组织为一组不可变的表 ^^分片^^。每当一批行被插入表中时,就会创建一个分片。^^分片^^ 是自包含的,也就是说,它们包含了解释其内容所需的全部元数据,而无需额外查询集中式目录。为了保持每个表的 ^^分片^^ 数量较少,后台合并任务会定期将多个较小的 ^^分片^^ 合并成一个较大的分片,直到达到可配置的分片大小(默认 150 GB)。由于 ^^分片^^ 按表的 ^^主键^^ 列排序(见第 3.2 节),合并时会使用高效的 k 路归并排序 [40]。源 ^^分片^^ 会被标记为非活动状态,并在其引用计数降到零(即不再有查询从中读取数据)后被最终删除。
行可以通过两种模式插入:在同步插入模式下,每条 INSERT 语句都会创建一个新的分片并将其追加到表中。为了最小化合并开销,建议数据库客户端进行批量插入,例如一次插入 20,000 行。然而,如果数据需要实时分析,由客户端批处理带来的延迟通常是无法接受的。例如,可观测性场景经常涉及成千上万的监控 Agent 持续发送少量事件和指标数据。这类场景可以使用异步插入模式,在该模式下,ClickHouse 会将来自多个针对同一表的 INSERT 语句的行缓存在一起,只有当缓冲区大小超过可配置阈值或超时时间到达时才创建新的分片。

图 3:^^MergeTree^^* 引擎表的插入与合并。
图 3 展示了对一个 ^^MergeTree^^* 引擎表进行的四次同步插入和两次异步插入。两次合并将活动 ^^分片^^ 的数量从最初的五个减少到两个。
与 LSM 树 [58] 及其在各种数据库中的实现 [13, 26, [56]](#page-13-8) 相比,ClickHouse 将所有 ^^分片^^ 一视同仁,而不是将它们按照层级进行组织。其结果是,合并不再局限于同一层级中的 ^^分片^^。由于这也放弃了 ^^分片^^ 的隐式时间顺序排序,因此需要采用不基于墓碑标记的其他机制来实现更新和删除(见第 3.4 节)。ClickHouse 将插入直接写入磁盘,而其他基于 LSM 树的存储通常使用预写日志(见第 3.7 节)。
一个分片对应磁盘上的一个目录,其中包含每一列的一个文件。作为一种优化,小分片(默认小于 10 MB)的各列会连续存储在单个文件中,以提高读写的空间局部性。一个分片中的行会在逻辑上进一步划分为若干组,每组包含 8192 条记录,称为 granule。一个 ^^granule^^ 表示由 ClickHouse 中的扫描和索引查找算子处理的最小不可再分的数据单元。然而,磁盘数据的读写并不是在 ^^granule^^ 级别进行的,而是在块级别进行的:块将某一列中多个相邻的 granule 组合在一起。新的块会基于可配置的每 ^^块^^ 字节大小形成(默认 1 MB),也就是说,一个 ^^块^^ 中的 granule 数量是可变的,并且依赖于该列的数据类型和分布。块还会被压缩以减小其大小和 I/O 成本。默认情况下,ClickHouse 使用 LZ4 [75] 作为通用压缩算法,但用户也可以为浮点数据指定专用编解码器,如 Gorilla [63] 或 FPC [12]。压缩算法还可以串联使用。例如,可以先使用差分编码 [23] 降低数值中的逻辑冗余,然后执行高开销压缩,最后使用 AES 编解码器对数据进行加密。块在从磁盘加载到内存时会被即时解压。为了在存在压缩的情况下仍然能够快速随机访问单个 granule,ClickHouse 还会为每一列存储一个映射,用于将每个 ^^granule^^ 的 ID 关联到其所在压缩 ^^块^^ 在该列文件中的偏移,以及该 ^^granule^^ 在未压缩 ^^块^^ 中的偏移。
列还可以进一步采用 ^^dictionary^^ 编码 [2, 77, [81]](#page-13-12),或者通过两种特殊的包装数据类型将其设为可为空(nullable):LowCardinality(T) 使用整数 ID 替换原始列值,因此对于只有少量唯一值的数据可以显著减少存储开销。Nullable(T) 则为列 T 添加一个内部位图,用于表示列值是否为 NULL。
最后,表可以使用任意分区表达式进行范围分区、哈希分区或轮询(round-robin)分区。为实现分区裁剪(partition pruning),ClickHouse 还会为每个分区额外存储分区表达式的最小值和最大值。用户还可以选择创建更高级的列统计信息(例如 HyperLogLog [30] 或 t-digest [28] 统计信息),以提供基数估计。
3.2 数据剪枝
在大多数用例中,为了回答单个查询就扫描 PB 级数据既缓慢又昂贵。ClickHouse 支持三种数据剪枝技术,可以在搜索过程中跳过绝大部分行,从而显著加速查询。
首先,用户可以为表定义一个 ^^主键^^ 索引。^^主键^^ 列决定了每个 part 内部行的排序顺序,即索引是局部聚簇的(locally clustered)。此外,ClickHouse 对于每个 part,会存储一个从每个 ^^粒度单元^^(granule)首行的 ^^主键^^ 列值到该 ^^粒度单元^^ ID 的映射,即索引是稀疏的 [31]。生成的数据结构通常足够小,可以完全常驻内存,例如,仅需 1000 个条目就可以为 810 万行建立索引。^^主键^^ 的主要用途,是在经常被过滤的列上对等值和范围谓词使用二分查找而不是顺序扫描进行评估(第 4.4 节)。此外,这种局部排序还可以用于 part 合并和查询优化,例如基于排序的聚合,或者当 ^^主键^^ 列构成排序列前缀时,从物理执行计划中去除排序算子。
图 4 展示了在包含页面展示统计的表中,按 EventTime 列构建的 ^^主键^^ 索引。满足查询中范围谓词的粒度单元可以通过对 ^^主键^^ 索引进行二分查找来定位,而无需顺序扫描 EventTime。

图 4:使用 ^^主键^^ 索引评估过滤条件。
第二,用户可以创建 表投影(table projections),即包含相同行但按不同 ^^主键^^ 排序的表的备选版本 [71]。投影可以加速在不同于主表 ^^主键^^ 的列上进行过滤的查询,但代价是插入、合并和空间消耗方面的额外开销。默认情况下,投影是惰性填充的,只会从新插入到主表中的 ^^part^^ 填充,而不会从已有 ^^part^^ 中填充,除非用户将该 ^^投影^^ 完整物化。查询优化器会基于预估的 I/O 成本,在从主表还是从某个 ^^投影^^ 读取之间进行选择。如果某个 part 没有对应的 ^^投影^^,查询执行将回退到读取相应的主表 part。
第三,跳过索引(skipping indices) 提供了一种相对于投影更轻量级的替代方案。跳过索引的思想是在多个连续粒度单元的层面上存储少量元数据,从而避免扫描无关的行。可以针对任意索引表达式,并使用可配置的粒度(即每个 ^^跳过索引^^ 块中的粒度单元数量)来创建跳过索引。可用的 ^^跳过索引^^ 类型包括:1. 最小-最大(min-max)索引 [51],为每个索引 ^^块^^ 存储索引表达式的最小值和最大值。该索引类型适用于局部聚簇且绝对范围较小的数据,例如粗略排序的数据。2. 集合(set)索引,为每个索引 ^^块^^ 存储可配置数量的唯一索引值。这类索引最适合用于局部基数较小的数据,即“聚在一起”的值。3. Bloom filter 索引 [9],针对行、token 或 n-gram 值构建,并具有可配置的误报率。这些索引支持文本搜索 [73],但与 min-max 和 set 索引不同,它们不能用于范围谓词或否定谓词。
3.3 合并时数据转换
商业智能和可观测性场景通常需要处理持续高速或突发产生的数据。此外,与历史数据相比,最近生成的数据通常对获得有意义的实时洞察更为重要。这类场景要求数据库既能支撑高数据摄取速率,又能通过聚合或数据老化等技术持续减少历史数据量。ClickHouse 允许使用不同的合并策略对已有数据进行持续的增量转换。合并时数据转换不会影响 INSERT 语句的性能,但也无法保证表中永远不会包含不需要的值(例如过时或尚未聚合的值)。如有必要,可以在查询时通过在 SELECT 语句中指定关键字 FINAL 来应用所有合并时转换。
替换合并(replacing merges) 会基于其所属 part 的创建时间戳,仅保留最近插入的那个元组版本,较旧的版本会被删除。如果元组具有相同的 ^^primary key^^ 列值,则被视为等价。为了显式控制保留哪个元组,也可以指定一个用于比较的特殊版本列。替换合并通常用作一种合并时更新机制(通常用于更新频繁的场景),或者作为插入时数据去重机制的替代方案(参见 3.5))。
聚合合并(aggregating merges) 会将具有相同 ^^primary key^^ 列值的多行折叠为一行聚合结果。非 ^^primary key^^ 列必须是保存汇总值的部分聚合状态。两个部分聚合状态(例如 avg() 的 sum 和 count)会被合并为一个新的部分聚合状态。聚合合并通常用于物化视图,而不是普通表。物化视图通过对源表执行转换查询所得到的结果进行填充。与其他数据库不同,ClickHouse 不会定期使用源表的全部内容来刷新物化视图。物化视图是以增量方式更新的:当新的 part 被插入源表时,会将转换查询的结果写入物化视图。
图 5 展示了一个定义在页面展示统计表上的 ^^materialized view^^。对于插入到源表中的新 ^^parts^^,转换查询会按 region 分组计算最大和平均延迟,并将结果插入 ^^materialized view^^。聚合函数 avg() 和 max() 带有 -State 扩展时会返回部分聚合状态,而不是实际结果。为该 ^^materialized view^^ 定义的聚合合并会持续地在不同 ^^parts^^ 间合并这些部分聚合状态。为了获得最终结果,用户需要在 ^^materialized view^^ 上使用带有 -Merge 扩展的 avg() 和 max() 来汇总这些部分聚合状态。

图 5:物化视图中的聚合合并。
^^TTL^^(time-to-live)合并 为历史数据提供老化机制。与删除和聚合合并不同,^^TTL^^ 合并一次仅处理一个 part。^^TTL^^ 合并通过包含触发器和操作的规则来定义。触发器是一条对每一行计算时间戳的表达式,该时间戳会与 ^^TTL^^ 合并运行时的时间进行比较。虽然这允许用户在行粒度上控制操作,但我们发现,仅检查所有行是否满足给定条件并对整个 part 执行操作就足够了。可能的操作包括:1)将该 part 移动到另一个卷(例如更便宜但更慢的存储),2)重新压缩该 part(例如使用更重量级的编解码器),3)删除该 part,以及 4)roll-up,即使用分组键和聚合函数对行进行汇总。
例如,考虑 清单 1. 中的日志表定义。ClickHouse 会将 timestamp 列值早于一周的 ^^parts^^ 移动到速度较慢但成本低廉的 S3 对象存储中。
示例 1:在一周后将数据片段移动到对象存储。
3.4 更新与删除
^^MergeTree^^* 表引擎的设计更适合仅追加(append-only)的工作负载,但某些场景下仍需要偶尔修改已有数据,例如为了满足监管合规要求。对于更新或删除数据有两种方式可选,且都不会 ^^阻塞^^ 并行插入。
Mutations 会就地重写表中的所有 ^^parts(数据片段)^^。为避免在删除整表或更新列时,表或列的大小在短时间内翻倍,该操作是非原子的,即并行执行的 SELECT 语句可能会同时读取已变更和未变更的 ^^parts^^。Mutations 保证在操作结束时数据在物理上已经被修改。删除类型的 mutation 仍然开销较大,因为它会重写所有 ^^parts^^ 中的所有列。
作为替代方案,轻量级删除(lightweight deletes) 只会更新一个内部位图列,用于标记某一行是否被删除。ClickHouse 会在 SELECT 查询中追加一个针对该位图列的额外过滤条件,以在结果中排除已删除的行。已删除的行只会在未来某个未指定的时间,由常规合并操作实际从物理上移除。根据列数量的不同,轻量级删除可能比 mutation 快得多,但代价是 SELECT 查询会变慢。
对同一张表执行的更新和删除操作应当比较少见,并且需串行化执行以避免逻辑冲突。
3.5 Idempotent Inserts
在实践中经常遇到的一个问题是:客户端在将数据发送到服务器以插入到表中之后,如果发生连接超时,应该如何处理。在这种情况下,客户端很难判断数据是否已成功插入。传统的解决方案是让客户端将数据重新发送到服务器,并依赖 ^^primary key^^ 或唯一约束来拒绝重复插入。数据库通过基于二叉树 [39, [68]](#page-13-16)、基数树 [45] 或哈希表 [29] 的索引结构快速执行所需的点查询。由于这些数据结构会对每个元组建立索引,对于大型数据集和高摄取速率而言,它们的空间和更新开销会变得难以接受。
ClickHouse 基于这样一个事实提供了一种更轻量级的替代方案:每次插入最终都会创建一个 part。更具体地说,服务器会维护最近 N 个已插入 ^^parts^^(例如 N=100)的哈希值,并忽略那些哈希值已知的 ^^parts^^ 的重新插入。非复制表和复制表的哈希值分别存储在本地和 Keeper 中。这样一来,插入操作就变得幂等:客户端在超时后可以简单地重新发送同一批行,并假定服务器会负责去重。为了对去重过程有更多控制,客户端可以选择性地提供一个作为 part 哈希的插入令牌(insert token)。虽然基于哈希的去重会引入为新行计算哈希的开销,但存储和比较哈希的成本可以忽略不计。
3.6 数据复制
复制是实现高可用性(容忍节点故障)的前提条件,同时也用于负载均衡和零停机升级 [14]。在 ClickHouse 中,复制基于表状态这一概念,表状态由一组表 ^^parts^^(3.1 节)以及表元数据(例如列名和类型)构成。节点通过三类操作推进表的状态:1)INSERT 向状态中添加一个新的 part;2)合并(merge)向状态中添加一个新的 part,并从状态中删除已有的 ^^parts^^;3)变更(mutation)和 DDL 语句根据具体操作,添加 ^^parts^^、和/或删除 ^^parts^^、和/或修改表元数据。所有操作在单个节点本地执行,并作为状态转换序列记录到全局复制日志中。
复制日志由一个通常由三个 ClickHouse Keeper 进程组成的集合维护,这些进程使用 Raft 共识算法 [59] 为 ClickHouse 节点的 ^^cluster^^ 提供分布式且具备容错能力的协调层。所有 ^^cluster^^ 节点起初都指向复制日志中的同一位置。当各节点执行本地的 insert、merge、mutation 和 DDL 语句时,复制日志会在所有其他节点上异步回放。因此,复制表仅能达到最终一致性,也就是说,在收敛到最新状态的过程中,节点可能会暂时读取到较旧的表状态。前述的大多数操作也可以选择以同步方式执行,直到一个节点法定数(例如节点多数或全部节点)采纳了新的状态为止。
作为示例,图 6 展示了一个在由三个 ClickHouse 节点组成的 ^^cluster^^ 中最初为空的复制表。节点 1 首先接收两条 insert 语句,并将它们(1 2)记录到存储在 Keeper 集合中的复制日志中。接着,节点 2 通过拉取第一条日志条目(3)并从节点 1 下载新的 part(4)来回放该日志记录,而节点 3 则回放两条日志记录(3 4 5 6)。最后,节点 3 将这两个 ^^parts^^ 合并为一个新的 part,删除输入的 ^^parts^^,并在复制日志中记录一次 merge 条目(7)。

图 6:由三个节点组成的 ^^cluster^^ 中的复制过程。
为加速同步过程,ClickHouse 采用了三项优化:第一,新加入 ^^cluster^^ 的节点不会从头开始回放复制日志,而是直接复制写入最后一条复制日志记录的那个节点的状态。第二,在回放 merge 操作时,可以选择在本地重新执行合并,或者从其他节点获取合并后的结果 part。具体行为可配置,用于在 CPU 消耗与网络 I/O 之间进行权衡。例如,跨数据中心复制通常更倾向于本地 merge,以最小化运营成本。第三,节点会并行回放彼此独立的复制日志条目。这包括例如对同一张表连续插入的新 ^^parts^^ 的获取操作,或对不同表执行的操作。
3.7 ACID 合规性
为最大化并发读写操作的性能,ClickHouse 尽可能避免使用锁。查询在开始时会基于所有相关表中全部 ^^parts^^ 的一个快照来执行。这样可以保证在执行期间,由并行 INSERT 或合并操作(第 3.1) 新插入的 ^^parts^^ 不会参与本次查询执行。为防止 ^^parts^^ 被同时修改或删除(第 3.4),在查询执行期间,会增加这些被处理 ^^parts^^ 的引用计数。从形式化角度看,这对应于基于带版本 ^^parts^^ 的 MVCC 变体 [6] 实现的快照隔离。因此,一般情况下,这些语句在严格意义上并不满足 ACID,只在一个少见情形下例外:即在获取快照时的并发写入各自仅影响单个 part。
在实践中,ClickHouse 的大多数写入密集型决策类用例,即便在断电情况下存在少量新数据丢失的风险也是可以接受的。数据库利用这一点,默认不会强制将新插入的 ^^parts^^ 提交(fsync)到磁盘,从而允许内核批量写入,以放弃 ^^原子性^^ 为代价换取更高性能。
4 查询处理层

图 7:在 SIMD 单元、CPU 核心和节点层面的并行化。
如图 7所示,ClickHouse 在数据元素、数据块以及表分片三个层级对查询进行并行化。在算子内部,可以使用 SIMD 指令一次处理多个数据元素。在单个节点上,查询引擎会在多个线程中同时执行算子。ClickHouse 采用与 MonetDB/X100 [11] 相同的向量化模型,即算子生成、传递并消费多行数据(数据块),而不是单行数据,从而最大限度地减少虚函数调用的开销。如果源表被划分为互不相交的表分片,则多个节点可以同时扫描这些分片。这样一来,所有硬件资源都能够得到充分利用,并且可以通过增加节点进行水平扩展,通过增加核心数进行垂直扩展来提升查询处理能力。
本节余下部分将首先更详细地描述在数据元素、数据块以及 ^^shard^^ 粒度上的并行处理。然后,我们将介绍若干关键优化手段以最大化查询性能。最后,我们将讨论在存在并发查询时,ClickHouse 如何管理共享系统资源。
4.1 SIMD 并行化
在算子之间传递多行数据为向量化创造了机会。向量化要么基于手写的内建函数(intrinsics)[64, [80]](#page-13-19),要么依赖编译器自动向量化 [25]。能够从向量化中获益的代码会被编译为不同的计算内核。例如,查询算子的内部热点循环可以分别实现为非向量化内核、自动向量化的 AVX2 内核以及手动向量化的 AVX-512 内核。最快的内核会在运行时根据 cpuid 指令进行选择。这种方式使得 ClickHouse 能够在最老可追溯到 15 年前的系统上运行(最低只需要 SSE 4.2),同时在新硬件上仍然提供显著的加速效果。
4.2 Multi-Core Parallelization

图 8:具有三条执行通道(lane)的物理算子计划。
ClickHouse 遵循传统做法 [31],将 SQL 查询转换为物理计划算子的有向图。算子计划的输入由特殊的 source 算子表示,这些算子以原生格式或任意受支持的第三方格式读取数据(参见第 5) 节)。类似地,一个特殊的 sink 算子将结果转换为所需的输出格式。物理算子计划在查询编译时会被展开为若干相互独立的执行通道,其数量取决于可配置的最大 worker 线程数(默认是 CPU 核心数)以及源表大小。执行通道会将需要由并行算子处理的数据分解为互不重叠的范围。为了最大化并行处理的机会,这些执行通道会尽可能晚地合并。
例如,图 8 中节点 1 的方框展示了一个典型 OLAP 查询在一个包含页面曝光统计的表上的算子图。在第一阶段,源表的三个不相交范围会被同时过滤。一个 Repartition 交换算子在第一和第二阶段之间动态地在各处理线程之间路由结果数据块,以保持线程利用率均衡。如果扫描的范围选择性差异显著,第一阶段之后各执行通道可能会出现负载不均。在第二阶段,经过过滤保留下来的行会按 RegionID 进行分组。Aggregate 算子维护以 RegionID 为分组列的本地结果分组,并为 avg() 维护每组的求和与计数作为部分聚合状态。最终,这些本地聚合结果会由 GroupStateMerge 算子合并为一个全局聚合结果。该算子同时也是一个流水线断点(pipeline breaker),也就是说,只有在聚合结果完全计算完成后,第三阶段才能开始。在第三阶段,结果分组首先由一个 Distribute 交换算子划分为三个大小相等且互不重叠的分区,然后再按 AvgLatency 进行排序。排序分三步执行:第一步,ChunkSort 算子对每个分区的各个数据块分别排序。第二步,StreamSort 算子维护一个本地有序结果,并通过 2 路归并排序将新到达的有序数据块与其合并。最后,MergeSort 算子通过 k 路排序将各本地结果合并,得到最终结果。
算子是有限状态机,并通过输入和输出端口彼此连接。算子有三种可能的状态:need-chunk、ready 和 done。要从 need-chunk 转换为 ready,需要在算子的输入端口放入一个数据块。要从 ready 转换为 done,算子会处理输入数据块并生成一个输出数据块。要从 done 转换为 need-chunk,则需要从算子的输出端口移除该输出数据块。对于两个相连算子来说,第一种和第三种状态转换只能以组合步骤的方式执行。source 算子(sink 算子)只具有 ready 和 done(need-chunk 和 done)这两种状态。
worker 线程会持续遍历物理算子计划并执行状态转换。为了保持 CPU 缓存的高命中率,计划中包含提示,建议由同一个线程在同一执行通道中连续处理多个算子。并行处理既可以在同一阶段内对不相交的输入进行水平并行(例如在图 8 中,多个 Aggregate 算子是并发执行的),也可以在未被流水线断点隔开的不同阶段之间进行垂直并行(例如在图 8 中,同一执行通道内的 Filter 和 Aggregate 算子可以同时运行)。为避免在新查询启动或并发查询结束时出现过度或不足的线程占用,查询执行期间可以在单线程与该查询在启动时指定的最大 worker 线程数之间动态调整并行度(参见第 4.5) 节)。
算子在运行时还可以通过两种方式进一步影响查询执行。第一,算子可以动态创建并连接新的算子。这主要用于在内存消耗超过可配置阈值时,改为切换到外部聚合、排序或连接算法,而不是直接取消查询。第二,算子可以请求将 worker 线程移入一个异步队列。当需要等待远程数据时,这可以更高效地利用 worker 线程。
ClickHouse 的查询执行引擎和基于 morsel 的并行机制 [44] 在以下方面是相似的:各个 lane 通常运行在不同的核心 / NUMA 插槽上,并且工作线程可以从其他 lane 中窃取任务。此外,没有集中式调度组件;相反,工作线程通过持续遍历算子计划,各自独立选择要执行的任务。与基于 morsel 的并行机制不同的是,ClickHouse 将最大并行度直接固化到计划中,并且在对源表进行分区时使用的范围远大于默认 morsel 大小(约 100,000 行)。尽管这在某些情况下可能导致停顿(例如,不同 lane 中过滤算子的运行时间存在巨大差异时),但我们发现,大量使用诸如 Repartition 之类的 exchange 算子,至少可以避免这种不平衡在多个阶段间不断累积。
4.3 多节点并行化
如果查询的源表是分片的,接收查询的节点(发起节点)上的查询优化器会尽可能将更多工作下推给其他节点完成。来自其他节点的结果可以在查询计划的不同阶段被整合。根据具体查询,远程节点可能会执行以下操作:1. 将源表的原始列以流式方式传输到发起节点;2. 对源列进行过滤后,仅发送通过过滤的行;3. 执行过滤和聚合步骤,并发送带有部分聚合状态的本地结果分组;或者 4. 在本地运行完整查询,包括过滤、聚合和排序。
图 8 中的节点 2 … N 展示了在其他持有 hits 表分片的节点上执行的计划片段。这些节点对本地数据进行过滤和分组,然后将结果发送到发起节点。节点 1 上的 GroupStateMerge 算子在最终对结果分组排序之前,负责合并本地和远程的结果。
4.4 全面性能优化
本节介绍在查询执行不同阶段应用的若干关键性能优化。
查询优化。第一组优化是在从查询的 AST 获得的语义查询表示之上进行的。此类优化的示例包括常量折叠(例如 concat(lower('a'),upper('b')) 变为 'aB')、从某些聚合函数中抽取标量(例如 sum(a2) 变为 2 * sum(a))、公共子表达式消除,以及将等值过滤条件的析取改写为 IN 列表(例如 x=c OR x=d 变为 x IN (c,d))。经过优化的语义查询表示随后会被转换为逻辑算子计划。基于逻辑计划的优化包括过滤下推,以及根据预估代价对函数求值和排序步骤进行重排。最后,逻辑查询计划被转换为物理算子计划。该转换可以利用所涉及表引擎的特性。例如,对于 ^^MergeTree^^-^^table engine^^,如果 ORDER BY 列构成 ^^primary key^^ 主键的前缀,就可以按磁盘顺序读取数据,并从计划中去除排序算子。此外,如果聚合中的分组列构成 ^^primary key^^ 主键的前缀,ClickHouse 可以使用排序聚合 [33],即直接对预排序输入中相同值的连续区间进行聚合。与哈希聚合相比,排序聚合显著降低了内存占用,并且在处理完一个区间后即可将聚合值立刻传递给下一个算子。
查询编译。ClickHouse 使用 基于 LLVM 的查询编译 动态融合相邻计划算子 [38, [53]](#page-13-0)。例如,表达式 a * b + c + 1 可以合并为单个算子,而不是三个算子。除了表达式之外,ClickHouse 还通过编译同时求值多个聚合函数(即用于 GROUP BY),以及在具有多个排序键时执行排序。查询编译减少了虚函数调用的次数,使数据尽可能保留在寄存器或 CPU 缓存中,并且由于需要执行的代码更少,还能帮助分支预测器。此外,运行时编译支持一系列丰富的优化,例如编译器中实现的逻辑优化和窥孔优化,并可以使用本地可用的最快 CPU 指令。只有当同一常规表达式、聚合表达式或排序表达式在不同查询中被执行的次数超过可配置阈值时,才会触发编译。已编译的查询算子会被缓存,并可被后续查询复用。[7]
^^Primary key^^ 主键索引评估。如果 WHERE 条件的合取范式中某个过滤子句子集构成 ^^primary key^^ 主键列的前缀,ClickHouse 就会使用 ^^primary key^^ 主键索引评估 WHERE 条件。^^primary key^^ 索引会对按字典序排序的键值区间从左到右进行分析。与某个 ^^primary key^^ 列对应的过滤子句使用三值逻辑进行评估——对于该区间内的值,它们要么全部为真、全部为假,要么真/假混合。在后一种情况下,该区间会被拆分为子区间并递归分析。对于过滤条件中的函数,还存在额外的优化。首先,函数具有描述其单调性的属性,例如,toDayOfMonth(date) 在一个月内是分段单调的。单调性属性允许推断函数在已排序的输入键值区间上是否产生有序结果。其次,某些函数可以计算给定函数结果的原像,这可用于将对键列的函数调用与常量的比较,替换为将键列值与其原像进行比较。例如,toYear(k) = 2024 可以替换为 k >= 2024-01-01 && k < 2025-01-01。
数据跳过(Data skipping)。ClickHouse 在查询运行时尝试利用第 3.2 节介绍的数据结构来避免读取数据。此外,不同列上的过滤会按估计选择性从高到低的顺序依次评估,该估计基于启发式方法和(可选的)列统计信息。只有包含至少一行匹配记录的数据块才会传递给下一个谓词。这样可以在从一个谓词到下一个谓词的过程中逐步减少读取的数据量以及需要执行的计算量。只有在至少存在一个高选择性谓词时才会应用该优化;否则,相比并行评估所有谓词,查询延迟会变差。
Hash tables(哈希表)。哈希表是用于聚合和哈希连接的基础数据结构。选择合适类型的哈希表对性能至关重要。ClickHouse 会基于通用哈希表模板,并以哈希函数、分配器、单元类型和扩缩策略为可变点,实例化 多种哈希表(截至 2024 年 3 月超过 30 种)。根据分组列的数据类型、预估的哈希表基数以及其他因素,会为每个查询算子单独选择最快的哈希表。针对哈希表实现的进一步优化包括:
- 使用包含 256 个子表的两级布局(基于哈希值的首字节)以支持巨大的键集合,
- 字符串哈希表 [79],带有四个子表,并针对不同字符串长度使用不同的哈希函数,
- 当键数量很少时,使用直接将键作为桶索引(即无需哈希)的查找表,
- 当比较代价较高时(例如字符串、AST)时,在值中嵌入哈希以加速哈希碰撞的解决,
- 基于运行时统计预测大小来创建哈希表,以避免不必要的扩缩,
- 在单个内存 slab 上分配多个具有相同创建/销毁生命周期的小哈希表,
- 使用针对每个哈希表与每个单元的版本计数器,瞬时清空哈希表以便复用,
- 使用 CPU 预取(
__builtin_prefetch)在对键进行哈希后加速值的检索。
Joins(连接)。由于 ClickHouse 最初只提供了非常基础的连接支持,很多历史用例采用了反规范化表。如今,该数据库提供 了 SQL 中所有可用的连接类型(inner、left-/right/full outer、cross、as-of),以及多种连接算法,例如哈希连接(naïve、grace)、排序-归并连接,以及适用于具有快速键值查找能力(通常是 dictionaries)的表引擎的索引连接。
由于连接是开销最大的数据库操作之一,提供经典连接算法的并行变体(理想情况下带有可配置的空间/时间权衡)十分重要。对于哈希连接,ClickHouse 实现了来源于 [7] 的非阻塞共享分区算法。例如,图 9 中的查询通过对页面访问统计表进行自连接来计算用户在 URL 之间的跳转路径。连接的构建阶段被划分为三条通道,每条负责源表中的一个互不相交的范围。连接不会使用全局哈希表,而是使用分区哈希表。(通常为三个)工作线程通过对哈希函数取模来为构建端的每一条输入记录确定目标分区。对哈希表分区的访问通过 Gather 交换算子进行同步。探测阶段同样以类似方式为其输入元组查找目标分区。虽然该算法为每个元组引入了两次额外的哈希计算,但根据哈希表分区的数量,它在构建阶段极大地减少了锁竞争。

图 9:具有三个哈希表分区的并行哈希连接。
4.5 工作负载隔离
ClickHouse 提供并发控制、内存使用限制以及 I/O 调度,使用户能够将查询隔离到不同的工作负载类别中。通过为特定工作负载类别设置共享资源(CPU 核心、DRAM、磁盘和网络 I/O)的限制,可确保这些查询不会影响其他关键业务查询。
并发控制可防止在高并发查询场景下出现线程过度使用的情况。更具体地说,每个查询的工作线程数量会根据与可用 CPU 核心数量的指定比例进行动态调整。
ClickHouse 在服务器级别、用户级别和查询级别跟踪内存分配的字节大小,从而可以设置灵活的内存使用限制。内存超额分配(memory overcommit)允许查询在保证内存之外额外使用空闲内存,同时保证其他查询的内存限制不被突破。此外,可以限制用于聚合、排序和连接子句的内存使用,当超过内存限制时会回退到外部算法。
最后,I/O 调度允许用户基于最大带宽、正在处理的请求数量以及策略(例如 FIFO、SFC [32]),限制工作负载类别的本地和远程磁盘访问。
5 INTEGRATION LAYER
实时决策类应用通常依赖在多个位置高效、低延迟地访问数据。为了在 OLAP 数据库中使用外部数据,主要有两种方法。对于基于推送(push-based)的数据访问,由第三方组件在数据库与外部数据存储之间充当桥梁。例如,专用的抽取-转换-加载(ETL)工具会将远程数据推送到目标系统。在基于拉取(pull-based)的模型中,数据库本身连接远程数据源,将数据拉取到本地表中以供查询,或者将数据导出到远程系统。虽然基于推送的方法更通用且更常见,但它们会带来更高的架构复杂度和可扩展性瓶颈。相比之下,在数据库内部直接提供远程连接,可以在保持整体架构简洁、缩短获得洞察时间的同时,提供诸如本地与远程数据之间联接(join)等更强的能力。
本节的其余部分将探讨 ClickHouse 中用于访问远程位置数据的基于拉取的数据集成方法。需要指出的是,在 SQL 数据库中实现远程连接的想法并不新鲜。例如,2001 年引入并由 PostgreSQL 自 2011 年起实现的 SQL/MED 标准 [35] [65],提出了外部数据封装器(foreign data wrapper),作为管理外部数据的统一接口。与其他数据存储和存储格式实现最大互操作性是 ClickHouse 的设计目标之一。截至 2024 年 3 月,据我们所知,ClickHouse 在所有分析型数据库中提供了最多的内置数据集成选项。
外部连接能力。ClickHouse 提供了 50+ 个集成表函数和引擎,用于与外部系统和存储位置进行连接,包括 ODBC、MySQL、PostgreSQL、SQLite、Kafka、Hive、MongoDB、Redis、S3/GCP/Azure 对象存储以及各种数据湖。我们进一步将它们划分为下图所示的类别(附加图,不属于原始 VLDB 论文的一部分)。

附加图:ClickBench 的互操作性选项。
使用集成 表函数(Table Functions) 的临时访问。表函数可以在 SELECT 查询的 FROM 子句中被调用,以读取远程数据,用于探索性临时查询。或者,它们也可以通过 INSERT INTO TABLE FUNCTION 语句,将数据写入远程存储。
持久化访问。存在三种方法可以与远程数据存储和处理系统建立永久连接。
第一,集成 表引擎(table engines) 将远程数据源(例如 MySQL 表)表示为持久化的本地表。用户使用 CREATE TABLE AS 语法,结合 SELECT 查询和表函数来存储表定义。可以指定自定义 schema,例如,只引用远程列的子集,或者使用 schema 推断自动确定列名及其对应的 ClickHouse 类型。我们进一步区分被动与主动的运行时行为:被动表引擎将查询转发到远程系统,并使用结果填充本地代理表。相比之下,主动表引擎会定期从远程系统拉取数据或订阅远程变更,例如,通过 PostgreSQL 的逻辑复制协议。其结果是,本地表包含远程表的完整副本。
第二,集成 数据库引擎(database engines) 会将远程数据存储中某个表 schema 下的所有表映射到 ClickHouse 中。与前者不同的是,它们通常要求远程数据存储是关系型数据库,并且另外对 DDL 语句提供有限支持。
第三,字典(dictionaries) 可以通过针对几乎所有可能数据源的任意查询来填充,只要该数据源有相应的集成表函数或引擎。其运行时行为是主动的,因为数据会以固定时间间隔从远程存储中拉取。
数据格式。为了与第三方系统交互,现代分析型数据库还必须能够处理任意格式的数据。除了其原生格式外,ClickHouse 支持 90+ 种格式,包括 CSV、JSON、Parquet、Avro、ORC、Arrow 和 Protobuf。每种格式都可以是输入格式(ClickHouse 可以读取)、输出格式(ClickHouse 可以导出)或两者兼有。一些面向分析的格式(如 Parquet)还与查询处理集成,即优化器可以利用其中嵌入的统计信息,并且可以直接在压缩数据上执行过滤。
兼容性接口。除了其原生二进制 wire 协议和 HTTP 之外,客户端还可以通过兼容 MySQL 或 PostgreSQL wire 协议的接口与 ClickHouse 交互。此类兼容性功能对于来自尚未实现 ClickHouse 原生连接能力的专有应用(例如某些商业智能工具)的访问非常有用。
6 将性能视为特性
本节介绍用于性能分析的内置工具,并使用真实业务场景和基准查询对性能进行评估。
6.1 内置性能分析工具
有多种工具可用于分析单个查询或后台操作中的性能瓶颈。用户通过基于系统表的统一接口与所有这些工具交互。
服务器和查询指标。服务器级统计信息(例如活动 part 的数量、网络吞吐量和缓存命中率)会辅以每个查询的统计信息,例如读取的块数量或索引使用情况统计。指标可以同步计算(按需)或以可配置的时间间隔异步计算。
采样分析器。可以使用采样分析器收集服务器线程的调用栈。结果可以选择导出到外部工具,例如火焰图可视化工具。
OpenTelemetry 集成。OpenTelemetry 是一种开放标准,用于在多个数据处理系统之间跟踪数据行的流转 [8]。ClickHouse 可以为所有查询处理步骤生成具有可配置粒度的 OpenTelemetry 日志 span,也可以收集并分析来自其他系统的 OpenTelemetry 日志 span。
EXPLAIN 查询。与其他数据库类似,可以在 SELECT 查询前加上 EXPLAIN,以便深入了解查询的 AST、逻辑与物理执行计划以及运行时行为。
6.2 基准测试
尽管基准测试因不够贴近实际而受到批评 [10, 52, 66, [74]](#page-13-24),但它在识别数据库的优缺点方面仍然非常有用。下文将讨论如何使用基准测试来评估 ClickHouse 的性能。
6.2.1 Denormalized Tables
对反规范化事实表进行过滤和聚合查询,一直是 ClickHouse 的主要典型用例。我们报告 ClickBench 的运行时间,这是此类典型负载,它模拟用于点击流与流量分析的临时和定期报表查询。该基准由针对一个包含 1 亿条匿名页面点击记录的数据表的 43 条查询组成,这些数据来源于全球最大的网页分析平台之一。一个在线仪表盘 [17] 展示了截至 2024 年 6 月超过 45 个商用及研究型数据库的测量结果(冷/热运行时、数据导入时间、磁盘占用大小)。测试结果由独立贡献者提交,基于公开可用的数据集和查询 [16]。这些查询测试顺序扫描和索引扫描访问路径,并经常暴露出受 CPU、IO 或内存限制的关系算子。
Figure 10 展示了在分析场景中常用数据库上顺序执行全部 ClickBench 查询的总相对冷、热运行时间。测量是在一台单节点 AWS EC2 c6a.4xlarge 实例上完成的,该实例配置为 16 vCPU、32 GB 内存,以及 5000 IOPS / 1000 MiB/s 磁盘。对于 Redshift(ra3.4xlarge,12 vCPU,96 GB 内存)和 Snowfake(warehouse size S:2×8 vCPU,2×16 GB 内存)也使用了可比的系统。物理数据库设计仅进行了轻量调优,例如我们指定了主键,但不会更改单个列的压缩方式、创建投影或跳跃索引。在每次冷查询运行前,我们会刷新 Linux 页面缓存,但不会调整数据库或操作系统的参数。对于每条查询,跨数据库中最快的运行时间被用作基线。其他数据库的相对查询运行时间按 ( + 10)/(_ + 10) 计算。某一数据库的总相对运行时间为其按查询比值计算得到的几何平均数。尽管研究型数据库 Umbra [54] 实现了最佳整体热运行时间,ClickHouse 仍在冷、热运行时间方面优于所有其他生产级数据库。

图 10:ClickBench 的相对冷、热运行时间。
为了随时间跟踪更为多样化负载中 SELECT 性能的演进,我们使用了一个由四个基准组合而成的套件,称为 VersionsBench [19]。该基准在每次新版本发布时(每月一次)执行,用于评估其性能 [20],并识别可能导致性能下降的代码变更。单个基准包括:1. ClickBench(如上所述),2. 15 条 MgBench [21] 查询,3. 针对一个包含 6 亿行的反规范化 Star Schema Benchmark [57] 事实表的 13 条查询,4. 针对 NYC Taxi Rides(包含 34 亿行)的 4 条查询 [70]。
Figure 11 展示了从 2018 年 3 月到 2024 年 3 月间 77 个 ClickHouse 版本的 VersionsBench 运行时间演进情况。为补偿单条查询相对运行时间的差异,我们使用几何平均,并以跨所有版本的最小查询运行时间比值作为权重,对运行时间进行归一化。VersionsBench 的性能在过去六年中提升了 1.72 倍。具有长期支持(LTS)的发行版日期标记在 x 轴上。虽然在部分时间段内性能曾短暂下降,但 LTS 发行版通常都与前一个 LTS 版本性能相当或更好。2022 年 8 月的显著性能提升来自第 4.4 节所述的按列逐列过滤求值技术。

图 11:VersionsBench 2018–2024 的相对热运行时间。
6.2.2 规范化表
在传统数仓中,数据通常使用星型或雪花模式进行建模。我们给出了 TPC-H 查询(规模因子 100)的运行时间,并指出规范化表是 ClickHouse 的一个新兴用例。图 12 展示了基于第 4.4 节所述并行哈希连接算法的 TPC-H 查询热态运行时间。测试在一台单节点 AWS EC2 c6i.16xlarge 实例上进行,该实例具有 64 个 vCPU、128 GB 内存以及 5000 IOPS / 1000 MiB/s 磁盘。记录了五次运行中最快的一次。作为对比,我们在一个规模相当的 Snowfake 系统(仓库大小 L,8x8 vCPU,8x16 GB 内存)上进行了相同的测试。表中排除了 11 个查询的结果:查询 Q2、Q4、Q13、Q17 和 Q20–22 包含相关子查询,而目前在 ClickHouse v24.6 中尚不支持。查询 Q7–Q9 和 Q19 依赖于连接的扩展执行计划级优化(如连接重排序和连接谓词下推,这两者在 ClickHouse v24.6 中均缺失)以获得可接受的运行时间。计划在 2024 年实现自动子查询去相关化以及更完善的连接优化器支持 [18]。在剩余的 11 个查询中,有 5(6)个查询在 ClickHouse(Snowfake)中执行得更快。鉴于前述优化已知对性能至关重要 [27],我们预期一旦实现,它们将进一步改善这些查询的运行时间。

图 12:TPC-H 查询的热态运行时间(单位:秒)。
7 相关工作
近几十年来,分析型数据库一直是学术界和业界广受关注的研究与应用方向 [1]。早期系统如 Sybase IQ [48]、Teradata [72]、Vertica [42] 和 Greenplum [47],其典型特征是代价高昂的批量 ETL 作业,以及由于其本地部署形态而导致的弹性受限。2010 年代初期,云原生数据仓库以及数据库即服务(DBaaS)产品(如 Snowfake [22]、BigQuery [49] 和 Redshift [4])的出现,大幅降低了组织开展分析工作的成本和复杂度,同时还具备高可用性和自动资源伸缩能力。更近期,分析执行内核(例如 Photon [5] 和 Velox [62])为不同的分析、流处理和机器学习应用提供了通用的数据处理能力。
在目标和设计原则方面,与 ClickHouse 最为相似的数据库是 Druid [78] 和 Pinot [34]。这两个系统都面向高数据摄取速率的实时分析场景。与 ClickHouse 一样,表被划分为称为 segment 的水平 ^^parts^^。不同的是,ClickHouse 会持续合并较小的 ^^parts^^,并可选择性地利用第 3.3 节中的技术来减少数据体量,而在 Druid 和 Pinot 中,^^parts^^ 一旦生成即保持不可变。此外,Druid 和 Pinot 需要专门的节点来创建、修改和查询表,而 ClickHouse 则使用一个单一的二进制文件来完成这些任务。
Snowfake [22] 是一种基于共享磁盘架构的流行专有云数据仓库。其将表划分为微分区(micro-partition)的方法,与 ClickHouse 中 ^^parts^^ 的概念类似。Snowfake 在持久化存储中使用混合 PAX 页 [3],而 ClickHouse 的存储格式则是严格的列式。Snowfake 还通过自动创建的轻量级索引 [31, [51]](#page-13-14) 来实现本地缓存和数据剪枝,以此作为性能优化的重要来源。类似于 ClickHouse 中的主键,用户可以选择性地创建聚簇索引,以将具有相同值的数据共置存储。
Photon [5] 和 Velox [62] 是被设计成可作为复杂数据管理系统组件使用的查询执行引擎。两个系统都将查询计划作为输入,然后在本地节点上针对 Parquet(Photon)或 Arrow(Velox)文件 [46] 执行这些计划。ClickHouse 能够读取和生成这些通用格式的数据,但在存储时更偏好其原生文件格式。尽管 Velox 和 Photon 并不对查询计划进行优化(Velox 会执行基本的表达式优化),它们利用运行时自适应技术,例如根据数据特征动态切换计算内核。类似地,ClickHouse 中的计划算子
可以在运行时创建其他算子,主要用于根据查询的内存消耗切换到外部聚合或连接算子。Photon 论文指出,与解释执行的向量化设计 [11] 相比,代码生成式设计 [38, 41, [53]](#page-13-0) 在开发和调试上更为困难。Velox 中(实验性的)代码生成支持,会构建并链接由运行时生成的 C++ 代码产生的共享库,而 ClickHouse 则直接与 LLVM 的按需编译 API 交互。
DuckDB [67] 同样旨在被嵌入到宿主进程中使用,但另外还提供查询优化和事务支持。它专为掺杂少量 OLTP 语句的 OLAP 查询而设计。DuckDB 因此选择了 DataBlocks [43] 存储格式,该格式采用诸如保序字典或 frame-of-reference [2] 等轻量级压缩方法,以在混合负载下获得良好性能。相比之下,ClickHouse 针对仅追加(append-only)的用例进行了优化,即几乎没有更新和删除操作。数据块使用诸如 LZ4 等重量级技术进行压缩,前提是假设用户会大量利用数据剪枝来加速频繁查询,并且对于剩余查询,I/O 成本远高于解压缩成本。DuckDB 还基于 Hyper 的 MVCC 方案 [55] 提供可串行化事务,而 ClickHouse 仅提供快照隔离。
8 结论与展望
我们介绍了 ClickHouse 的架构,这是一款开源的高性能 OLAP 数据库。以写入优化的存储层和最先进的向量化查询引擎为基础,ClickHouse 能够在高摄取速率下,对拍字节级数据集进行实时分析。通过在后台异步合并和转换数据,ClickHouse 高效地解耦了数据维护与并行插入。其存储层通过稀疏主索引、跳过索引以及 ^^projection^^ 表实现了高效的数据裁剪。我们描述了 ClickHouse 中更新和删除、幂等插入以及跨节点数据复制以实现高可用性的实现方式。查询处理层利用大量技术优化查询,并在所有服务器和 ^^cluster^^ 资源上并行执行。各类集成表引擎和函数为与其他数据管理系统和数据格式的无缝交互提供了一种便捷方式。通过基准测试,我们展示了 ClickHouse 是当前市场上最快的分析型数据库之一,并展示了多年来在实际 ClickHouse 部署中典型查询性能的显著提升。
2024 年计划推出的所有特性和增强功能可在公开路线图 [18] 中查看。计划中的改进包括:支持用户事务,将 PromQL [69] 作为一种替代查询语言,引入用于半结构化数据(例如 JSON)的新数据类型,改进连接在执行计划层面的优化,以及实现轻量级更新以补充轻量级删除。
ACKNOWLEDGMENTS
在 24.6 版本中,执行 SELECT * FROM system.contributors 会返回 1994 位为 ClickHouse 做出贡献的个人。我们谨向 ClickHouse Inc. 全体工程团队以及 ClickHouse 卓越的开源社区致以诚挚的谢意,感谢他们为共同构建这一数据库所付出的辛勤工作和奉献。
参考资料
- [1] Daniel Abadi, Peter Boncz, Stavros Harizopoulos, Stratos Idreaos, and Samuel Madden. 2013. 《现代列式数据库系统的设计与实现》。 https://doi.org/10.1561/9781601987556
- [2] Daniel Abadi、Samuel Madden 和 Miguel Ferreira。2006。在列式数据库系统中集成压缩与执行。载于 2006 年 ACM SIGMOD 数据管理国际会议(SIGMOD '06)论文集,671–682。https://doi.org/10.1145/1142473.1142548
- [3] Anastassia Ailamaki、David J. DeWitt、Mark D. Hill 和 Marios Skounakis。2001 年 Weaving Relations for Cache Performance。见第 27 届国际超大型数据库会议(VLDB '01)论文集。Morgan Kaufmann Publishers Inc.,美国加利福尼亚州旧金山,169–180。
- [4] Nikos Armenatzoglou, Sanuj Basu, Naga Bhanoori, Mengchu Cai, Naresh Chainani, Kiran Chinta, Venkatraman Govindaraju, Todd J. Green, Monish Gupta, Sebastian Hillig, Eric Hotinger, Yan Leshinksy, Jintian Liang, Michael McCreedy, Fabian Nagel, Ippokratis Pandis, Panos Parchas, Rahul Pathak, Orestis Polychroniou, Foyzur Rahman, Gaurav Saxena, Gokul Soundararajan, Sriram Subramanian 和 Doug Terry。2022 年。Amazon Redshift Re-Invented。载于 2022 International Conference on Management of Data 会议论文集(美国宾夕法尼亚州费城)(SIGMOD '22)。Association for Computing Machinery,美国纽约州纽约市,2205–2217。 https://doi.org/10.1145/3514221.3526045
- [5] Alexander Behm, Shoumik Palkar, Utkarsh Agarwal, Timothy Armstrong, David Cashman, Ankur Dave, Todd Greenstein, Shant Hovsepian, Ryan Johnson, Arvind Sai Krishnan, Paul Leventis, Ala Luszczak, Prashanth Menon, Mostafa Mokhtar, Gene Pang, Sameer Paranjpye, Greg Rahn, Bart Samwel, Tom van Bussel, Herman van Hovell, Maryann Xue, Reynold Xin 和 Matei Zaharia。2022 年。Photon: 面向 Lakehouse 系统的高速查询引擎(Photon: A Fast Query Engine for Lakehouse Systems)(SIGMOD '22)。Association for Computing Machinery,New York, NY, USA,2326–2339。https://doi.org/10.1145/3514221. 3526054
- [6] Philip A. Bernstein 和 Nathan Goodman。1981 年。Concurrency Control in Distributed Database Systems(分布式数据库系统中的并发控制)。ACM Computing Surveys 13, 2 (1981), 185–221。https://doi.org/10.1145/356842.356846
- [7] Spyros Blanas、Yinan Li 和 Jignesh M. Patel。2011。面向多核 CPU 的主内存哈希连接算法的设计与评估。载于 2011 年 ACM SIGMOD International Conference on Management of Data 论文集(希腊雅典)(SIGMOD '11)。Association for Computing Machinery,美国纽约,37–48 页。 https://doi.org/10.1145/1989323.1989328
- [8] Daniel Gomez Blanco. 2023 年. Practical OpenTelemetry. Springer Nature.
- [9] Burton H. Bloom. 1970. 允许误差的哈希编码中的空间/时间折衷. Commun. ACM 13, 7 (1970), 422–426. https://doi.org/10.1145/362686. 362692
- [10] Peter Boncz、Thomas Neumann 和 Orri Erling。2014 年。TPC-H 分析:来自一个有影响力的基准测试的隐含信息和经验教训。载于《性能表征与基准测试》。61–76。 https://doi.org/10.1007/978-3-319- 04936-6_5
- [11] Peter Boncz、Marcin Zukowski 和 Niels Nes。2005 年。MonetDB/X100:超流水线化查询执行。载于 CIDR。
- [12] Martin Burtscher 和 Paruj Ratanaworabhan. 2007. 双精度浮点数据的高吞吐量压缩. 载于 Data Compression Conference (DCC), 293–302. https://doi.org/10.1109/DCC.2007.44
- [13] Jef Carpenter 和 Eben Hewitt. 2016. Cassandra: 权威指南(第2版)。O'Reilly Media, Inc.
- [14] Bernadette Charron-Bost、Fernando Pedone 和 André Schiper(主编)。2010 年。Replication: Theory and Practice. Springer-Verlag.
- [15] chDB. 2024. chDB - 嵌入式 OLAP SQL 引擎。检索于 2024-06-20,来自 https://github.com/chdb-io/chdb
- [16] ClickHouse。2024。ClickBench:一种针对分析型数据库的基准测试。检索于 2024-06-20,来自 https://github.com/ClickHouse/ClickBench
- [17] ClickHouse。2024。ClickBench:比较测评。2024-06-20 访问自 https://benchmark.clickhouse.com
- [18] ClickHouse. 2024. ClickHouse Roadmap 2024 (GitHub)。取自 2024-06-20,https://github.com/ClickHouse/ClickHouse/issues/58392
- [19] ClickHouse. 2024. ClickHouse 版本基准测试。2024-06-20 检索自 https://github.com/ClickHouse/ClickBench/tree/main/versions
- [20] ClickHouse. 2024. ClickHouse 版本基准测试结果。检索于 2024-06-20,来自 https://benchmark.clickhouse.com/versions/
- [21] Andrew Crotty. 2022. MgBench。访问日期:2024-06-20,链接:https://github.com/ andrewcrotty/mgbench
- [22] Benoit Dageville, Thierry Cruanes, Marcin Zukowski, Vadim Antonov, Artin Avanes, Jon Bock, Jonathan Claybaugh, Daniel Engovatov, Martin Hentschel, Jiansheng Huang, Allison W. Lee, Ashish Motivala, Abdul Q. Munir, Steven Pelley, Peter Povinec, Greg Rahn, Spyridon Triantafyllis 和 Philipp Unterbrunner。2016 年。The Snowflake Elastic Data Warehouse。收录于 2016 International Conference on Management of Data(SIGMOD '16,美国加利福尼亚州旧金山)论文集。Association for Computing Machinery, New York, NY, USA, 215–226。https: //doi.org/10.1145/2882903.2903741
- [23] Patrick Damme, Annett Ungethüm, Juliana Hildebrandt, Dirk Habich 和 Wolfgang Lehner。2019 年。From a Comprehensive Experimental Survey to a Cost-Based Selection Strategy for Lightweight Integer Compression Algorithms。ACM Trans. Database Syst. 44, 3, Article 9 (2019), 共 46 页。https://doi.org/10.1145/3323991
- [24] Philippe Dobbelaere 和 Kyumars Sheykh Esmaili。2017 年。Kafka versus RabbitMQ: A Comparative Study of Two Industry Reference Publish/Subscribe Implementations: Industry Paper (DEBS '17)。Association for Computing Machinery,New York, NY, USA,227–238。https://doi.org/10.1145/3093742.3093908
- [25] LLVM 文档,2024 年。《LLVM 中的自动向量化》。访问于 2024-06-20,链接:https://llvm.org/docs/Vectorizers.html
- [26] Siying Dong, Andrew Kryczka, Yanqin Jin, and Michael Stumm. 2021. RocksDB:面向大规模应用的键值存储中开发优先级的演进。ACM Transactions on Storage 17, 4, 第 26 篇文章 (2021), 32 页。 https://doi.org/10.1145/3483840
- [27] Markus Dreseler、Martin Boissier、Tilmann Rabl 和 Matthias Ufacker。2020 年。《对 TPC-H 瓶颈点及其优化的量化分析》。Proc. VLDB Endow. 13, 8 (2020), 1206–1220。 https://doi.org/10.14778/3389133.3389138
- [28] Ted Dunning. 2021. t-digest:对分布的高效估计. Software Impacts 7 (2021). https://doi.org/10.1016/j.simpa.2020.100049
- [29] Martin Faust、Martin Boissier、Marvin Keller、David Schwalb、Holger Bischof、Katrin Eisenreich、Franz Färber 和 Hasso Plattner。2016 年。利用 SAP HANA 中的哈希索引缩减存储占用并实现唯一性约束(Footprint Reduction and Uniqueness Enforcement with Hash Indices in SAP HANA)。见《Database and Expert Systems Applications》,第 137–151 页。https://doi.org/10.1007/978-3-319-44406- 2_11
- [30] Philippe Flajolet, Eric Fusy, Olivier Gandouet, and Frederic Meunier. 2007 年。HyperLogLog:对一种近乎最优的基数估计算法的分析。收录于 AofA: Analysis of Algorithms,DMTCS Proceedings 第 AH 卷,2007 Conference on Analysis of Algorithms (AofA 07)。Discrete Mathematics and Theoretical Computer Science, 137–156。 https://doi.org/10.46298/dmtcs.3545
- [31] Hector Garcia-Molina、Jefrey D. Ullman 和 Jennifer Widom. 2009. Database Systems - The Complete Book(第 2 版)。
- [32] Pawan Goyal、Harrick M. Vin 和 Haichen Chen。1996。起始时间公平排队:一种用于综合业务分组交换网络的调度算法。26(4) (1996), 157–168。 https://doi.org/10.1145/248157.248171
- [33] Goetz Graefe. 1993. 大规模数据库中的查询处理技术. ACM Comput. Surv. 25, 2 (1993), 73–169. https://doi.org/10.1145/152610.152611
- [34] Jean-François Im、Kishore Gopalakrishna、Subbu Subramaniam、Mayank Shrivastava、Adwait Tumbde、Xiaotian Jiang、Jennifer Dai、Seunghyun Lee、Neha Pawar、Jialiang Li 和 Ravi Aringunram。2018 年。Pinot:面向 5.3 亿用户的实时 OLAP。收录于 2018 年国际数据管理大会(SIGMOD '18,地点:美国德克萨斯州休斯敦)的论文集。Association for Computing Machinery,纽约州纽约市,美国,583–594。 https://doi.org/10.1145/3183713.3190661
- [35] ISO/IEC 9075-9:2001 2001. 信息技术 — 数据库语言 — SQL — 第 9 部分:外部数据管理(SQL/MED)。标准。国际标准化组织。
- [36] Paras Jain, Peter Kraft, Conor Power, Tathagata Das, Ion Stoica, 和 Matei Zaharia. 2023. 分析与比较 Lakehouse 存储系统。CIDR。
- [37] Project Jupyter,2024。Jupyter Notebooks。检索于 2024-06-20,来自 https: //jupyter.org/
- [38] Timo Kersten, Viktor Leis, Alfons Kemper, Thomas Neumann, Andrew Pavlo 和 Peter Boncz. 2018. 关于编译与向量化查询的一切:你一直想知道却不敢问的问题。Proc. VLDB Endow. 11, 13 (2018 年 9 月), 2209–2222. https://doi.org/10.14778/3275366.3284966
- [39] Changkyu Kim, Jatin Chhugani, Nadathur Satish, Eric Sedlar, Anthony D. Nguyen, Tim Kaldewey, Victor W. Lee, Scott A. Brandt, 和 Pradeep Dubey。2010 年。FAST:面向现代 CPU 和 GPU 的高速体系结构感知型树搜索。发表于《Proceedings of the 2010 ACM SIGMOD International Conference on Management of Data (Indianapolis, Indiana, USA) (SIGMOD '10)》。Association for Computing Machinery,New York, NY, USA,339–350。 https://doi.org/10.1145/1807167.1807206
- [40] Donald E. Knuth. 1973. 《计算机程序设计艺术(第3卷):排序和查找》. Addison-Wesley 出版社.
- [41] André Kohn、Viktor Leis 和 Thomas Neumann。2018 年。Adaptive Execution of Compiled Queries(已编译查询的自适应执行)。载于 2018 年 IEEE 第 34 届数据工程国际会议(ICDE)。第 197–208 页。 https://doi.org/10.1109/ICDE.2018.00027
- [42] Andrew Lamb, Matt Fuller, Ramakrishna Varadarajan, Nga Tran, Ben Vandiver, Lyric Doshi 和 Chuck Bear。2012 年。Vertica 分析型数据库:C-Store 七年之后。Proc. VLDB Endow. 5, 12(2012 年 8 月), 1790–1801。https://doi.org/10. 14778/2367502.2367518
- [43] Harald Lang, Tobias Mühlbauer, Florian Funke, Peter A. Boncz, Thomas Neumann, and Alfons Kemper. 2016. Data Blocks: Hybrid OLTP and OLAP on Compressed Storage using both Vectorization and Compilation. 收录于 Proceedings of the 2016 International Conference on Management of Data (San Francisco, California, USA) (SIGMOD '16). Association for Computing Machinery, New York, NY, USA, 311–326. https://doi.org/10.1145/2882903.2882925
- [44] Viktor Leis, Peter Boncz, Alfons Kemper 和 Thomas Neumann。2014。基于 morsel 的并行处理:面向多核时代的 NUMA 感知查询评估框架。收录于 2014 年 ACM SIGMOD International Conference on Management of Data 论文集(美国犹他州 Snowbird)(SIGMOD '14)。Association for Computing Machinery,美国纽约州纽约市,743–754。 https://doi.org/10.1145/2588555. 2610507
- [45] Viktor Leis、Alfons Kemper 和 Thomas Neumann。2013 年。The adaptive radix tree: 自适应基数树:用于内存数据库的 ARTful 索引。载于 2013 年 IEEE 第 29 届数据工程国际会议 (ICDE)。38–49。 https://doi.org/10.1109/ICDE. 2013.6544812
- [46] Chunwei Liu, Anna Pavlenko, Matteo Interlandi, and Brandon Haynes. 2023. 面向分析型 DBMS 的常见开放格式深度剖析。16, 11 (2023 年 7 月), 3044–3056。 https://doi.org/10.14778/3611479.3611507
- [47] Zhenghua Lyu, Huan Hubert Zhang, Gang Xiong, Gang Guo, Haozhou Wang, Jinbao Chen, Asim Praveen, Yu Yang, Xiaoming Gao, Alexandra Wang, Wen Lin, Ashwin Agrawal, Junfeng Yang, Hao Wu, Xiaoliang Li, Feng Guo, Jiang Wu, Jesse Zhang, and Venkatesh Raghavan. 2021. Greenplum:一种用于事务型和分析型工作负载的混合数据库(SIGMOD ’21)。Association for Computing Machinery(ACM), New York, NY, USA, 2530–2542. https: //doi.org/10.1145/3448016.3457562
- [48] Roger MacNicol 和 Blaine French。2004。Sybase IQ Multiplex——为分析而设计。载于第 30 届超大数据库国际会议(Very Large Data Bases, VLDB '04)论文集第 30 卷(加拿大多伦多)。VLDB Endowment,1227–1230。
- [49] Sergey Melnik, Andrey Gubarev, Jing Jing Long, Geofrey Romer, Shiva Shivakumar, Matt Tolton, Theo Vassilakis, Hossein Ahmadi, Dan Delorey, Slava Min, Mosha Pasumansky, and Jef Shute. 2020. Dremel:Web 规模交互式 SQL 分析十年历程. Proc. VLDB Endow. 13, 12 (2020 年 8 月), 3461–3472. https://doi.org/10.14778/3415478.3415568
- [50] Microsoft. 2024. Kusto Query Language。检索于 2024-06-20,来自:https: //github.com/microsoft/Kusto-Query-Language
- [51] Guido Moerkotte. 1998. Small Materialized Aggregates: 面向数据仓库的轻量级物化聚合索引结构(Small Materialized Aggregates: A Light Weight Index Structure for Data Warehousing)。收录于第 24 届国际超大型数据库会议(VLDB '98)论文集。476–487.
- [52] Jalal Mostafa、Sara Wehbi、Suren Chilingaryan 和 Andreas Kopmann。2022。SciTS:用于科学实验和工业物联网的时间序列数据库基准测试。收录于第 34 届国际科学与统计数据库管理会议(SSDBM '22)论文集。第 12 篇论文。https: //doi.org/10.1145/3538712.3538723
- [53] Thomas Neumann. 2011. 为现代硬件高效编译高效查询计划(Efficiently Compiling Efficient Query Plans for Modern Hardware)。Proc. VLDB Endow. 4, 9 (2011 年 6 月), 539–550. https://doi.org/10.14778/ 2002938.2002940
- [54] Thomas Neumann and Michael J. Freitag. 2020. Umbra:具备内存级性能的基于磁盘系统。见第十届创新数据系统研究会议(CIDR 2020),荷兰阿姆斯特丹,2020 年 1 月 12–15 日,在线论文集。www.cidrdb.org。http://cidrdb.org/cidr2020/papers/p29-neumanncidr20.pdf
- [55] Thomas Neumann、Tobias Mühlbauer 和 Alfons Kemper。2015。Fast Serializable Multi-Version Concurrency Control for Main-Memory Database Systems。收录于 Proceedings of the 2015 ACM SIGMOD International Conference on Management of Data(SIGMOD '15,澳大利亚维多利亚州墨尔本)。Association for Computing Machinery, New York, NY, USA, 677–689。https://doi.org/10.1145/2723372. 2749436
- [56] GitHub 上的 LevelDB。2024 年。LevelDB。检索于 2024-06-20,来自 https://github. com/google/leveldb
- [57] Patrick O'Neil, Elizabeth O'Neil, Xuedong Chen 和 Stephen Revilak. 2009. The Star Schema Benchmark and Augmented Fact Table Indexing(星型模式基准测试与增强型事实表索引). 载于《Performance Evaluation and Benchmarking》. Springer Berlin Heidelberg, 237–252. https: //doi.org/10.1007/978-3-642-10424-4_17
- [58] Patrick E. O'Neil, Edward Y. C. Cheng, Dieter Gawlick 和 Elizabeth J. O'Neil. 1996年。日志结构合并树(LSM-tree)。Acta Informatica 33 (1996), 351–385. https://doi.org/10.1007/s002360050048
- [59] Diego Ongaro 和 John Ousterhout. 2014. In Search of an Understandable Consensus Algorithm(寻求一种易于理解的一致性算法). 收录于 Proceedings of the 2014 USENIX Conference on USENIX Annual Technical Conference (USENIX ATC'14). 305–320. https://doi.org/doi/10. 5555/2643634.2643666
- [60] Patrick O'Neil, Edward Cheng, Dieter Gawlick 和 Elizabeth O'Neil。1996 年。Log-Structured Merge-Tree(日志结构合并树,LSM-Tree)。Acta Inf. 33, 4 (1996), 351–385. https: //doi.org/10.1007/s002360050048
- [61] Pandas. 2024. Pandas DataFrames。检索日期:2024-06-20,来源:https://pandas. pydata.org/
- [62] Pedro Pedreira, Orri Erling, Masha Basmanova, Kevin Wilfong, Laith Sakka, Krishna Pai, Wei He, 和 Biswapesh Chattopadhyay. 2022. Velox: Meta 的统一执行引擎。Proc. VLDB Endow. 15, 12 (2022 年 8 月), 3372–3384。 https: //doi.org/10.14778/3554821.3554829
- [63] Tuomas Pelkonen、Scott Franklin、Justin Teller、Paul Cavallaro、Qi Huang、Justin Meza 和 Kaushik Veeraraghavan。2015。Gorilla:一种快速、可扩展的内存型时间序列数据库。Proceedings of the VLDB Endowment 8, 12 (2015), 1816–1827。 https://doi.org/10.14778/2824032.2824078
- [64] Orestis Polychroniou、Arun Raghavan 和 Kenneth A. Ross。2015。Rethinking SIMD Vectorization for In-Memory Databases。见 Proceedings of the 2015 ACM SIGMOD International Conference on Management of Data (SIGMOD '15)。1493–1508。https://doi.org/10.1145/2723372.2747645
- [65] PostgreSQL. 2024. PostgreSQL - 外部数据封装器(Foreign Data Wrappers)。访问于 2024-06-20,来自 https://wiki.postgresql.org/wiki/Foreign_data_wrappers
- [66] Mark Raasveldt、Pedro Holanda、Tim Gubner 和 Hannes Mühleisen。2018 年。公平基准测试之难:数据库性能测试中的常见陷阱。载于《数据库系统测试研讨会论文集》(美国德克萨斯州休斯敦)(DBTest'18)。文章 2,共 6 页。https://doi.org/10.1145/3209950.3209955
- [67] Mark Raasveldt 和 Hannes Mühleisen. 2019. DuckDB: 一种可嵌入式分析数据库(SIGMOD '19)。Association for Computing Machinery, New York, NY, USA, 1981–1984. https://doi.org/10.1145/3299869.3320212
- [68] Jun Rao 和 Kenneth A. Ross. 1999. Cache Conscious Indexing for Decision-Support in Main Memory(用于主存中决策支持的缓存感知索引). In Proceedings of the 25th International Conference on Very Large Data Bases (VLDB '99). San Francisco, CA, USA, 78–89.
- [69] Navin C. Sabharwal 和 Piyush Kant Pandey。2020。《Working with Prometheus Query Language (PromQL)》。载于《Monitoring Microservices and Containerized Applications》。https://doi.org/10.1007/978-1-4842-6216-0_5
- [70] Todd W. Schneider. 2022 年. 纽约市出租车和网约车数据。访问日期:2024-06-20,来源:https://github.com/toddwschneider/nyc-taxi-data
- [71] Mike Stonebraker, Daniel J. Abadi, Adam Batkin, Xuedong Chen, Mitch Cherniack, Miguel Ferreira, Edmond Lau, Amerson Lin, Sam Madden, Elizabeth O'Neil, Pat O'Neil, Alex Rasin, Nga Tran, and Stan Zdonik. 2005. C-Store:一种列式 DBMS。收录于第 31 届超大规模数据库国际会议(VLDB '05)论文集,第 553–564 页。
- [72] Teradata. 2024. Teradata Database. 2024-06-20 检索自 https://www. teradata.com/resources/datasheets/teradata-database
- [73] Frederik Transier. 2010. Algorithms and Data Structures for In-Memory Text Search Engines(内存文本搜索引擎的算法与数据结构). 博士论文. https://doi.org/10.5445/IR/1000015824
- [74] Adrian Vogelsgesang, Michael Haubenschild, Jan Finis, Alfons Kemper, Viktor Leis, Tobias Muehlbauer, Thomas Neumann 和 Manuel Then。2018 年。Get Real: How Benchmarks Fail to Represent the Real World(实事求是:基准测试如何无法代表真实世界)。收录于 Testing Database Systems 研讨会论文集(DBTest'18,美国德克萨斯州休斯敦)。第 1 篇文章,共 6 页。https://doi.org/10.1145/3209950.3209952
- [75] LZ4 网站. 2024. LZ4. 访问日期:2024-06-20,链接:https://lz4.org/
- [76] PRQL 网站。2024。PRQL。检索自 2024-06-20,https://prql-lang.org [77] Till Westmann、Donald Kossmann、Sven Helmer 和 Guido Moerkotte。2000。The Implementation and Performance of Compressed Databases。SIGMOD Rec.
- 29, 3(2000 年 9 月),55–67。 https://doi.org/10.1145/362084.362137 [78] Fangjin Yang、Eric Tschetter、Xavier Léauté、Nelson Ray、Gian Merlino 和 Deep Ganguli。2014。Druid:一种实时分析型数据存储系统。收录于 2014 年 ACM SIGMOD 数据管理国际会议论文集(Proceedings of the 2014 ACM SIGMOD International Conference on Management of Data,SIGMOD '14,Snowbird, Utah, USA)。Association for Computing Machinery,New York, NY, USA,157–168。 https://doi.org/10.1145/2588555.2595631
- [79] Tianqi Zheng、Zhibin Zhang 和 Xueqi Cheng. 2020. SAHA:面向分析型数据库的字符串自适应哈希表. Applied Sciences 10, 6 (2020). https: //doi.org/10.3390/app10061915
- [80] Jingren Zhou and Kenneth A. Ross. 2002. 使用 SIMD 指令实现数据库操作。载于 2002 年 ACM SIGMOD 数据管理国际会议论文集 (SIGMOD '02)。145–156. https://doi.org/10. 1145/564691.564709
- [81] Marcin Zukowski、Sandor Heman、Niels Nes 和 Peter Boncz。2006 年。Super-Scalar RAM-CPU Cache Compression(超标量 RAM-CPU 缓存压缩)。收录于第 22 届数据工程国际会议(International Conference on Data Engineering,ICDE '06)论文集,第 59 页。https://doi.org/10.1109/ICDE. 2006.150