跳到主要内容
跳到主要内容

架构概述

这是我们VLDB 2024 科学论文的网络版本。我们也写博客介绍了其背景和历程,并推荐观看 ClickHouse 首席技术官及创始人 Alexey Milovidov 的 VLDB 2024 演讲:

摘要

在过去的几十年中,存储和分析的数据量呈指数级增长。各行各业的企业开始依靠这些数据来改进产品、评估绩效并做出重要的商业决策。然而,随着数据量的不断增长成为互联网规模,企业需要以一种具成本效益和可扩展的方式管理历史数据和新数据,同时使用高并发的查询来分析数据,并期望实时延迟(例如,低于一秒,具体取决于使用场景)。

本文概述了 ClickHouse,这是一种流行的开源 OLAP 数据库,旨在提供针对大规模数据集(例如,数据量达到 PB 级别并具有高摄取率)进行高性能分析的能力。它的存储层结合了基于传统日志结构合并(LSM)树的数据格式以及用于后台历史数据连续转换的新技术(例如,聚合、归档)。查询使用方便的 SQL 方言编写,并由最先进的向量化查询执行引擎处理,可选择性地进行代码编译。ClickHouse 积极使用修剪技术来避免评估查询中无关的数据。其他数据管理系统可以在表函数、表引擎或数据库引擎级别集成。实际的基准测试表明,ClickHouse 是市场上最快的分析数据库之一。

1 引言

本文描述了 ClickHouse,这是一种列式 OLAP 数据库,专为高性能分析查询设计,适用于具有万亿行和数百列的表。ClickHouse 于 2009 年作为针对大规模日志文件数据的过滤和聚合操作符启动,并于 2016 年开源。 图 1 说明了本文描述的主要功能何时被引入 ClickHouse。

ClickHouse 旨在解决现代分析数据管理的五个关键挑战:

  1. 海量数据集和高摄取率。许多数据驱动的应用程序(如网页分析、金融和电子商务)具有庞大且不断增长的数据量。为了处理海量数据集,分析数据库不仅必须提供有效的索引和压缩策略,还必须能够在多个节点之间分配数据(扩展),因为单个服务器的存储限制为几十 TB。此外,最近的数据通常对实时洞察比历史数据更相关。因此,分析数据库必须能够以始终较高的速率或突发方式摄取新数据,并且能够持续“降低优先级”(例如,聚合、归档)历史数据,而不会拖慢并行报告查询的速度。

  2. 许多同时查询并期望低延迟。查询通常可以分为即席查询(例如,探索性数据分析)或定期查询(例如,定期仪表板查询)。用例越具互动性,要求的查询延迟越低,这就导致了查询优化和执行上的挑战。定期查询还提供了将物理数据库布局调整到工作负载的机会。因此,数据库应该提供修剪技术,以优化频繁的查询。根据查询的优先级,数据库还必须在共享系统资源(如 CPU、内存、磁盘和网络 I/O)上提供平等或优先的访问,即使在大量查询同时运行时也如此。

  3. 数据存储、存储位置和格式的多样化。为了与现有数据架构集成,现代分析数据库应该表现出较高的开放性,以在任何系统、位置或格式中读取和写入外部数据。

  4. 方便的查询语言,支持性能检查。OLAP 数据库的现实使用还提出了其他“软性”要求。例如,用户通常希望使用一种表达性 SQL 方言与数据库进行交互,而不是一个小众编程语言,并希望支持嵌套数据类型和广泛的常规、聚合和窗口函数。分析数据库还应提供复杂的工具,以检查系统或单个查询的性能。

  5. 行业级的健壮性和多样化的部署。由于商品硬件不可靠,数据库必须提供数据复制,以防止节点故障。此外,数据库应能够在任何硬件上运行,从旧笔记本电脑到强大的服务器。最后,为了避免 JVM 程序中的垃圾回收开销并实现裸金属性能(例如,SIMD),理想情况下,数据库作为目标平台的本机二进制文件进行部署。

图 1: ClickHouse 时间线。

2 架构

图 2: ClickHouse 数据库引擎的高层架构。

图 2所示,ClickHouse 引擎分成三个主要层次:查询处理层(在第 4 节中描述)、存储层(第 3 节)和集成层(第 5 节)。除了这些之外,访问层管理用户会话,并通过不同协议与应用程序进行通信。还有正交组件用于线程管理、缓存、基于角色的访问控制、备份和持续监控。ClickHouse 是用 C++ 构建的单个静态链接二进制文件,没有依赖项。

查询处理遵循传统的范式,包括解析传入的查询、构建和优化逻辑和物理查询计划以及执行。ClickHouse 使用类似于 MonetDB/X100 的向量化执行模型 [11],结合机会性代码编译 [53]。查询可以使用功能丰富的 SQL 方言、PRQL [76] 或 Kusto 的 KQL [50] 编写。

存储层由不同的表引擎组成,封装了表数据的格式和位置。表引擎分为三类:第一类是 MergeTree* 表引擎系列,它代表了 ClickHouse 中主要的持久化格式。基于 LSM 树的理念 [60],表被分为水平的、排序的部分,后台进程会持续地将这些部分合并在一起。个别的 MergeTree* 表引擎在合并如何将输入部分的行组合时存在差异。例如,可以对过时行进行聚合或替换。

第二类是特殊用途的表引擎,用于加速或分配查询执行。该类包括称为字典的内存内键值表引擎。字典 缓存定期执行的查询结果,这些查询是针对内部或外部数据源执行的。这在某些情况下显著减少了访问延迟,可以容忍一定的数据陈旧性。其他特殊用途的表引擎例子包括用于临时表的纯内存引擎和用于透明数据分片的分布式表引擎(见下文)。

第三类表引擎是用于与外部系统(如关系数据库(例如 PostgreSQL、MySQL)、发布/订阅系统(例如 Kafka、RabbitMQ [24])或键值存储(例如 Redis))进行双向数据交换的虚拟表引擎。虚拟引擎还可以与数据湖(例如 Iceberg、DeltaLake、Hudi [36])或对象存储中的文件(例如 AWS S3、Google GCP)进行交互。

ClickHouse 支持在多个集群节点间对表进行分片和复制,以实现可扩展性和可用性。分片根据分片表达式将表划分为一组表分片。个别的分片是相互独立的表,通常位于不同的节点上。客户端可以直接读取和写入分片,即将它们视为独立的表,或者使用分布式特定表引擎,该引擎提供了所有表分片的全局视图。分片的主要目的是处理超出单个节点(通常为几十 TB 的数据)容量的数据集。分片的另一个用途是在多个节点上分配一个表的读写负载,即负载均衡。正交于此,一个分片可以在多个节点间进行复制,以防止节点故障。为此,每个 Merge-Tree* 表引擎都有对应的 ReplicatedMergeTree* 引擎,使用基于 Raft 共识的多主协调方案 [59](由Keeper实现,Keeper 是用 C++ 编写的 Apache Zookeeper 的替代品),以保证每个分片始终有一个可配置数量的副本。第 3.6 节将详细讨论复制机制。作为示例, 图 2 显示了一个具有两个分片的表,每个分片复制到两个节点。

最后,ClickHouse 数据库引擎可以在本地、云端、独立或进程内模式中运行。在本地模式下,用户将 ClickHouse 设置为本地单个服务器或具有分片和/或复制的多节点集群。客户端通过本地原生协议、MySQL 二进制协议、PostgreSQL 二进制协议或 HTTP REST API 与数据库通信。云模式由 ClickHouse Cloud 表示,这是一个完全托管和自动扩展的 DBaaS 提供。虽然本文重点介绍本地模式,但我们计划在后续出版物中描述 ClickHouse Cloud 的架构。独立模式 将 ClickHouse 转变为用于分析和转换文件的命令行工具,使其成为类似于 Unix 工具如 cat 和 grep 的基于 SQL 的替代品。虽然这不需要先前的配置,但独立模式受限于单个服务器。最近,开发了一种名为 chDB [15] 的进程内模式,适用于像 Jupyter notebooks [37] 这样的交互式数据分析使用案例,结合 Pandas 数据框 [61]。受到 DuckDB [67] 的启发, chDB 将 ClickHouse 嵌入到主机进程中,作为高性能 OLAP 引擎。与其他模式相比,这允许在数据库引擎与应用程序之间高效地传递源数据和结果数据,而无需复制,因为它们在同一地址空间中运行。

3 存储层

本节讨论 MergeTree* 表引擎作为 ClickHouse 的本地存储格式。我们描述它们的磁盘表示,并讨论 ClickHouse 中的三种数据修剪技术。之后,我们呈现合并策略,它们能够持续地转换数据,而不会影响同时的插入。最后,我们解释更新和删除的实现方式,以及数据去重、数据复制和 ACID 的合规性。

3.1 磁盘格式

每个 MergeTree* 表引擎中的表被组织为不可变的表部分集合。每当一组行插入到表中时,就会创建一个部分。部分是自包含的,意为它们包含了解释其内容所需的所有元数据,而无需额外查找中心目录。为了保持每个表的部分数量较低,后台合并作业会定期将多个较小的部分合并到更大的部分中,直到达到可配置的部分大小(默认为 150 GB)。由于部分按表的主键列排序(见第 3.2 节),因此在合并时使用高效的 k-way 合并排序 [40]。源部分标记为非活跃状态,并在引用计数降至零(即没有进一步的查询从它们中读取)时最终被删除。

行可以以两种模式插入:在同步插入模式下,每个 INSERT 语句创建一个新部分并将其附加到表中。为了减少合并的开销,数据库客户端鼓励一次以大批量插入元组,例如,一次插入 20,000 行。然而,如果数据需要实时分析,客户端批量处理造成的延迟往往是不可接受的。例如,观察性使用案例通常涉及数千个监控代理持续发送少量事件和指标数据。这些场景可以使用异步插入模式,其中 ClickHouse 将来自多个传入 INSERT 的行缓冲到同一表中,并仅在缓冲区大小超过可配置的阈值或超时到期后创建新部分。

图 3: MergeTree*-引擎表的插入和合并。

图 3 说明了对 MergeTree*-引擎表的四个同步和两个异步插入。两次合并减少了活动部分的数量,从最初的五个减少到两个。

与 LSM 树 [58] 及其在各种数据库中的实现 [13, 26, 56] 相比,ClickHouse 将所有部分视为平等,而不是将它们排列在层次中。因此,合并不再限于同一级别的部分。由于这还放弃了部分的隐含时间顺序,因此需要基于其他机制进行更新和删除,而不是基于墓碑(见第 3.4 节)。ClickHouse 直接将插入写入磁盘,而其他基于 LSM 树的存储通常使用写预日志(见第 3.7 节)。

一个部分对应于磁盘上的一个目录,每个列包含一文件。作为优化,小部分(默认小于 10 MB)的列连续存储在一个文件中,以增加读写时的空间局部性。一个部分的行进一步逻辑上划分为 8192 条记录的组,称为颗粒。颗粒代表 ClickHouse 中由扫描和索引查找操作处理的最小不可分割的数据单元。然而,磁盘数据的读写并不是在颗粒级别上执行的,而是在块的粒度上执行,这些块将同一列中的多个相邻颗粒组合在一起。基于每块的可配置字节大小(默认 1 MB),形成新块,即块中的颗粒数量是可变的,并取决于列的数据类型和分布。块进一步被压缩以减少其大小和 I/O 成本。 默认情况下,ClickHouse 使用 LZ4 [75] 作为通用压缩算法,但用户也可以指定用于浮点数据的专用编解码器,如 Gorilla [63] 或 FPC [12]。压缩算法也可以链式应用。例如,可以首先使用增量编码来减少数值的逻辑冗余,然后执行重压缩,最后使用 AES 编解码器加密数据。在从磁盘加载到内存时,块是在其上随即解压缩的。为了在压缩的情况下启用对单个颗粒的快速随机访问,ClickHouse 还为每列存储了一个映射,将每个颗粒 id 与其所属压缩块在列文件中的偏移量及颗粒在未压缩块中的偏移量关联起来。

列还可以被字典编码 [2, 77, 81] 或使用两个特殊的包装数据类型变为 Nullable:LowCardinality(T) 通过整数 id 替代原始列值,从而显著减少具有少量唯一值的数据的存储开销。Nullable(T) 向 T 列添加一个内部位图,表示列值是否为 NULL。

最后,表可以使用任意分区表达式进行范围、哈希或轮询分区。为了启用分区修剪,ClickHouse 还为每个分区存储分区表达式的最小值和最大值。用户可选择创建更高级的列统计信息(例如,HyperLogLog [30] 或 t-digest [28] 统计信息),这些统计信息也提供基数估计。

3.2 数据修剪

在大多数使用案例中,仅为了回答单个查询而扫描 PB 级别的数据速度太慢且成本过高。ClickHouse 支持三种数据修剪技术,允许在搜索过程中跳过大多数行,从而显著加快查询速度。

首先,用户可以为表定义 主键索引。主键列决定各个部分内行的排序顺序,即索引是局部聚集的。ClickHouse 还为每个部分存储从每个颗粒的第一行的主键列值到颗粒 id 的映射,即索引是稀疏的 [31]。生成的数据结构通常足够小,可以完全保留在内存中,例如,仅需要 1000 个条目即可为 810 万行建立索引。主键的主要目的是使用二进制搜索评估经常被过滤的列的相等性和范围谓词,而不是顺序扫描(见第 4.4 节)。局部排序还可用于部分合并和查询优化,例如基于排序的聚合或在主键列形成排序列的前缀时,去除物理执行计划中的排序操作符。

图 4 显示了一个表上 EventTime 列的主键索引,该表具有页面展示统计信息。通过对主键索引进行二进制搜索,可以找到与查询中范围谓词匹配的颗粒,而无需顺序扫描 EventTime。

图 4: 使用主键索引评估过滤器。

其次,用户可以创建 表投影,即按不同主键排序的具有相同行的表的替代版本 [71]。投影可以加速对与主表的主键不同的列的过滤查询,但代价是插入、合并和空间消耗的开销会增加。默认情况下,仅从新插入的主表部分惰性填充投影,而不从现有部分填充,除非用户将投影完全物化。查询优化器根据预估的 I/O 成本在从主表读取或从投影读取之间进行选择。如果某个部分没有投影,查询执行将回退到相应的主表部分。

第三,跳过索引提供了一个轻量级的替代方案来代替投影。跳过索引的思想是在多个连续颗粒的层面存储少量元数据,从而避免扫描无关行。可以为任意索引表达式创建跳过索引,并使用可配置的粒度,即跳过索引块中的颗粒数量。可用的跳过索引类型包括:1. 最小-最大索引 [51],存储每个索引块的索引表达式的最小值和最大值。此索引类型适用于具有小绝对范围的局部聚集数据,例如松散排序数据。 2. 集合索引,存储可配置数量的唯一索引块值。此索引最适用于具有小局部基数的数据,即“聚拢在一起”的值。 3. 布隆过滤器索引 [9],构建用于行、标记或 n-gram 值,具有可配置的假阳性率。这些索引支持文本搜索 [73],但与最小-最大和集合索引不同,不能用于范围或负谓词。

3.3 合并时的数据转换

商业智能和观察性用例经常需要处理以恒定高速度或突发生成的数据。此外,最近生成的数据通常对有意义的实时洞察比历史数据更相关。这些用例需要数据库在持续降低历史数据体积的同时,维持高数据摄取率,方法包括聚合或数据老化。ClickHouse 允许使用不同的合并策略对现有数据进行持续增量转换。合并时的数据转换不会影响 INSERT 语句的性能,但不能保证表中永远不包含不需要的(例如,过时或未聚合的)值。如果需要,所有合并时的转换都可以通过在 SELECT 语句中指定关键字 FINAL 在查询时应用。

替换合并 仅保留基于其包含部分的创建时间戳的元组的最新插入版本,旧版本会被删除。如果两个元组的主键列值相同,则认为它们是等价的。为了明确控制保留哪个元组,也可以为比较指定一个特殊的版本列。替换合并通常用作合并时的更新机制(通常在更新频繁的用例中),或作为插入时数据去重的替代方案(见第 3.5 节)。

聚合合并 将具有相等主键列值的行合并为一行聚合行。非主键列必须处于持有汇总值的部分聚合状态。两个部分聚合状态(例如,avg() 的和与计数)会组合成一个新的部分聚合状态。聚合合并通常在物化视图中使用,而不是普通表中。物化视图基于对源表的转换查询进行填充。与其他数据库不同,ClickHouse 不会定期使用源表的整个内容刷新物化视图。物化视图通常在新部分插入到源表时,根据转换查询的结果进行增量更新。

图 5 显示了在拥有页面展示统计信息的表上定义的物化视图。对于插入到源表的新部分,转换查询计算按区域分组的最大值和平均延迟,并将结果插入物化视图。聚合函数 avg() 和 max() 配带扩展 -State 返回部分聚合状态,而不是实际结果。为物化视图定义的聚合合并会不断合并不同部分中的部分聚合状态。为了获得最终结果,用户用 avg() 和 max() 结合 -Merge 扩展合并物化视图中的部分聚合状态。

图 5: 物化视图中的聚合合并。

生存时间 (TTL) 合并 为历史数据提供老化机制。与删除和聚合合并不同,TTL 合并一次只处理一个部分。TTL 合并是根据带有触发器和操作的规则定义的。触发器是一个计算每一行的时间戳的表达式,并与运行 TTL 合并的时间进行比较。虽然这允许用户在行级别上控制操作,但我们发现检查所有行是否满足给定条件并在整个部分上运行操作就足够了。可用的操作包括 1. 将部分移动到另一个卷(例如,更便宜且速度较慢的存储),2. 重新压缩部分(例如,使用更重的编解码器),3. 删除部分,以及 4. 回滚,即使用分组键和聚合函数对行进行聚合。

例如,考虑在列表 1 中的日志表定义。ClickHouse 将把时间戳列值早于一星期的部分移动到速度较慢但便宜的 S3 对象存储中。

列表 1: 一周后将部分移动到对象存储。

3.4 更新和删除

MergeTree* 表引擎的设计倾向于追加仅工作负载,但某些用例偶尔需要修改现有数据,例如为了符合法规。对于更新或删除数据,存在两种方法,均不阻止并行插入。

突变 在原地重写表的所有部分。为了防止表(删除)或列(更新)在短时间内临时翻倍,此操作是非原子的,即并行 SELECT 语句可能读取突变和非突变部分。突变保证在操作结束时数据的物理更改。删除突变仍然非常昂贵,因为它们会重写所有部分中的所有列。

作为替代方案,轻量级删除 仅更新一个内部位图列,指示某行是否被删除。ClickHouse 在 SELECT 查询中附加一个额外的过滤器,以基于位图列排除已删除的行。已删除的行仅在未来的某个不确定时间由常规合并物理删除。根据列计数,轻量级删除可能比突变快得多,但代价是 SELECT 的速度较慢。

对同一表执行的更新和删除操作通常被期望是罕见的,并且被序列化以避免逻辑冲突。

3.5 幂等插入

实践中经常出现的问题是,客户如何在将数据发送到服务器以插入到表中后处理连接超时。在这种情况下,客户端很难区分数据是否已成功插入。这个问题通常通过从客户端重新发送数据到服务器并依赖主键或唯一约束来拒绝重复插入来解决。数据库通过基于二叉树的索引结构快速执行所需的点查找 [39, 68]、基数树 [45] 或哈希表 [29]。由于这些数据结构对每个元组建立索引,因此它们的空间和更新开销在大数据集和高摄取率下会变得不堪重负。

ClickHouse 提供了一种更轻量级的替代方案,基于每次插入最终创建一个部分的事实。更具体地说,服务器维护最近 N 个插入部分(例如,N=100)的哈希,并忽略已知哈希的部分的重新插入。非复制和复制表的哈希分别存储在 Keeper 中。因此,插入变得幂等,即客户端可以在超时后简单地重新发送相同的一批行,并假设服务器会负责去重。为了对去重过程有更多控制,客户端还可以选择提供一个插入令牌作为部分哈希。虽然基于哈希的去重需要与散列新行相关的开销,但存储和比较哈希的成本微不足道。

3.6 数据复制

复制是高可用性(对节点故障的容忍)的前提,但也用于负载均衡和零停机升级 [14]。在 ClickHouse 中,复制基于表状态的概念,这些状态由一组表片段(第 3.1 节)和表元数据(如列名和类型)组成。节点通过三种操作来推进表的状态:1. 插入将新片段添加到状态中,2. 合并将新片段添加到状态中并删除现有片段,3. 变更和 DDL 语句根据具体操作添加片段、删除片段和/或更改表元数据。操作在单个节点上本地执行,并记录为全局复制日志中的状态转换序列。

复制日志由典型的三台 ClickHouse Keeper 进程的集群维护,使用 Raft 共识算法 [59] 提供分布式和容错的协调层,适用于 ClickHouse 节点的集群。所有集群节点最初指向复制日志中的同一位置。当节点执行本地插入、合并、变更和 DDL 语句时,复制日志会异步地在所有其他节点上重放。因此,复制表仅最终一致,即节点在逐渐趋向于最新状态时,可以临时读取旧的表状态。上述大多数操作也可以同步执行,直到节点的法定人数(例如,过半数节点或所有节点)采纳新状态。

例如,图 6显示了在三台 ClickHouse 节点的集群中,最初为空的复制表。节点 1 首先接收两个插入语句,并在 Keeper 集群中记录它们(1 2)。接下来,节点 2 通过获取第一个日志条目(3)并从节点 1 下载新片段(4)来重放该条目,而节点 3 则重放两个日志条目(3 4 5 6)。最后,节点 3 将两个片段合并为一个新片段,删除输入片段,并在复制日志中记录合并条目(7)。

图 6:在三节点集群中的复制示例。

三种优化方法可加速同步:首先,加入集群的新节点并不从头重播复制日志,而是简单地复制写入最后一个复制日志条目的节点的状态。其次,合并通过重复本地合并或从其他节点获取结果片段进行重播。具体行为是可配置的,可以平衡 CPU 消耗和网络 I/O。例如,跨数据中心复制通常更倾向于本地合并以降低运营成本。第三,节点并行重放互相独立的复制日志条目。这包括例如,连续插入同一表的新片段的获取,或在不同表上的操作。

3.7 ACID 合规性

为了最大化并发读写操作的性能,ClickHouse 尽可能避免加锁。查询是针对在查询开始时创建的所有相关表中所有片段的快照执行的。这确保了由并行 INSERT 或合并(第 3.1 节)插入的新片段不参与执行。为了防止片段被同时修改或删除(第 3.4 节),在查询执行期间处理的片段的引用计数会增加。正式地,这对应于通过基于版本的片段实现的快照隔离。结果是,语句通常不符合 ACID,除非在快照拍摄时并发写入每次只影响单个片段的罕见情况。

实际上,ClickHouse 的大多数写重决策使用案例即使在断电情况下也容忍小的丢失新数据的风险。数据库利用这一点,默认情况下不强制将新插入的片段提交(fsync)到磁盘,而是允许内核批量写入,从而放弃原子性。

4 查询处理层

图 7:在 SIMD 单元、核心和节点之间的并行化示意图。

图 7所示,ClickHouse 在数据元素、数据块和表分片层面并行化查询。多个数据元素可以在操作符内同时使用 SIMD 指令处理。在单个节点上,查询引擎同时在多个线程中执行操作符。ClickHouse 使用与 MonetDB/X100 相同的向量化模型 [11],即操作符生成、传递和消费多个行(数据块),而不是单行,以最小化虚拟函数调用的开销。如果源表被拆分为不相交的表分片,多个节点可以同时扫描这些分片。因此,所有硬件资源得到了充分利用,并且可以通过添加节点和通过添加内核水平扩展查询处理。

本节的其余部分首先详细描述数据元素、数据块和分片粒度的并行处理。然后,我们介绍一些关键优化,以最大化查询性能。最后,我们讨论 ClickHouse 如何在存在并发查询的情况下管理共享系统资源。

4.1 SIMD 并行化

在操作符之间传递多个行创建了向量化的机会。向量化基于手动编写的内在函数 [64, 80] 或编译器自动向量化 [25]。受益于向量化的代码被编译为不同的计算内核。例如,查询操作符的内部热循环可以基于非向量化内核、自动向量化的 AVX2 内核和手动向量化的 AVX-512 内核来实现。运行时根据 cpuid 指令选择最快的内核。这个方法使 ClickHouse 可以在最老的系统(最低要求 SSE 4.2)上运行,同时在最新硬件上提供显著的性能提升。

4.2 多核并行化

图 8:具有三个通道的物理操作计划。

ClickHouse 采用传统方法 [31],将 SQL 查询转换为物理计划操作符的有向图。操作符计划的输入由读取原生数据或任何受支持的第三方格式的数据的特殊源操作符表示(参见第 5 节)。同样,特殊的汇操作符将结果转换为所需的输出格式。在查询编译时,物理操作计划根据可配置的最大工作线程数量(默认情况下为核心数)和源表的大小展开为独立的执行通道。通道将要由并行操作符处理的数据分解为不重叠的范围。为了最大化并行处理的机会,通道在最后合并。

例如,图 8中节点 1 的框显示了对具有页面展示统计的表的典型 OLAP 查询的操作符图。在第一阶段,源表的三个不相交范围被同时过滤。一个重新分区交换操作符动态路由结果块以保持处理线程均匀利用。如果扫描范围的选择性差异显著,在第一阶段后通道可能会变得不平衡。在第二阶段,存活的行按 RegionID 分组。聚合操作符维护以 RegionID 为分组列的本地结果组,并为 avg() 提供每组的总和和计数作为部分聚合状态。本地聚合结果最终由 GroupStateMerge 操作符合并为全局聚合结果。该操作符也是管道断路器,即只有在聚合结果完全计算后,第三阶段才能开始。在第三阶段,结果组首先通过一个分发交换操作符分成三个同样大的不相交分区,然后按 AvgLatency 排序。排序分为三个步骤:首先,ChunkSort 操作符对每个分区的单个块进行排序。其次,StreamSort 操作符维护一个本地排序结果,该结果与通过 2 路合并排序的新排序块结合。最后,MergeSort 操作符使用 k 路排序结合本地结果以获得最终结果。

操作符是状态机,并通过输入和输出端口互相连接。操作符的三个可能状态是需要块、准备好和完成。这些状态之间的转换如下:从需要块到准备好,必须将在操作符的输入端口中放置一个块;从准备好到完成,操作符处理输入块并生成输出块;从完成到需要块,输出块从操作符的输出端口中移除。两个连接操作符中的第一次和第三次状态转换只能在一个组合步骤中执行。源操作符(汇操作符)只有准备好和完成两个状态(需要块和完成)。

工作线程不断遍历物理操作计划,并执行状态转换。为了保持 CPU 缓存热,计划中包含提示,以指示同一线程应在同一通道中处理连续的操作符。并行处理发生在多个输入之间的水平(例如,在 图 8 中,聚合操作符同时执行)和在未被管道断路器分隔的阶段之间的垂直(例如,在 图 8 中,同一通道的过滤器和聚合操作符可以同时运行)。为了避免在新查询开始时或并发查询结束时出现过度和不足的订阅,可以在查询过程中根据查询开始时指定的查询工作线程最大数量和一个之间动态调整并行度(参见第 4.5 节)。

操作符还可以以两种方式在运行时进一步影响查询执行。首先,操作符可以动态创建并连接新的操作符。这主要用于在内存消耗超过可配置阈值时切换到外部聚合、排序或连接算法,而不是取消查询。其次,操作符可以请求工作线程移入异步队列。这在等待远程数据时提供了更有效的工作线程使用。

ClickHouse 的查询执行引擎和基于小块驱动的并行性 [44] 在于通道通常在不同的核心/ NUMA 套接字上执行,并且工作线程可以从其他通道窃取任务。此外,也没有中央调度组件;相反,工作线程通过不断遍历操作符计划单独选择其任务。与基于小块驱动的并行性不同,ClickHouse 将最大并行度直接纳入计划中,并使用更大的范围来划分源表,与默认的小块大小大约为 100,000 行相比。这在某些情况下可能导致阻塞(例如,当不同通道中过滤器操作符的运行时差异很大时),但我们发现,广泛使用的交换操作符(例如重新分区)至少可以避免这样的不平衡在各个阶段之间的积累。

4.3 多节点并行化

如果查询的源表经过分片,接收查询的节点上的查询优化器(发起节点)尽量在其他节点上执行尽可能多的工作。来自其他节点的结果可以在查询计划的不同点集成。根据查询情况,远程节点可以 1. 将原始源表列流式传输到发起节点,2. 过滤源列并发送生存下来的行,3. 执行过滤和聚合步骤,并发送具有部分聚合状态的本地结果组,或 4. 运行整个查询,包括过滤、聚合和排序。

图 8中的节点 2 ... N 显示了在持有命中表的分片的其他节点上执行的计划片段。这些节点过滤和分组本地数据,然后将结果发送到发起节点。节点 1 上的 GroupStateMerge 操作符合并本地和远程结果,然后最后对结果组排序。

4.4 整体性能优化

本节介绍应用于查询执行不同阶段的几个关键性能优化。

查询优化。第一组优化应用于语义查询表示之上,语义查询表示来源于查询的 AST。这类优化的例子包括常量折叠(例如,concat(lower('a'),upper('b')) 变为 'aB')、从某些聚合函数中提取标量(例如,sum(a2) 变为 2 * sum(a))、共同子表达式消除以及将相等过滤器的并联变换为 IN 列表(例如,x=c 或 x=d 变为 x IN (c,d))。经过优化的语义查询表示随后转换为逻辑操作计划。基于逻辑计划的优化包括过滤下推、重新排列函数评估和排序步骤,具体取决于哪一个估计更加昂贵。最后,逻辑查询计划被转换为物理操作计划。这个转换可以利用所涉及表引擎的特殊性。例如,在 MergeTree-表引擎的情况下,如果 ORDER BY 列形成主键的前缀,则可以按照磁盘顺序读取数据,并且可以从计划中删除排序操作符。此外,如果聚合中的分组列形成主键的前缀,ClickHouse 可以使用排序聚合,即直接聚合预排序输入中相同值的运行。与哈希聚合相比,排序聚合显著减少了内存使用,并且聚合值可以在运行处理之后立即传递给下一个操作符。

查询编译。ClickHouse 采用 基于 LLVM 的查询编译 动态融合相邻的计划操作符 [38, 53]。例如,表达式 a * b + c + 1 可以合并为一个操作符,而不是三个操作符。除了表达式外,ClickHouse 还用于评估一次性处理多个聚合函数(即用于 GROUP BY)和具有多个排序键的排序时的编译。查询编译减少了虚拟调用的数量,将数据保存在寄存器或 CPU 缓存中,并对分支预测有所帮助,因为需要执行的代码更少。此外,运行时编译使得丰富的优化集成为可能,例如编译器中实现的逻辑优化和局部优化,并使得能访问到最快的本地可用 CPU 指令。编译仅当相同的常规、聚合或排序表达式被不同查询执行超过可配置次数时才会启动。编译后的查询操作符会被缓存,并可以被后续查询重用。

主键索引评估。如果条件的合取范式中的一部分过滤子句构成主键列的前缀,ClickHouse 会使用主键索引来评估 WHERE 条件。主键索引从左到右分析按字典序排序的键值范围。与主键列对应的过滤子句使用三元逻辑评估-它们在范围内均为真、均为假或混合真/假。在后者情况下,该范围被拆分为子范围,递归分析。对于过滤条件中的函数还有其他优化。首先,函数具有描述其单调性的特征,例如,toDayOfMonth(date) 在一个月内分段单调。单调性特征允许推断一个函数是否对排序的输入键值范围产生排序结果。其次,某些函数可以计算给定函数结果的原像。这用于将常量的比较替换为键列上的函数调用,通过比较键列值与原像。例如,可以将 toYear(k) = 2024 替换为 k >= 2024-01-01 && k < 2025-01-01。

数据跳过。ClickHouse 尝试在查询运行时避免对数据的读取,使用第 3.2 节 中介绍的数据结构。此外,针对不同列的过滤子句按下降的估计选择性顺序依次进行评估,基于启发式和(可选)的列统计信息。只有包含至少一个匹配行的数据块会被传递到下一个谓词。这逐渐减少了从谓词到谓词所需读取的数据量和要执行的计算数量。只有在至少存在一个高选择性谓词的情况下才会应用该优化,否则查询延迟会相较于并行评估所有谓词而恶化。

哈希表。哈希表是用于聚合和哈希连接的基本数据结构。选择合适类型的哈希表对性能至关重要。ClickHouse 实例化 多种哈希表(截至 2024 年 3 月已超过 30 种),从通用哈希表模板中选择哈希函数、分配器、单元类型和调整大小策略作为可变点。根据分组列的数据类型、估计的哈希表基数和其他因素,为每个查询操作符单独选择速度最快的哈希表。为哈希表实施的其他优化包括:

  • 使用 256 个子表的两级布局(基于哈希的第一个字节)以支持庞大的键集,
  • 字符串哈希表 [79] 具有四个子表和针对不同字符串长度的不同哈希函数,
  • 查找表在只有少量键时直接使用键作为桶索引(即无哈希),
  • 带有嵌入哈希值的值,用于在比较代价高昂时更快地解决冲突(例如字符串、AST),
  • 基于运行时统计数据预测大小创建哈希表,以避免不必要的调整,
  • 在单个内存块上分配多个具有相同创建/销毁生命周期的小哈希表,
  • 使用每个哈希映射和每个单元版本计数器立即清除哈希表以供重用,
  • 利用 CPU 预取(__builtin_prefetch)加速在对键进行哈希后检索的值。

连接。由于 ClickHouse 最初仅支持 rudimentary 连接,许多用例在历史上依赖于非规范化表。如今,数据库 提供 SQL 中的所有连接类型(内连接、左/右/全外连接、交叉连接、随时间变化的连接),以及不同的连接算法,例如哈希连接(简单、优雅)、排序-合并连接和针对具有快速键值查找的表引擎的索引连接(通常是字典)。

由于连接是数据库中成本最高的操作之一,对于经典连接算法提供并行变体是重要的,理想的情况下需具备可配置的空间/时间权衡。对于哈希连接,ClickHouse 实现了非阻塞的共享分区算法 [7]。例如,图 9 中的查询计算用户在页面点击统计表中如何在 URL 之间移动。连接的构建阶段被拆分为三个通道,覆盖源表的三个不相交范围。使用分区哈希表,而不是全局哈希表。工作线程(通常是三个)通过计算哈希函数的模来确定构建侧每个输入行的目标分区。对哈希表分区的访问通过 Gather 交换操作符进行同步。探测阶段也以类似的方式查找其输入元组的目标分区。虽然该算法在每个元组中引入两个额外的哈希计算,但它大大减少了构建阶段的锁争用,具体取决于哈希表分区的数量。

图 9:具有三个哈希表分区的并行哈希连接。

4.5 工作负载隔离

ClickHouse 提供并发控制、内存使用限制和 I/O 调度,使用户能够将查询隔离到工作负载类别中。通过为特定工作负载类别设置共享资源(CPU 核心、DRAM、磁盘和网络 I/O)的限制,它确保这些查询不会影响其他关键业务查询。

并发控制防止在并发查询数量较高的情况下产生线程过度分配。更具体地说,每个查询的工作线程数会根据指定的比例动态调整与可用 CPU 核心的数量之间的关系。

ClickHouse 跟踪在服务器、用户和查询级别的内存分配字节大小,从而允许设置灵活的内存使用限制。内存超分配允许查询在保证内存之外使用额外的可用内存,同时保证其他查询的内存限制。此外,可以限制聚合、排序和连接子句的内存使用,一旦超过内存限制,就会回退到外部算法。

最后,I/O 调度允许用户基于最大带宽、进行中的请求和策略(例如,FIFO、SFC [32])限制工作负载类别的本地和远程磁盘访问。

5 集成层

实时决策应用程序通常依赖于对多个位置的数据进行高效的低延迟访问。有两种方法可以使外部数据在 OLAP 数据库中可用。使用基于推送的数据访问,第三方组件将数据库与外部数据存储连接起来。一个例子是专门的提取-转换-加载(ETL)工具,它将远程数据推送到目标系统。在基于拉取的模型中,数据库本身连接到远程数据源,将数据拉取到本地表中进行查询,或将数据导出到远程系统。虽然基于推送的方法更具灵活性和常见性,但它们涉及更大的架构占用和可扩展性瓶颈。相比之下,数据库直接的远程连接提供了有趣的功能,例如在本地数据和远程数据之间进行连接,同时保持整体架构简单并缩短洞察时间。

本节的其余部分探讨 ClickHouse 中的基于拉取的数据集成方法,旨在访问远程位置的数据。我们注意到在 SQL 数据库中远程连接的理念并不新鲜。例如,SQL/MED 标准 [35],在 2001 年引入并自 2011 年以来由 PostgreSQL 实现 [65],建议外部数据包装器作为管理外部数据的统一接口。与其他数据存储和存储格式的最大互操作性是 ClickHouse 的设计目标之一。截至 2024 年 3 月,ClickHouse 提供了我们所知的在所有分析数据库中最丰富的内置数据集成选项。

外部连接性。ClickHouse 提供 50+ 积分表函数和引擎,以连接外部系统和存储位置,包括 ODBC、MySQL、PostgreSQL、SQLite、Kafka、Hive、MongoDB、Redis、S3/GCP/Azure 对象存储和各种数据湖。我们将它们进一步分为以下附加图表中显示的类别(不是原始 vldb 论文的一部分)。

附加图:ClickBench 的互操作选项。

通过集成 表函数 实现临时访问。表函数可以在 SELECT 查询的 FROM 子句中调用,以读取远程数据进行探索性临时查询。或者,它们可以用于使用 INSERT INTO TABLE FUNCTION 语句将数据写入远程存储。

持久访问。有三种方法可以与远程数据存储和处理系统建立长期连接。

首先,集成 表引擎 将远程数据源(例如 MySQL 表)表示为持久的本地表。用户使用 CREATE TABLE AS 语法存储表定义,并与 SELECT 查询和表函数结合使用。可以指定自定义架构,例如仅引用远程列的子集,或使用架构推断自动确定列名和等效的 ClickHouse 类型。我们进一步区分被动和主动运行时行为:被动表引擎将查询转发到远程系统,并用结果填充本地代理表。相比之下,主动表引擎定期从远程系统拉取数据,或订阅远程更改,例如通过 PostgreSQL 的逻辑复制协议。因此,本地表包含远程表的完整副本。

第二,集成 数据库引擎 将远程数据存储中的所有表映射到 ClickHouse 中的一个表架构。与前者不同,它们通常要求远程数据存储为关系数据库,并提供对 DDL 语句的有限支持。

第三,字典 可使用针对几乎所有可能数据源的任意查询进行填充,方法是相应的集成表函数或引擎。运行时行为是主动的,因为数据是以恒定的间隔从远程存储中拉取的。

数据格式。为了与第三方系统进行交互,现代分析数据库还必须能够处理任何格式的数据。除了其本机格式外,ClickHouse 还支持 90+ 种格式,包括 CSV、JSON、Parquet、Avro、ORC、Arrow 和 Protobuf。每种格式可以是输入格式(ClickHouse 可以读取),输出格式(ClickHouse 可以导出),或两者兼而有之。一些面向分析的格式,如 Parquet,亦与查询处理集成,即优化器可以利用嵌入统计信息,过滤器直接在压缩数据上评估。

兼容性接口。除了其原生的二进制传输协议和 HTTP,客户端还可以通过与 MySQL 或 PostgreSQL 传输协议兼容的接口与 ClickHouse 交互。此兼容性特性对于启用来自专有应用程序(例如某些商业智能工具)的访问非常有用,因为供应商尚未实现原生 ClickHouse 连接。

6 性能作为一种特性

本节展示了用于性能分析的内置工具,并使用实际和基准查询评估性能。

6.1 内置性能分析工具

可用一系列工具来调查单个查询或后台操作中的性能瓶颈。用户通过基于系统表的统一接口与所有工具交互。

服务器和查询指标。服务器级别的统计信息,如活动片段计数、网络吞吐量和缓存命中率,辅以逐查询统计信息,例如读取的块数或索引使用统计信息。指标可以在请求时同步计算,或在可配置的间隔内异步计算。

采样分析器。可以使用采样分析器收集服务器线程的调用堆栈。结果可以选择性地导出到外部工具,如火焰图可视化工具。

OpenTelemetry 集成。OpenTelemetry 是一种在多个数据处理系统中跟踪数据行的开放标准 [8]。ClickHouse 可以为所有查询处理步骤生成具有可配置粒度的 OpenTelemetry 日志跨度,并收集和分析来自其他系统的 OpenTelemetry 日志跨度。

解释查询。与其他数据库一样,SELECT 查询可以通过 EXPLAIN 前置,以获取对查询 AST、逻辑和物理操作计划及执行时行为的详细见解。

6.2 基准测试

尽管基准测试因不够现实而受到批评 [10] 52, 66, 74],但它仍然有助于识别数据库的优缺点。接下来,我们讨论基准测试如何用于评估 ClickHouse 的性能。

6.2.1 非规范化表

对非规范化事实表的过滤和聚合查询历史上代表了 ClickHouse 的主要用例。我们报告了 ClickBench 的运行时间,这是一个典型的工作负载,模拟了在点击流和流量分析中使用的临时和定期报告查询。基准测试由 43 个查询组成,这些查询针对一个包含 1 亿个匿名页面点击的表,该数据源自网络上最大的分析平台之一。一个在线仪表板 [17] 显示截至 2024 年 6 月的 45 个以上商业和研究数据库的测量数据(冷/热运行时间、数据导入时间、磁盘大小)。结果由独立贡献者提交,基于公开可用的数据集和查询 [16]。这些查询测试顺序和索引扫描访问路径,并常规暴露 CPU、IO 或内存绑定的关系操作符。

图 10 显示了在常用于分析的数据库中顺序执行所有 ClickBench 查询的总体相对冷和热运行时间。测量是在单节点 AWS EC2 c6a.4xlarge 实例上进行的,该实例具有 16 个 vCPU、32 GB RAM 和 5000 IOPS / 1000 MiB/s 磁盘。用于 Redshift 的系统(ra3.4xlarge,12 个 vCPU、96 GB RAM)和 Snowfake 的系统(仓库大小 S: 2x8 vCPU,2x16 GB RAM)也采用了可比的架构。物理数据库设计仅进行了轻微调整,例如,我们指定了主键,但不更改单独列的压缩,不创建投影或跳过索引。在每个冷查询运行之前,我们还会清空 Linux 页面缓存,但不调整数据库或操作系统的设定。对于每个查询,选取所有数据库中最快的运行时间作为基线。其他数据库的相对查询运行时间计算为 ( + 10)/(_ + 10)。某个数据库的总相对运行时间是每个查询比率的几何平均值。虽然研究数据库 Umbra [54] 实现了最佳的整体热运行时间,但 ClickHouse 在热和冷运行时间上优于所有其他生产级数据库。

图 10: ClickBench 的相对冷和热运行时间。

为了随着时间的推移跟踪在更复杂工作负载下 SELECT 的性能,我们 使用 一种名为 VersionsBench 的四重基准组合 [19]。每当发布新版本时,此基准每月执行一次,以评估其性能 [20],并确定可能导致性能下降的代码变更:个别基准包括:1. ClickBench(如上所述),2. 15 个 MgBench [21] 查询,3. 针对包含 6 亿行的非规范化星型模式基准 [57] 事实表的 13 个查询。4. 针对 NYC Taxi Rides 的 4 个查询,该查询包含 34 亿行 [70]

图 11 显示了 2018 年 3 月到 2024 年 3 月间 77 个 ClickHouse 版本的 VersionsBench 运行时间的发展情况。为了补偿单个查询相对运行时间的差异,我们使用几何平均对运行时间进行标准化,采用相对于所有版本中最小查询运行时间的比率作为权重。在过去六年中,VersionBench 的性能提高了 1.72 ×。具有长期支持(LTS)的版本发布日期在 x 轴上标记。尽管有些时候性能暂时下降,但 LTS 版本通常具有与之前 LTS 版本相当或更好的性能。2022 年 8 月的显著改善是由第 4.4 节中描述的逐列过滤评估技术引起的。

图 11: 2018-2024 年 VersionsBench 的相对热运行时间。

6.2.2 规范化表

在经典仓库中,数据通常使用星型或雪花模式建模。我们展示了 TPC-H 查询(规模因子 100)的运行时间,但指出规范化表是 ClickHouse 的一个新兴用例。 图 12 显示了基于第 4.4 节中描述的并行哈希连接算法的 TPC-H 查询的热运行时间。测量是在单节点 AWS EC2 c6i.16xlarge 实例上进行的,该实例具有 64 个 vCPU、128 GB RAM 和 5000 IOPS / 1000 MiB/s 磁盘。记录了五次运行中最快的一次。作为参考,我们在一个可比大小的 Snowfake 系统(仓库大小 L,8x8 vCPU,8x16 GB RAM)中进行了相同的测量。表中排除了 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 查询的热运行时间(以秒为单位)。

分析数据库在近几十年间一直受到学术界和商业界的高度关注 [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 一样,表被分割成称为段的水平部分。虽然 ClickHouse 持续合并较小的部分,并可选择性地通过第 3.3 节中的技术减少数据量,但 Druid 和 Pinot 中的分片永远保持不可变。此外,Druid 和 Pinot 需要专用节点来创建、修改和搜索表,而 ClickHouse 则使用单一的二进制文件执行这些任务。

Snowfake [22] 是一个受欢迎的基于共享磁盘架构的专有云数据仓库。其将表划分为微分区的方法与 ClickHouse 中分片的概念相似。Snowfake 使用混合 PAX 页面 [3] 进行持久化,而 ClickHouse 的存储格式则严格为列式。Snowfake 还强调使用自动创建的轻量级索引 [31, 51] 进行本地缓存和数据裁剪以获得良好性能。与 ClickHouse 的主键类似,用户可以选择性地创建聚集索引,以将具有相同值的数据放在一起。

Photon [5] 和 Velox [62] 是查询执行引擎,旨在作为复杂数据管理系统中的组件使用。这两个系统接收查询计划作为输入,随后在本地节点上针对 Parquet(Photon)或 Arrow(Velox)文件执行 [46]。ClickHouse 能够消费和生成这些通用格式的数据,但更喜欢其本地文件格式进行存储。虽然 Velox 和 Photon 不优化查询计划(Velox 执行基本的表达式优化),但它们利用运行时自适应技术,例如根据数据特征动态切换计算内核。同样,ClickHouse 中的计划操作符可以在运行时创建其他操作符,主要目的是在查询内存消耗的基础上切换到外部聚合或连接操作符。Photon 论文指出,代码生成设计 [38, 41, 53] 比解释型向量化设计 [11] 更难开发和调试。在 Velox 的支持下,代码生成会构建并链接由运行时生成的 C++ 代码生成的共享库,而 ClickHouse 则直接与 LLVM 的按需编译 API 交互。

DuckDB [67] 也旨在被主机进程嵌入,但还提供查询优化和事务。它设计用于 OLAP 查询并混合偶尔的 OLTP 语句。DuckDB 相应地选择了 DataBlocks [43] 存储格式,该格式采用轻量级压缩方法,如保持顺序的字典或参考框架 [2] ,以在混合工作负载中实现良好的性能。相比之下,ClickHouse 针对仅追加的用例进行了优化,即没有或很少的更新和删除。块使用重型技术(如 LZ4)进行压缩,假设用户自由使用数据裁剪以加速频繁查询,并且 I/O 成本相较于剩余查询的解压缩成本更高。DuckDB 还基于 Hyper 的 MVCC 方案提供可序列化的事务 [55],而 ClickHouse 仅提供快照隔离。

8 结论与展望

我们介绍了 ClickHouse 的架构,这是一个开源的高性能 OLAP 数据库。以写优化的存储层和最先进的向量化查询引擎为基础,ClickHouse 可以对每个 PB 级数据集执行实时分析,同时具有高摄取率。通过在后台异步合并和转换数据,ClickHouse 有效地解耦了数据维护和并行插入。其存储层使用稀疏主索引、跳过索引和投影表支持激进的数据裁剪。我们描述了 ClickHouse 的更新和删除、幂等插入和节点间数据复制的实现,以支持高可用性。查询处理层利用丰富的技术来优化查询,并在所有服务器和集群资源之间并行执行。集成表引擎和函数提供了一种方便的方式与其他数据管理系统和数据格式无缝交互。通过基准测试,我们证明 ClickHouse 是市场上最快的分析数据库之一,并展示了 ClickHouse 在现实部署中典型查询性能的显著改善。

所有计划于 2024 年实施的功能和增强内容可以在公开路线图上找到 [18]。计划的改进包括对用户事务的支持、PromQL [69] 作为替代查询语言、用于半结构化数据的新数据类型(例如 JSON)、连接的更好计划级优化,以及轻量级更新的实现,以补充轻量级删除。

致谢

根据版本 24.6,SELECT * FROM system.contributors 返回 1994 名对 ClickHouse 做出贡献的个人。我们要感谢 ClickHouse Inc. 的整个工程团队和 ClickHouse 的优秀开源社区,感谢他们在一起构建这个数据库上的辛勤工作和奉献。

REFERENCES