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

并行副本

Beta feature. Learn more.

引言

ClickHouse 处理查询的速度极快,但是这些查询是如何在多个服务器之间分配和并行化的?

在本指南中,我们首先将讨论 ClickHouse 如何通过分布式表在多个分片之间分配查询,然后讨论查询如何利用多个副本进行执行。

分片架构

在共享无状态架构中,集群通常被分为多个分片,每个分片包含整体数据的一个子集。一个分布式表位于这些分片之上,提供所有数据的统一视图。

读取操作可以发送到本地表。查询执行将仅发生在指定的分片上,或者可以发送到分布式表,在这种情况下,每个分片将执行给定的查询。查询分布式表的服务器将汇总数据并响应客户端:

sharded archtiecture

上面的图展示了当客户端查询分布式表时会发生什么:

  1. 选择查询通过负载均衡器发送到一个节点的分布式表。这个节点现在将充当协调者。

  2. 节点将根据分布式表提供的信息定位需要执行查询的每个分片,并将查询发送到每个分片。

  3. 每个分片在本地读取、过滤和聚合数据,然后将可合并的状态发送回协调者。

  4. 协调节点合并数据,然后将响应返回给客户端。

当我们将副本添加到混合中时,过程相似,不同之处在于每个分片只有一个副本将执行查询。这意味着可以并行处理更多的查询。

非分片架构

ClickHouse Cloud 的架构与上述所示的架构非常不同。(有关更多详细信息,请参见 "ClickHouse Cloud 架构")。由于计算和存储的分离,以及几乎无限的存储需求,分片的必要性变得不那么重要。

下图展示了 ClickHouse Cloud 架构:

non sharded architecture

这种架构使我们能够几乎瞬间添加和删除副本,确保极高的集群可扩展性。ClickHouse Keeper 集群(见右侧)确保我们对元数据具有单一来源的真相。副本可以从 ClickHouse Keeper 集群中获取元数据,并保持相同的数据。数据本身存储在对象存储中,SSD 缓存使我们能够加速查询。

但是我们现在如何在多个服务器之间分发查询执行呢?在分片架构中,由于每个分片实际上可以在数据子集上执行查询,这点显而易见。那么在没有分片的情况下,如何工作呢?

引入并行副本

要通过多个服务器并行化查询执行,我们首先需要将我们的一个服务器指定为协调者。协调者是创建需要执行的任务列表的人,确保所有任务都被执行、聚合并将结果返回给客户端。在大多数分布式系统中,这将是接收初始查询的节点的作用。我们还需要定义工作单元。在分片架构中,工作单元是分片,是数据的一个子集。对于并行副本,我们将使用表的一个小部分,称为 granules,作为工作单元。

现在,让我们看看下面的图中它在实际中的工作原理:

Parallel replicas

使用并行副本时:

  1. 来自客户端的查询通过负载均衡器发送到一个节点。这个节点成为该查询的协调者。

  2. 节点分析每个部分的索引,并选择正确的部分和 granules 进行处理。

  3. 协调者将工作负载分割成可以分配给不同副本的一组 granules。

  4. 每组 granules 由相应的副本处理,处理完成时将可合并的状态发送回协调者。

  5. 最后,协调者合并来自副本的所有结果,然后将响应返回给客户端。

上面的步骤大致概述了并行副本在理论上的工作原理。 然而,在实践中,有许多因素可能会阻止这种逻辑完美地工作:

  1. 一些副本可能不可用。

  2. ClickHouse 中的复制是异步的,某些副本在某些时间点可能没有相同的部分。

  3. 需要以某种方式处理副本之间的尾延迟。

  4. 每个副本的文件系统缓存因活动而异,这意味着随机的任务分配可能导致由于缓存局部性而性能不佳。

我们在接下来的部分中探讨如何克服这些因素。

公告

为了解决上面列表中的 (1) 和 (2) 问题,我们引入了公告的概念。让我们通过下面的图来可视化它是如何工作的:

Announcements
  1. 来自客户端的查询通过负载均衡器发送到一个节点。该节点成为该查询的协调者。

  2. 协调节点向集群中的所有副本发送请求以获取公告。副本可能对表的当前部分集合有略微不同的视图。因此,我们需要收集这些信息以避免错误的调度决策。

  3. 协调节点然后利用公告定义可以分配给不同副本的一组 granules。此处例如,我们可以看到由于副本 2 没有在其公告中提供该部分,因此没有分配部分 3 的任何 granules 给副本 2。还注意到,副本 3 没有被分配任何任务,因为该副本没有提供公告。

  4. 每个副本在其 granules 子集上处理查询并将可合并的状态返回给协调者后,协调者合并结果并将响应发送给客户端。

动态协调

为了解决尾延迟的问题,我们添加了动态协调。这意味着,并不是所有的 granules 都在一个请求中发送给副本,而是每个副本能够向协调者请求一个新任务(即一组要处理的 granules)。协调者将根据收到的公告向副本提供 granules 的集合。

假设我们在所有副本发送了包含所有部分的公告的阶段。

下图可视化了动态协调是如何工作的:

Dynamic Coordination - part 1
  1. 副本让协调节点知道它们能够处理任务,它们还可以指定能够处理的工作量。

  2. 协调者将任务分配给副本。

Dynamic Coordination - part 2
  1. 副本 1 和 2 能够非常快速地完成其任务。它们将请求协调者节点的另一个任务。

  2. 协调者将新任务分配给副本 1 和 2。

Dynamic Coordination - part 3
  1. 所有副本现在完成了任务的处理。它们请求更多的任务。

  2. 协调者使用公告检查剩余的任务,但没有剩余的任务。

  3. 协调者告知副本一切已处理完毕。它现在将合并所有可合并的状态并对查询进行响应。

管理缓存局部性

最后一个潜在的问题是我们如何处理缓存局部性。如果查询多次执行,如何确保相同的任务被路由到相同的副本?在之前的例子中,我们分配了以下任务:

副本 1副本 2副本 3
部分 1g1, g6, g7g2, g4, g5g3
部分 2g1g2, g4, g5g3
部分 3g1, g6g2, g4, g5g3

为了确保相同的任务被分配给相同的副本并能利用缓存,会进行以下两个操作。计算部分 + granules 集合(任务)的哈希。然后应用任务分配的副本数量的模数。

从理论上讲,这听起来不错,但实际上,如果某个副本突然负载增加或网络质量下降,如果始终使用同一副本执行某些任务可能会引入尾延迟。如果 max_parallel_replicas 小于副本数量,则在查询执行时会随机选择副本。

任务窃取

如果某个副本的任务处理速度慢于其他副本,其他副本将尝试通过哈希"窃取"那些原则上属于该副本的任务,以减少尾延迟。

限制

此功能有已知限制,其中主要限制在本节中记录。

备注

如果您发现了一个问题,而不是下面给出的限制,并怀疑并行副本是原因,请使用标签 comp-parallel-replicas 在 GitHub 上打开一个问题。

限制描述
复杂查询目前并行副本在简单查询时表现良好。复杂层如 CTE、子查询、JOIN、非扁平查询等可能对查询性能产生负面影响。
小查询如果您执行的查询不处理很多行,可能在多个副本上执行时不会带来更好的性能时间,因为副本之间协调的网络时间可能会导致查询执行中的额外周期。您可以通过以下设置来限制这些问题: parallel_replicas_min_number_of_rows_per_replica
使用 FINAL 时禁用并行副本
高基数数据和复杂聚合需要发送大量数据的高基数聚合可能会显著降低您的查询速度。
与新分析器的兼容性新的分析器可能在特定情况下显著减慢或加快查询执行。
设置描述
enable_parallel_replicas0: 禁用
1: 启用
2: 强制使用并行副本,如果未使用则抛出异常。
cluster_for_parallel_replicas用于并行复制的集群名称;如果您使用 ClickHouse Cloud,使用 default
max_parallel_replicas用于在多个副本上执行查询的最大副本数量,如果指定的数量小于集群中的副本数量,节点将被随机选择。此值也可以被超承诺以适应横向扩展。
parallel_replicas_min_number_of_rows_per_replica帮助根据需要处理的行数限制所使用的副本数量,所使用的副本数量由以下定义:
estimated rows to read / min_number_of_rows_per_replica
allow_experimental_analyzer0: 使用旧分析器
1: 使用新分析器。

并行副本的行为可能会根据所使用的分析器而变化。

调查并行副本的问题

您可以检查每个查询中使用的设置,方法是查看 system.query_log 表。您还可以查看 system.events 表,以查看服务器上发生的所有事件,并可以使用 clusterAllReplicas 表函数查看所有副本上的表 (如果您是云用户,请使用 default)。

响应

system.text_log 表也包含有关使用并行副本执行查询的信息:

响应

最后,您还可以使用 EXPLAIN PIPELINE。它突出显示 ClickHouse 将如何执行查询以及将用于执行查询的资源。让我们以以下查询为例:

让我们查看没有并行副本的查询管道:

没有 parallel_replica 的 EXPLAIN

现在查看带有并行副本的管道:

带有 parallel_replica 的 EXPLAIN