博客 / 产品

ClickHouse Cloud 通过 SharedMergeTree 和轻量级更新提升性能

author avatar
Tom Schreiber
2023 年 8 月 17 日 - 31 分钟阅读

导言

ClickHouse 是用于实时应用和分析的最快速、资源效率最高的数据库。MergeTree 表引擎系列中的表是 ClickHouse 快速数据处理能力的核心组件。在这篇文章中,我们描述了这个系列新成员——SharedMergeTree 表引擎——背后的动机和机制。

这个表引擎是 ClickHouse Cloud 中 ReplicatedMergeTree 表引擎更高效的直接替代品,并且是为云原生数据处理而设计和优化的。我们将深入了解这个新的表引擎,解释它的优势,并通过基准测试展示其效率。我们还有一件事要告诉您。我们正在引入轻量级更新,它与 SharedMergeTree 具有协同效应。

MergeTree 表引擎是 ClickHouse 的核心

MergeTree 系列的表引擎是 ClickHouse 中的主要表引擎。它们负责存储通过 insert 查询接收的数据,在后台合并这些数据,应用引擎特定的数据转换等等。MergeTree 系列中的大多数表都支持自动数据复制,通过 ReplicatedMergeTree 基表引擎的复制机制实现。

在传统的无共享 ClickHouse 集群中,通过 ReplicatedMergeTree 进行复制用于数据可用性,而分片可用于集群扩展。ClickHouse Cloud 采用了一种新方法来构建基于 ClickHouse 的云原生数据库服务,我们将在下面进行描述。

ClickHouse Cloud 登场

ClickHouse Cloud 于 2022 年 10 月进入公测阶段,采用了针对云优化的完全不同的架构(并且我们解释了我们如何在一年内从头开始构建它)。通过将数据存储在几乎无限的共享对象存储中,存储和计算是分离的:所有水平垂直可扩展的 ClickHouse 服务器都可以访问相同的物理数据,并且实际上是单个无限分片的多个副本
smt_01.png

用于数据可用性的共享对象存储

由于 ClickHouse Cloud 将所有数据存储在共享对象存储中,因此无需在不同的服务器上显式创建数据的物理副本。Amazon AWS Simple Storage Service、Google GCP Cloud Storage 和 Microsoft Azure Blob Storage 等对象存储实现确保存储具有高可用性和容错能力。

请注意,ClickHouse Cloud 服务具有多层读取穿透写入穿透缓存(在本地 NVMe SSD 上),旨在在对象存储之上本地工作,以提供快速的分析查询结果,尽管底层主数据存储的访问延迟较慢。对象存储表现出较慢的访问延迟,但以大的聚合带宽提供高度并发的吞吐量。ClickHouse Cloud 通过利用多个 I/O 线程访问对象存储数据,并通过异步预取数据来利用这一点。

自动集群扩展

ClickHouse Cloud 没有使用分片来扩展集群大小,而是允许用户简单地增加在共享且几乎无限的对象存储之上运行的服务器的大小和数量。这提高了 INSERT 和 SELECT 查询的数据处理并行性。

请注意,ClickHouse Cloud 服务器实际上是单个无限分片的多个副本,但它们不像无共享集群中的副本服务器。这些服务器不是包含相同数据的本地副本,而是可以访问存储在共享对象存储中的相同数据。这使这些服务器变成了动态计算单元或计算节点,其大小和数量可以轻松地适应工作负载。可以手动或完全自动完成。下图说明了这一点: smt_02.png ① 通过向上扩展和 ② 向下扩展操作,我们可以更改节点的大小(CPU 核心和 RAM 的数量)。并且通过 ③ 横向扩展,我们可以增加参与并行数据处理的节点数量。无需任何物理重新分片或数据重新平衡,我们可以自由地添加或删除节点。

对于这种集群扩展方法,ClickHouse Cloud 需要一个表引擎,该引擎支持更多数量的服务器访问相同的共享数据。

在 ClickHouse Cloud 中运行 ReplicatedMergeTree 的挑战

ReplicatedMergeTree 表引擎对于 ClickHouse Cloud 的预期架构来说并不理想,因为它的复制机制旨在在少量副本服务器上创建数据的物理副本。而 ClickHouse Cloud 需要一个引擎,该引擎支持大量服务器在共享对象存储之上运行。

不需要显式数据复制

我们简要解释一下 ReplicatedMergeTree 表引擎的复制机制。该引擎使用 ClickHouse Keeper(也称为“Keeper”)作为协调系统,通过复制日志进行数据复制。Keeper 充当复制特定元数据和表模式的中心存储,以及分布式操作的共识系统。Keeper 确保按顺序分配连续的块编号以用于部件名称。使用 Keeper 提供的共识机制来分配合并mutation到特定的副本服务器。

下图草绘了一个包含 3 个副本服务器的无共享 ClickHouse 集群,并显示了 ReplicatedMergeTree 表引擎的数据复制机制: smt_03.png 当 ① server-1 接收到 insert 查询时,② server-1 会在其本地磁盘上创建一个包含查询数据的新数据部件。③ 通过复制日志,其他服务器(server-2、server-3)被告知 server-1 上存在新部件。在 ④,其他服务器独立地从 server-1 下载(“fetch”)部件到它们自己的本地文件系统。在创建或接收部件后,所有三个服务器还会在 Keeper 中更新它们自己的元数据,描述它们的部件集。

请注意,我们仅展示了如何复制新创建的部件。部件合并(和 mutation)以类似的方式复制。如果一个服务器决定合并一组部件,则其他服务器将自动在其本地部件副本上执行相同的合并操作(或者只是下载合并后的部件)。

如果本地存储完全丢失或添加了新副本,ReplicatedMergeTree 会从现有副本克隆数据。

ClickHouse Cloud 使用持久的共享对象存储来实现数据可用性,并且不需要 ReplicatedMergeTree 的显式数据复制。

集群扩展不需要分片

无共享 ClickHouse 集群的用户可以将复制与分片结合使用,以便使用更多服务器处理更大的数据集。表数据以分片(表数据部件的不同子集)的形式拆分到多个服务器上,并且每个分片通常有 2 或 3 个副本,以确保存储和数据可用性。可以通过添加更多分片来提高数据摄取和查询处理的并行性。请注意,ClickHouse 将具有更复杂拓扑的集群抽象为一个分布式表,以便您可以像本地查询一样执行分布式查询。

ClickHouse Cloud 不需要分片来进行集群扩展,因为所有数据都存储在几乎无限的共享对象存储中,并且可以通过添加可以访问共享数据的其他服务器来简单地提高并行数据处理的级别。但是,ReplicatedMergeTree 的复制机制最初是设计用于在无共享集群架构中的本地文件系统之上以及少量副本服务器上工作的。拥有大量 ReplicatedMergeTree 副本是一种反模式,服务器在复制日志上创建了过多的争用,并且在服务器间通信上产生了开销。

零拷贝复制无法解决这些挑战

ClickHouse Cloud 提供服务器的自动垂直扩展——服务器的 CPU 核心数和 RAM 会根据 CPU 和内存压力自动调整以适应工作负载。我们最初为每个 ClickHouse Cloud 服务配置了固定数量的 3 台服务器,最终引入了横向扩展到任意数量的服务器。

为了支持在具有 ReplicatedMergeTree 的共享存储之上进行这些高级扩展操作,ClickHouse Cloud 使用了一种名为零拷贝复制的特殊修改,用于调整 ReplicatedMergeTree 表的复制机制以在共享对象存储之上工作。

此适配几乎使用相同的原始复制模型,只是对象存储中仅存储一份数据。因此得名零拷贝复制。服务器之间不复制数据。相反,我们仅复制元数据: smt_04.png 当 ① server-1 接收到 insert 查询时,② 服务器以部件的形式将插入的数据写入对象存储,并且 ③ 将关于部件的元数据(例如,部件在对象存储中的存储位置)写入其本地磁盘。④ 通过复制日志,其他服务器被告知 server-1 上存在新部件,尽管实际数据存储在对象存储中。并且 ⑤ 其他服务器独立地从 server-1 下载(“fetch”)元数据到它们自己的本地文件系统。为了确保在所有副本删除指向同一对象的元数据之前数据不会被删除,使用了分布式引用计数机制:在创建或接收元数据后,所有三个服务器还会在 ClickHouse Keeper 中更新它们自己的元数据部件信息集。

为此,以及为了将合并和 mutation 等操作分配给特定的服务器,零拷贝复制机制依赖于在 Keeper 中创建独占。这意味着这些操作可能会相互阻塞,并且需要等待当前执行的操作完成。

零拷贝复制无法充分解决在共享对象存储之上使用 ReplicatedMergeTree 的挑战

  • 元数据仍然与服务器耦合:元数据存储与计算没有分离。零拷贝复制仍然需要在每台服务器上使用本地磁盘来存储关于部件的元数据。本地磁盘是额外的故障点,其可靠性取决于副本的数量,这与高可用性的计算开销有关。
  • 零拷贝复制的持久性取决于 3 个组件的保证:对象存储、Keeper 和本地存储。组件数量的增加增加了复杂性和开销,因为此堆栈是在现有组件之上构建的,而不是作为云原生解决方案重新设计的。
  • 这仍然是为少量服务器设计的:元数据更新仍然使用最初为具有少量副本服务器的无共享集群架构设计的相同复制模型。大量服务器会在复制日志上造成过多的争用,并在锁和服务器间通信上造成高开销。此外,在实现从一个副本到另一个副本的数据复制和克隆的代码中存在很多复杂性。并且不可能为所有副本进行原子提交,因为元数据是独立更改的。

用于云原生数据处理的 SharedMergeTree

我们决定(并且从一开始就计划)从头开始为 ClickHouse Cloud 实现一个新的表引擎,称为 SharedMergeTree——旨在在共享存储之上工作。对于我们来说,SharedMergeTree 是云原生的方式,可以 (1) 使 MergeTree 代码更直接且更易于维护,(2) 支持服务器的垂直和水平自动扩展,以及 (3) 为我们的云用户启用未来的功能和改进,例如更高的保证一致性、更好的持久性、时间点恢复、数据时间旅行等等。

在这里,我们简要描述一下 SharedMergeTree 如何原生支持 ClickHouse Cloud 的自动集群扩展 模型。作为提醒:ClickHouse Cloud 服务器是计算单元,可以访问相同的共享数据,其大小和数量可以自动更改。对于此机制,SharedMergeTree 将数据和元数据的存储与服务器完全分离,并使用 Keeper 的接口来从所有服务器读取、写入和修改共享元数据。每台服务器都有一个本地缓存,其中包含元数据的子集,并通过订阅机制自动获知数据更改。

下图草绘了如何将新服务器添加到具有 SharedMergeTree 的集群中: smt_05.png 当将 server-3 添加到集群时,这个新服务器 ① 订阅 Keeper 中的元数据更改,并将当前元数据的部分内容提取到其本地缓存中。这不需要任何锁定机制;新服务器基本上只是说,“我在这里。请让我及时了解所有数据更改”。新添加的 server-3 几乎可以立即参与数据处理,因为它只需从 Keeper 中提取必要的共享元数据集,即可了解存在哪些数据以及在对象存储中的位置。

下图显示了所有服务器如何了解新插入的数据: smt_06.png 当 ① server-1 接收到 insert 查询时,② 服务器以部件的形式将查询的数据写入对象存储。③ Server-1 还在其本地缓存和 Keeper 中存储关于部件的信息(例如,哪些文件属于该部件以及与文件对应的 blob 在对象存储中的位置)。之后,④ ClickHouse 向查询发送者确认插入。其他服务器(server-2、server-3)⑤ 通过 Keeper 的订阅机制自动收到关于对象存储中存在新数据的通知,并将元数据更新提取到它们的本地缓存中。

请注意,插入查询的数据在步骤 ④ 之后是持久的。即使 Server-1 崩溃,或者任何或所有其他服务器崩溃,该部件也存储在高度可用的对象存储中,并且元数据存储在 Keeper 中(Keeper 具有至少 3 个 Keeper 服务器的高度可用设置)。

从集群中删除服务器也是一个直接且快速的操作。对于正常删除,服务器只需从 Keeper 中取消注册自己,以便正确处理正在进行的分布式查询,而不会出现服务器丢失的警告消息。

ClickHouse Cloud 用户的优势

在 ClickHouse Cloud 中,SharedMergeTree 表引擎是 ReplicatedMergeTree 表引擎更高效的直接替代品。它为 ClickHouse Cloud 用户带来了以下强大的优势。

无缝集群扩展

ClickHouse Cloud 将所有数据存储在几乎无限、持久且高度可用的共享对象存储中。SharedMergeTree 表引擎为所有表组件添加了共享元数据存储。它实现了在存储之上运行的服务器的几乎无限的扩展。服务器实际上是无状态计算节点,我们可以几乎立即更改它们的大小和数量。

示例

假设 ClickHouse Cloud 用户当前正在使用三个节点,如下图所示: smt_07.png 可以直接(手动或自动)将计算量增加一倍,方法是使每个节点的大小翻倍,或者(例如,当达到每个节点的最大大小时)将节点数量从三个翻倍到六个: smt_08.png 这种翻倍了摄取吞吐量。对于 SELECT 查询,增加节点数量会提高并行数据处理的级别,包括并发查询的执行和单个查询的并发执行。请注意,增加(或减少)ClickHouse Cloud 中的节点数量不需要任何物理重新分片或实际数据的重新平衡。我们可以自由地添加或删除节点,其效果与无共享集群中的手动分片相同。

更改无共享集群中的服务器数量需要更多的精力和时间。如果集群当前由三个分片组成,每个分片有两个副本: smt_09.png 那么将分片数量翻倍需要对当前存储的数据进行重新分片和重新平衡: smt_10.png

插入查询的自动更强持久性

使用 ReplicatedMergeTree,您可以使用 insert_quorum 设置来确保数据持久性。您可以配置为仅当查询的数据(零拷贝复制情况下的元数据)存储在特定数量的副本上时,insert 查询才返回给发送者。对于 SharedMergeTree,不需要 insert_quorum。如上所示,当 insert 查询成功返回给发送者时,查询的数据将存储在高度可用的对象存储中,并且元数据集中存储在 Keeper 中(Keeper 具有至少 3 个 Keeper 服务器的高度可用设置)。

更轻量级的 SELECT 查询强一致性

如果您的用例需要一致性保证,即每台服务器都提供相同的查询结果,那么您可以运行 SYNC REPLICA 系统语句,对于 SharedMergeTree 来说,这是一个更轻量级的操作。每台服务器只需从 Keeper 获取当前版本的元数据,而无需在服务器之间同步数据(或零拷贝复制的元数据)。

改进的后台合并和 mutation 的吞吐量和可扩展性

使用 SharedMergeTree,服务器数量增加不会导致性能下降。后台合并的吞吐量随服务器数量扩展,只要 Keeper 有足够的资源即可。Mutation也是如此,mutation 是通过显式触发和(默认情况下)异步执行的合并来实现的。

这对 ClickHouse 中的其他新功能具有积极意义,例如轻量级更新,它从 SharedMergeTree 获得了性能提升。同样,引擎特定的数据转换AggregatingMergeTree的聚合,ReplacingMergeTree的去重等)受益于 SharedMergeTree 更好的合并吞吐量。这些转换在后台部件合并期间增量应用。为了确保查询结果的正确性,对于可能未合并的部件,用户需要在查询时通过使用 FINAL 修饰符或使用带有聚合的显式 GROUP BY 子句来合并未合并的数据。在这两种情况下,这些查询的执行速度都受益于更好的合并吞吐量。因为这样查询需要完成的查询时数据合并工作就更少了。

新的 ClickHouse Cloud 默认表引擎

SharedMergeTree 表引擎现在已普遍可用,作为 ClickHouse Cloud 中新开发层服务的默认表引擎。如果您想创建使用 SharedMergeTree 表引擎的新生产层服务,请联系我们。

ClickHouse Cloud 支持的所有 MergeTree 系列的表引擎都自动基于 SharedMergeTree。例如,当您创建一个 ReplacingMergeTree 表时,ClickHouse Cloud 会在后台自动创建一个 SharedReplacingMergeTree 表

CREATE TABLE T (id UInt64, v String)
ENGINE = ReplacingMergeTree
ORDER BY (id);

SELECT engine
FROM system.tables
WHERE name = 'T';

┌─engine───────────────────┐
│ SharedReplacingMergeTree │
└──────────────────────────┘

请注意,现有服务将随着时间的推移从 ReplicatedMergeTree 引擎迁移到 SharedMergeTree 引擎。如果您想讨论此事,请联系 ClickHouse 支持团队。

另请注意,当前 SharedMergeTree 的实现尚不支持 ReplicatedMergeTree 中存在的更高级的功能,例如异步插入的去重和静态加密,但计划在未来的版本中支持。

SharedMergeTree 实践

在本节中,我们将演示 SharedMergeTree 的无缝摄取性能扩展能力。我们将在另一篇博客中探讨 SELECT 查询的性能扩展。

摄取场景

对于我们的示例,我们加载了 2022 年前六个月的 WikiStat 数据集(托管在 S3 存储桶中)到一个 ClickHouse Cloud 中的中。为此,ClickHouse 需要从约 4300 个压缩文件中加载约 260 亿条记录(一个文件代表特定日期特定小时的数据)。我们结合使用 s3Cluster 表函数parallel_distributed_insert_select 设置来利用集群的所有计算节点。我们使用四种配置,每种配置具有不同数量的节点。每个节点有 30 个 CPU 核心和 120 GB RAM

  • 3 个节点
  • 10 个节点
  • 20 个节点
  • 80 个节点

请注意,前两个集群配置都使用专用的 3 节点 ClickHouse Keeper 服务,每个节点具有 3 个 CPU 核心和 2 GB RAM。对于 20 节点和 80 节点配置,我们将 Keeper 的大小增加到每个节点 6 个 CPU 核心和 6 GB RAM。我们在数据加载运行期间监控了 Keeper,以确保 Keeper 资源不是瓶颈。

结果

我们并行使用的节点越多,数据加载速度就越快(希望如此),但是,单位时间内创建的部件也越多。为了实现 SELECT 查询的最大性能,有必要最小化处理的部件数量。为此,每个 ClickHouse MergeTree 系列表引擎都在后台持续地合并数据部件到更大的部件中。表(每个表分区)的默认健康部件数量 3000(以前是 300)。

因此,我们正在测量每次数据加载运行的时间(从每次数据加载开始),以了解引擎将摄取期间创建的部件合并到少于 3000 个部件的健康数量所花费的时间。为此,我们使用 SQL 查询来查询 ClickHouse 系统表,以自省(和可视化)活动部件数量随时间的变化。

请注意,我们还可以选择包含使用零拷贝复制的 ReplicatedMergeTree 引擎的数据摄取运行的数字。如上所述,此引擎并非设计为支持大量副本服务器,我们想在此处强调这一点。

此图表显示了将所有部件合并到少于 3000 个部件的健康数量所花费的时间(以秒为单位): smt_11.png SharedMergeTree 支持无缝集群扩展。我们可以看到,在我们的测试运行中,后台合并的吞吐量与节点数量呈相当线性的扩展。当我们大约将节点数量从 3 个增加到 10 个时,吞吐量也增加了三倍。当我们再次将节点数量增加 2 倍到 20 个节点,然后增加 4 倍到 80 个节点时,吞吐量也分别大约翻倍和翻了四倍。正如预期的那样,随着副本节点数量的增加,使用零拷贝复制的 ReplicatedMergeTree 的扩展性不如 SharedMergeTree 好(甚至在较大的集群规模下会降低摄取性能)。因为它的复制机制从未设计为与大量副本一起工作。

为了完整性,此图表显示了合并到剩余少于 300 个部件的时间: smt_12.png

详细结果

3 个节点

下图可视化了活动部件的数量、成功加载数据所花费的秒数(请参阅 Ingest finished 标记)以及在具有 3 个副本节点的集群上进行基准测试运行期间,将部件合并到少于 3000 个和 300 个活动部件所花费的秒数: smt_13.png 我们看到,这两个表引擎的性能在这里非常相似。

我们可以看到,在数据加载期间,两个引擎执行的合并操作数量大致相同: smt_14.png

10 个节点

在我们的包含 10 个副本节点的集群上,我们可以看到一个差异: smt_15.png 导入时间的差异仅为 19 秒。但是,当导入完成时,两种表引擎的活动部件数量差异很大。对于使用零拷贝复制的 ReplicatedMergeTree,数量高出三倍以上。并且使用 ReplicatedMergeTree 将部件合并到少于 3000 和 300 个部件所需的时间是原来的两倍。这意味着使用 SharedMergeTree,我们能更快地获得更快的查询性能。导入完成时,大约 4 千个活动部件的数量仍然可以接受查询。而大约 1.5 万个则不可行。

两种引擎都创建了相同数量的约 2.3 万个初始部件,大小约为 10 MB,包含约 100 万 行,用于导入来自 WikiStat 数据子集的约 260 亿行

WITH
    'default' AS db_name,
    'wikistat' AS table_name,
    (
        SELECT uuid
        FROM system.tables
        WHERE (database = db_name) AND (name = table_name)
    ) AS table_id
SELECT
    formatReadableQuantity(countIf(event_type = 'NewPart')) AS parts,
    formatReadableQuantity(avgIf(rows, event_type = 'NewPart')) AS rows_avg,
    formatReadableSize(avgIf(size_in_bytes, event_type = 'NewPart')) AS size_in_bytes_avg,
    formatReadableQuantity(sumIf(rows, event_type = 'NewPart')) AS rows_total
FROM clusterAllReplicas(default, system.part_log)
WHERE table_uuid = table_id;

┌─parts──────────┬─rows_avg─────┬─size_in_bytes_avg─┬─rows_total────┐
│ 23.70 thousand │ 1.11 million │ 9.86 MiB          │ 26.23 billion │
└────────────────┴──────────────┴───────────────────┴───────────────┘

并且约 2.3 万个初始部件的创建均匀分布在 10 个副本节点上

WITH
    'default' AS db_name,
    'wikistat' AS table_name,
    (
        SELECT uuid
        FROM system.tables
        WHERE (database = db_name) AND (name = table_name)
    ) AS table_id
SELECT
    DENSE_RANK() OVER (ORDER BY hostName() ASC) AS node_id,
    formatReadableQuantity(countIf(event_type = 'NewPart')) AS parts,
    formatReadableQuantity(sumIf(rows, event_type = 'NewPart')) AS rows_total
FROM clusterAllReplicas(default, system.part_log)
WHERE table_uuid = table_id
GROUP BY hostName()
    WITH TOTALS
ORDER BY node_id ASC;

┌─node_id─┬─parts─────────┬─rows_total───┐
│       12.44 thousand │ 2.69 billion │
│       22.49 thousand │ 2.75 billion │
│       32.34 thousand │ 2.59 billion │
│       42.41 thousand │ 2.66 billion │
│       52.30 thousand │ 2.55 billion │
│       62.31 thousand │ 2.55 billion │
│       72.42 thousand │ 2.68 billion │
│       82.28 thousand │ 2.52 billion │
│       92.30 thousand │ 2.54 billion │
│      102.42 thousand │ 2.68 billion │
└─────────┴───────────────┴──────────────┘

Totals:
┌─node_id─┬─parts──────────┬─rows_total────┐
│       123.71 thousand │ 26.23 billion │
└─────────┴────────────────┴───────────────┘

但是 SharedMergeTree 引擎在数据加载运行期间更有效地合并部件: smt_16.png

20 个节点

当 20 个节点并行插入数据时,使用零拷贝复制的 ReplicatedMergeTree 难以应对每时间单位新创建的部件数量: smt_17.png 尽管 ReplicatedMergeTree 在 SharedMergeTree 之前完成了数据导入过程,但活动部件的数量继续增加到约 1 万个部件。因为引擎仍然在 队列 中有插入操作,这些操作仍然需要跨 20 个节点进行复制。请参阅 复制队列中的插入 行,其值是我们通过这个 查询 获得的。处理此队列花费了将近 45 分钟。每时间单位创建大量新部件的 20 个节点会导致复制日志上的争用过多,以及锁和服务器间通信的开销过高。缓解这种情况的一种方法是通过手动调整插入查询的某些设置来限制新创建部件的数量。例如,您可以减少每个节点的并行插入线程数,并增加写入每个新部件的行数。请注意,后者会增加主内存使用量。

请注意,Keeper 硬件在测试运行期间未过载。以下屏幕截图显示了两种表引擎的 Keeper 的 CPU 和内存使用情况: smt_19.png

80 个节点

在我们的 80 个节点的集群上,我们仅将数据加载到 SharedMergeTree 表中。我们已经在上面展示了,使用零拷贝复制的 ReplicatedMergeTree 不是为更高的副本节点数量设计的。

smt_18.png

260 亿行的插入在 67 秒内完成,即每秒 3.88 亿行。

介绍轻量级更新,由 SharedMergeTree 提升

SharedMergeTree 是一个强大的构建块,我们将其视为云原生服务的基础。它使我们能够构建新功能并改进现有功能,而这在以前是不可能或过于复杂的。许多功能受益于在 SharedMergeTree 之上工作,并使 ClickHouse Cloud 更高性能、更持久且更易于使用。“轻量级更新”就是这些功能之一——这是一种优化,允许即时提供 ALTER UPDATE 查询的结果,同时使用更少的资源。

传统分析数据库中的更新是繁重的操作

ALTER TABLE … UPDATE ClickHouse 中的查询被实现为 mutation。mutation 是一种重量级操作,它同步或异步地重写部件。

同步 mutation

smt_20.png 在我们上面的示例场景中,ClickHouse ① 接收到针对初始为空表的插入查询,② 将查询的数据写入存储上的新数据部件,以及 ③ 确认插入。接下来,ClickHouse ④ 接收到更新查询,并通过 ⑤ mutation Part-1 来执行该查询。部件被加载到主内存中,完成修改,并将修改后的数据写入存储上的新 Part-2(Part-1 被删除)。仅当部件重写完成后,⑥ 更新查询的确认才会返回给更新查询的发送者。其他更新查询(也可以删除数据)以相同的方式执行。对于较大的部件,这是一个非常繁重的操作。

异步 mutation

默认情况下,默认,更新查询是异步执行的,以便将接收到的多个更新融合到单个 mutation 中,从而减轻重写部件的性能影响: smt_21.png 当 ClickHouse ① 接收到更新查询时,更新会被添加到 队列 中并异步执行,并且 ② 更新查询立即获得更新的确认。

请注意,在更新 ⑤ 通过后台 mutation 物化之前,对表的 SELECT 查询看不到更新。

另请注意,ClickHouse 可以将排队的更新融合到单个部件重写操作中。因此,最佳实践是批量更新并使用单个查询发送数百个更新。

轻量级更新

不再需要上述显式批量更新查询,并且从用户的角度来看,即使异步物化,来自单个更新查询的修改也将立即发生。

此图草绘了 ClickHouse 中新的轻量级和即时更新 机制smt_22.png 当 ClickHouse ① 接收到更新查询时,更新会被添加到队列中并异步执行。 ② 此外,更新查询的更新表达式被放入主内存中。更新表达式也存储在 Keeper 中并分发到其他服务器。当 ③ ClickHouse 在更新通过部件重写物化之前接收到 SELECT 查询时,ClickHouse 将像往常一样执行 SELECT 查询 - 使用 主索引 来减少需要从部件流式传输到内存的行集,然后来自 ② 的更新表达式将应用于流式传输的行。这就是我们称此机制为 即时 mutation 的原因。当 ④ ClickHouse 接收到另一个更新查询时,⑤ 查询的更新(在本例中为删除)表达式再次保存在主内存中,并且 ⑥ 后续的 SELECT 查询将通过将两个(② 和 ⑤)更新表达式即时应用于流式传输到内存的行来执行。当 ⑦ 所有排队的更新都通过下一个后台 mutation 物化时,即时更新表达式将从内存中删除。 ⑧ 新接收的更新和 ⑩ SELECT 查询将如上所述执行。

可以通过简单地将 apply_mutations_on_fly 设置为 1 来启用此新机制。

优势

用户无需等待 mutation 物化。ClickHouse 立即交付更新后的结果,同时使用更少的资源。此外,这使得更新对于 ClickHouse 用户来说更易于使用,他们可以发送更新,而无需考虑如何批量处理它们。

与 SharedMergeTree 的协同作用

从用户的角度来看,来自轻量级更新的修改将立即发生,但是用户会体验到 SELECT 查询性能略有下降,直到更新被物化,因为更新是在查询时在内存中对流式传输的行执行的。由于更新在后台作为合并操作的一部分被物化,因此对查询延迟的影响会消失。SharedMergeTree 表引擎具有 改进的后台合并和 mutation 的吞吐量和可扩展性,因此,mutation 完成得更快,并且轻量级更新后的 SELECT 查询更快地恢复到全速。

下一步是什么

我们上面描述的轻量级更新机制只是第一步。我们已经在计划实施的其他阶段,以进一步提高轻量级更新的性能并消除当前的 限制

总结

在这篇博文中,我们探讨了新的 ClickHouse Cloud SharedMergeTree 表引擎的机制。我们解释了为什么有必要引入一个新的表引擎,该引擎原生支持 ClickHouse Cloud 架构,其中垂直和水平可扩展的计算节点与存储在几乎无限的共享对象存储中的数据分离。SharedMergeTree 实现了计算层在存储之上的无缝且几乎无限的扩展。插入和后台合并的吞吐量可以轻松扩展,这有利于 ClickHouse 中的其他功能,例如轻量级更新和引擎特定的数据转换。此外,SharedMergeTree 为插入提供更强的持久性,为 select 查询提供更轻量级的强一致性。最后,它为新的云原生功能和改进打开了大门。我们通过基准测试证明了引擎的效率,并描述了一个由 SharedMergeTree 推动的新功能,称为轻量级更新。

我们期待看到这个新的默认表引擎在实际应用中提升您的 ClickHouse Cloud 用例的性能。

立即开始使用 ClickHouse Cloud,并获得 300 美元的积分。在 30 天试用期结束时,继续使用按需付费计划,或联系我们以了解有关我们基于量的折扣的更多信息。访问我们的 定价页面 了解详细信息。

分享这篇文章

订阅我们的新闻通讯

随时了解功能发布、产品路线图、支持和云产品!
正在加载表单...
关注我们
X imageSlack imageGitHub image
Telegram imageMeetup imageRss image
©2025ClickHouse, Inc. 总部位于加利福尼亚州湾区和荷兰阿姆斯特丹。