做者:张博康git
在 TiDB 的架构中,全部的数据按照 range 划分红一个个 Region 分布在多个 TiKV 实例上。随着数据的写入,一个集群中会产生上百万,甚至千万个 Region。而量变引发质变,单 TiKV 实例上过多的 Region 无疑会带来比较大的负担,进而影响整个集群的性能表现。github
本文将介绍 TiKV 核心模块 Raftstore 的处理流程以使你们更好得理解海量 Region 致使性能问题的根源,以及针对这种状况的一些优化手段。网络
你们都知道在一个 TiKV 实例上会有多个 Region,这些 Region 消息处理就是经过 Raftstore 这个模块来驱动的,包括 Region 上读写请求的处理,Raft log 的持久化以及复制,还有 Raft 的心跳处理等等。为何 Region 数多了就会影响到整个集群的性能呢?为了解释这个问题,咱们先来看看 TiKV 的核心模块 Raftstore 是怎样工做的。多线程
<center>图 1 Raftstore 处理流程示意图</center>架构
注:该示意图仅仅表意,不表明代码层面的实际结构。
上图是 Raftstore 处理流程的示意图,能够看到,从 TiDB 发来的请求会经过 gRPC 和 storage 模块变成最终的 KV 读写消息发往相应的 Region,而这些消息并不会当即处理而是暂存下来。而在 Raftstore 中会轮询检查每一个 Region 是否有须要处理的消息。若是有消息,那么 Raftstore 会驱动 Raft 状态机去处理这些消息,并根据这些消息所产生的状态变动去完成一些后续动做。好比,在有写请求时,Raft 状态机须要将日志落盘而且将日志发送给其余副本;在达到心跳间隔时,Raft 状态机须要将心跳信息发送给其余副本。并发
从上面咱们能够看到,这么多 Region 的消息是一个接一个地处理。那么在 Region 不少的状况下,Raftstore 会须要花费一些时间处理大量 Region 的心跳,势必会引入一些延迟,致使一些读写请求得不到特别及时的处理。若是在读写压力大的状况下,很容易使得 Raftstore 线程 CPU 达到瓶颈,而延迟会被进一步放大,进而影响性能表现。app
通常来讲,在有负载的状况下,若是 TiKV 的 Raftstore CPU 使用率达到了 85%+(指的是单线程的状况,多线程等比例放大),咱们就认为 Raftstore 已经达到了比较繁忙的状态成为了瓶颈(因为 Raftstore 线程中有 IO 操做,因此 CPU 使用率不可能达到 100%),同时 propose wait duration 可能会达到百毫秒级别。性能
相关 metrics 可在 TiKV grafana 面板下查看:优化
Raft store CPU
参考值:最好低于 raftstore.store-pool-size * 85%
(v2.1 版本中无此配置项,可认为 raftstore.store-pool-size = 1
)。spa
<center>图 2 查看 Raft store CPU</center>
Propose wait duration
Propose wait duration
是发送请求给 Raftstore、到 Raftstore 真正处理请求之间的延迟。若是该延迟比较长,说明 Raftstore 比较繁忙或者 append log 比较耗时致使 Raftstore 不能及时处理请求。
参考值:最好低于 50-100ms。
<center>图 3 查看 Propose wait duration</center>
既然咱们已经知道了性能问题的根源,那么就能够从两方面入手:减小单个 TiKV 实例的 Region 数;减小单个 Region 的消息数。根据不一样版本,具体能够参考如下优化方法:
在 v2.1 版本中 Raftstore 只能是单线程,所以通常在 Region 数超过 10 万时就会逐渐成为瓶颈。
1. 增长 TiKV 实例
若是 IO 资源和 CPU 资源都还有比较多的盈余的话,能够在单个机器上部署多个 TiKV 实例,以减小单个 TiKV 实例上的 Region 个数,或者扩容集群的 TiKV 机器数。
2. 开启 Region Merge
另一种能够减小 Region 个数的办法是开启 Region Merge。与 Region Split 相反,Region Merge 是经过调度把相邻的小 Region 合并的过程。在集群有删除数据或者进行过 Drop Table/Truncate Table 后,能够将那些小 Region 甚至空 Region 进行合并以减小资源的消耗。
简单来讲,经过 pd-ctl 设置相关参数便可开启 Region Merge
>> pd-ctl config set max-merge-region-size 20 >> pd-ctl config set max-merge-region-keys 200000 >> pd-ctl config set merge-schedule-limit 8
关于更多详情请参考这两个文档 如何配置 Region Merge 和 PD 配置文件描述,在此再也不展开。
同时,默认配置的 Region Merge 默认参数设置相对保守,能够根据需求参考 《TiDB 最佳实践系列(二)PD 调度策略》 中说起的具体方法加快 Region Merge 速度。
3. 调整 raft-base-tick-interval
除了减少 Region 个数,咱们还能够经过尽可能减小 Region 单位时间内的消息数量以减少 Raftstore 压力。好比,在 TiKV 配置中适当增大 raft-base-tick-interval
:
[raftstore] raft-base-tick-interval = “2s”
raft-base-tick-interval
是 Raftstore 驱动每一个 Region 的 Raft 状态机的基本时间单位,也就是每隔这么久就须要向 Raft 状态机发送一个 tick 消息。显然增大这个时间间隔,能够有效减小 Raftstore 消息数量。
须要注意的是,这个 tick 也决定了 election timeout 和 heartbeat 的间隔。
raft-election-timeout = raft-base-tick-interval * raft-election-timeout-ticks raft-heartbeat-interval = raft-base-tick-interval * raft-heartbeat-ticks
follower 在 raft-election-timeout
间隔内未收到来自 leader 的心跳会认为 leader 出现故障而发起新的选举,而 raft-heartbeat-interval
是 leader 向 follower 发送心跳的间隔,所以增大 raft-base-tick-interval
能够减小单位时间内 Raft 发送的网络消息,但也会让 Raft 检测到 leader 故障的时间更长。
除了以上说起的优化方法外(注:Region Merge 在 v3.0 版本中默认开启),v3.0 版本中还能够进行如下优化:
1. 提升 Raftstore 并发数
在 v3.0 版本中 Raftstore 已经扩展为多线程,极大下降了 Raftstore 线程成为瓶颈的可能性。
默认 TiKV 配置 raftstore.store-pool-size
为 2
,若是在 Raftstore 出现瓶颈的时候能够根据实际状况适当提升,但不建议设置过大以防引入没必要要的线程切换开销。
2. 开启 Hibernate Region
在实际状况下,读写请求并不会均匀的打在每一个 Region 上,而是主要集中在少数的 Region 上,那么对于暂时空闲的 Region 咱们是否是能够尽可能减小它们的消息数量。这也就是 Hibernate Region 的主要思想,在无必要的时候不进行 raft-base-tick
,也就是不去驱动那些空闲 Region 的 Raft 状态机,那么就不会触发这些 Region 的 Raft 心跳信息的产生,极大得减少了 Raftstore 的工做负担。
截止发稿时 Hibernate Region 仍是一个实验 feature,在 master 上已经默认开启。若有须要,可酌情开启,相关配置说明请参考 配置 Hibernate Region。
PD 须要将 Region Meta 信息持久化在 etcd 以保证 PD Leader 节点切换后能快速继续提供 Region 路由服务。随着 Region 数量的增加,Etcd 的性能问题会使得 PD 在切换 Leader 时从 etcd 获取这些信息时比较慢,在百万 Region 量级时可能达到十几秒甚至几十秒。
所以在 v3.0 版本中 PD 将 Region Meta 信息存在本地的 LevelDB 中,经过另外的机制同步 PD 节点间的信息。
在 v3.0 版本中 PD 已经默认开启配置 use-region-storage
,而 v2.1 版本如碰到相似问题建议升级到 v3.0。
在 TiKV 中是由 pd-worker 这个模块来将 Region Meta 信息按期上报给 PD,在 TiKV 重启或者 Region Leader 切换时须要经过统计信息从新计算 Region 的 approximate size/keys
。那么在 Region 数量比较多的状况下,pd-worker 单线程可能成为瓶颈,形成任务的堆积不能及时处理,所以 PD 不能及时获取某些 Region Meta 信息以至路由信息更新不及时。该问题不会影响实际的读写,但可能致使 PD 调度的不许确以及 TiDB 更新 region cache 时须要多几回 round-trip。
能够在 TiKV grafana 面板中查看 Task 下的 Worker pending tasks 来肯定 pd-worker 是否有任务堆积,正常来讲 pending tasks 应该维持在一个比较低的值。
<center>图 4 查看 pd-worker</center>
咱们在 master 上已经对 pd-worker 进行了效率优化,预计会在 v2.1.19 和 v3.0.5 中带上相关优化,如碰到相似问题建议升级。
在大规模集群中,TiKV 实例数的增长会让 Prometheus 的查询时的计算压力较大致使 Grafana 查看 metrics 时较慢,在 v3.0 版本中经过设置了一些 metrics 的预计算有所缓解。
本文介绍了百万级 Region 的集群规模下的常见问题以及相应的处理方法。整体来说,在 v3.0 版本中咱们作了比较多的优化,海量 Region 致使的性能问题上已经有了明显的改善。但愿本文在问题根源的解释上能帮助读者更好的理解相关参数调整背后的逻辑,并能触类旁通地应用在相似问题的解决上。最后,“量变引发质变”,你们的参与才能让咱们的产品更进一步,期待大家的反馈和建议(zhangbokang@pingcap.com)。
原文阅读:https://pingcap.com/blog-cn/best-practice-massive-regions-performance-improvement/
更多最佳实践:https://pingcap.com/blog-cn/#%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5