TiDB 6.5 LTS 版本曾经公布了。这是 TiDB V6 的第二个长期反对版,携带了诸多备受期待的新个性: 产品易用性进一步晋升、内核一直打磨,更加成熟、多样化的灾备能力、增强利用开发者生态构建 ……
TiDB 6.5 新个性解析系列文章由 PingCAP 产研团队重磅打造,从原理剖析、技术实现、和产品体验几个层面展现了 6.5 版本的多项性能优化, 旨在帮忙读者更简略、更全面的体验 6.5 版本 。
本文为系列文章的第一篇,介绍了 TiFlash 在高并发场景下的稳定性和资源利用率的优化原理。
缘起
最近的某天,咱们测试 TiFlash 在高并发查问场景下的稳定性时,发现 TiFlash 终于能够长时间稳固将 CPU 齐全打满,这意味着咱们能充沛的利用 CPU 资源。回忆一年多前,咱们还在为高并发查问下的 OOM(out-of memory)、OOT(out-of thread)、CPU 使用率不低等各种问题而搜索枯肠。是时候来回顾一下咱们做了哪些事件,让质变引起量变。
<center>👆 CPU 使用率打满 </center>
咱们都晓得,对于剖析型的查问来说,有时候一个申请就能将机器的 CPU 资源打满。所以,大部分 OLAP 零碎在设计和实现的时候,很少思考零碎在高查问并发下的体现——晚期的 TiFlash 也没在这方面思考太多。
晚期的 TiFlash 的资源管理比拟高级——没有高效的线程管理机制、短少强壮的查问任务调度器、每个查问的内存应用没有任何限度、最新写入的数据存储和治理也有较大优化空间。这些优化措施的缺位,导致晚期的 TiFlash 在高并发查问场景下体现不佳,常常无奈将 CPU 使用率打满,稍有不慎还可能呈现 OOM 或 OOT。
过来一年里,针对 TiFlash 在高并发场景下的稳定性和资源利用率这个问题,咱们在存储和计算上都做了不少尝试和致力。现在回头看,有了下面的后果,也算是达到了一个小里程碑。
DynamicThreadPool
TiFlash 最开始的线程治理非常简单粗犷:申请到来时,按需创立新线程;申请完结之后,主动销毁线程。在这种模式下,咱们发现:对于一些逻辑比较复杂,然而数据量不大的查问,无论怎么减少查问的并发,TiFlash 的整机 CPU 使用率都远远不能打满。
<center>👆 CPU 使用率始终保持在 75% 以下 </center>
通过一系列的钻研之后,咱们终于定位到问题的根本原因:高并发下,TiFlash 会频繁地创立线程和开释线程。在 Linux 内核中,线程在创立和开释的时候,都会抢同一把全局互斥锁,从而在高并发线程创立和开释时, 这些线程会产生排队、阻塞的景象,进而导致利用的计算工作也被阻塞,而且并发越多,这个问题越重大,所以 CPU 使用率不会随着并发减少而减少。具体分析能够参考文章:深刻解析 TiFlash 丨多并发下线程创立、开释的阻塞问题。
解决这个问题的间接思路是应用线程池,缩小线程创立和开释的频率。然而,咱们目前的查问工作应用线程的模式是非抢占的,对于固定大小的线程池,因为零碎中没有全局的调度器,会有死锁的危险。为此,咱们引入了 DynamicThreadPool 这一个性。
在 DynamicThreadPool 中,线程分为两类:
- 固定线程:固定数量的线程,生命期与整个线程池雷同。
- 动静线程:运行过程中随着负载升高而创立,会自行在冷却后销毁。
每当有新工作须要执行时,DynamicThreadPool 会按以下程序查找可用线程:
- 闲暇的固定线程。
- 闲暇的动静线程。
- 当没有可用线程时,创立新的动静线程服务当前任务。
所有闲暇的动静线程会组成一个 LIFO 的链表,每个动静线程在解决完一个工作后都会将本身插入到链表头部,这样下次调度时会被优先应用,从而达到尽可能复用最近应用过的动静线程的目标。链表尾部的动静线程会在超过一个工夫阈值没有收到新工作之后判断本身已冷却,自行退出。
MinTSOScheduler
因为 DynamicThreadPool 没有限度线程的数量,在遇到高并发查问时,TiFlash 依然有可能会遇到无奈调配出线程(OOT)的问题。为了解决此问题,咱们必须管制 TiFlash 中同时应用的线程数量。
为了管制同时应用的计算线程数量,同时防止死锁,咱们为 TiFlash 引入了名为 MinTSOScheduler 的查问任务调度器——一个齐全分布式的调度器,它仅依赖 TiFlash 节点本身的信息。
<center>👆 MinTSOScheduler 的基本原理 </center>
MinTSOScheduler 的基本原理是:保障 TiFlash 节点上最小的 start_ts 对应的所有 MPPTask 能失常运行。因为全局最小的 start_ts 在各个节点上必然也是最小的 start_ts,所以 MinTSOScheduer 可能保障全局至多有一个查问能顺利运行从而保障整个零碎不会有死锁。而对于非最小 start_ts 的 MPPTask,则依据以后零碎的线程应用状况来决定是否能够运行,所以也能达到控制系统线程使用量的目标。
MemoryTracker
DynamicThreadPool 和 MinTSOScheduler 基本上解决了线程频繁创立和销毁、线程应用数量不受管制两大问题。对于一个运行高并发查问的环境,还有一个重要的问题要解决——缩小查问之间的互相烦扰。
实际中,咱们发现最重要的一点就是要防止其中某一个查问突然消耗掉大量内存,导致整个节点 OOM。为了防止某个大查问导致的 OOM,咱们显著加强了 MemoryTracker 跟踪和记录每个 MPPTask 应用的内存的精确度。当内存应用超过限度时,能够强行停止申请,防止 OOM 影响其它申请。
PageStorage
PageStorage 是 TiFlash 中的一个存储的形象层,相似对象存储。它次要是为了存储一些较小的数据块,如最新数据和 TiFlash 存储引擎的元数据。所以,PageStorage 次要面向新写入数据的高频读写设计。v6.1 及之前 TiFlash 应用的是 PageStorage 的 v2 版本(简称 PSv2)。
通过一系列的迭代和业务打磨,咱们发现 PSv2 存在一些问题亟需改良:
- 在一些写入负载,特地是 append-only 负载下,容易触发激进的 GC 策略对硬盘数据进行重写。重写数据时引起较大的写放大,以及内存的周期性疾速上涨,造成零碎不稳固。同时也会挤占前台写入和查问线程 CPU。
- 在 snapshot 开释时进行内存中的垃圾回收,其中波及较多内存小对象的拷贝。在高并发写入和查问的场景下,snapshot 开释的过程与读写工作挤占 CPU。
这些问题在大部分写入和查问并发较低的 OLAP 场景下,对系统的影响无限。然而,TiFlash 作为 TiDB 的 HTAP 架构中重要的一环,常常须要面对高并发的写入和查问。为此,咱们从新设计并实现了全新的 PageStorage(简称 PSv3)以应答更严苛的 HTAP 负载需要。
<center>👆 PSv3 架构图 </center>
上图是 PSv3 的整体架构。其中,橙色块代表接口,绿色块代表在硬盘上存储的组件,蓝色块代表在内存中的组件。
- WALStore 中保护数据(page)在 BlobFile 中地位,内存中的 PageDirectory 实现了 MVCC 反对。
- 数据保留在 BlobFile 中,如果其中的数据重复重写,会造成 CPU 以及 IO 资源的节约。咱们通过 SpaceMap 记录了 BlobFile 上的数据块应用状况(闲暇或占用)。删除数据时,只须要更新一下标记。写入数据时,能够间接从 SpaceMap 查找符合要求的闲暇块间接写入。大部分状况下,BlobFile 能够间接复用被删除的闲暇数据块,防止数据重写的产生,最大水平地缩小了垃圾回收的需要,从而显著缩小 CPU 和内存空间应用。
- 因为有 SpaceMap 的存在,写线程在 SpaceMap 中调配好数据块地位之后,多个写线程的 IO 操作能够并发执行。在复用空间时 BlobFile 文件大小不变,能够缩小了文件元数据的 sync 操作。TiFlash 整体的写提早升高,进而缩小期待数据一致性的 wait index 阻塞工夫,晋升查问线程的 CPU 利用率。
- 让读写线程 snapshot 创立和开释时的操作更高效,内存对象的整顿的工夫从开释 snapshot 时改为在后盾线程进行回收,缩小了对前台读写工作的影响,从而晋升了查问线程的 CPU 利用率。
总结
DynamicThreadPool | MinTSOScheduler | PageStorageV3 | CPU 最大使用率 |
---|---|---|---|
enable | enable | enable | 100% |
disable | disable | enbale | 75% |
enable | disable | enable | 90% |
enable | enable | disable | 75% |
disable | enable | enable | 85% |
下面这个表格总结了本文介绍的这几个晋升 TiFlash 稳定性和 CPU 使用率的要害个性的组合状况,能够看出:
- DynamicThreadPool 解决了频繁创立和销毁线程带来的开销;PageStorage v3 大大降低了 GC 和 snapshot 的开销,晋升了高并发写入和查问的稳定性。这两者对晋升 CPU 利用率有显著的成果。
- MinTSOScheduler 调度器限度了查问应用线程的数量,防止了呈现调配不出线程的状况,能够无效避免高并发申请导致的 OOM、OOT。
- 而 MemoryTracker(内存限度)通过被动 cancel 掉局部申请来避免整个过程 OOM,能够无效防止一个大查问导致整个节点不可用(OOM)的状况产生。
除此之外,过来一年,TiFlash 在性能和性能方面也做了不少优化,感兴趣的敌人能够关注咱们的 github 代码和官网文档。以上全副改变能够在 TiDB v6.5 LTS 版本中体验到,欢送尝试。