The Way to TiDB 3.0 and Beyond (下篇)

本文为我司 Engineering VP 申砾在 TiDB DevCon 2019 上的演讲实录。在 上篇 中,申砾老师重点回顾了 TiDB 2.1 的特性,并分享了咱们对「如何作好一个数据库」的见解。 本篇将继续介绍 TiDB 3.0 Beta 在稳定性、易用性、功能性上的提高,以及接下来在 Storage Layer 和 SQL Layer 的规划,enjoy~算法

TiDB 3.0 Beta

2018 年年末咱们开了一次用户吐槽大会,当时咱们请了三个 TiDB 的重度用户,都是在生产环境有 10 套以上 TiDB 集群的用户。那次大会规则是你们不能讲 TiDB 的优势,只能讲缺点;研发同窗要直面问题,不能辩解,直接提解决方案;固然咱们也保护用户的安全(开个玩笑 :D),让他们放心的来吐槽。刚刚的社区实践分享也有点像吐槽大会第二季,咱们也但愿用户来提问题,分享他们在使用过程遇到什么坑,由于只有直面这些问题,才有可能改进。因此咱们在 TiDB 3.0 Beta 中有了不少改进,固然还有一些会在后续版本中去改进。数据库

1. Stability at Scale

TiDB 3.0 版本第一个目标就是「更稳定」,特别是在大规模集群、高负载的状况下保持稳定。稳定性压倒一切,若是你不稳定,用户担惊受怕,业务时断时续,后面的功能都是没有用的。因此咱们但愿「先把事情作对,再作快」。安全

1.1 Multi-thread RaftStore

首先来看 TiDB 3.0 一个比较亮眼的功能——多线程 Raft。我来给你们详细解释一下,为何要作这个事情,为何咱们之前不作这个事情。性能优化

<center>图 8 TiKV 抽象架构图</center>多线程

这是 TiKV 一个抽象的架构(图 8)。中间标红的图形是 RaftStore 模块,全部的 Raft Group 都在一个 TiKV 实例上,全部 Raft 状态机的驱动都是由一个叫作 RaftStore 的线程来作的,这个线程会驱动 Raft 状态机,而且将 Raft Log Append 到磁盘上,剩下的包括发消息给其余 TiKV 节点以及 Apply Raft Log 到状态机里面,都是由其余线程来作的。早期的时候,可能用户的数据量没那么大,或者吞吐表现不大的时候,实际上是感知不到的。可是当吞吐量或者数据量大到必定程度,就会感受到这里实际上是一个瓶颈。虽然这个线程作的事情已经足够简单,可是由于 TiKV 上全部的 Raft Peer 都会经过一个线程来驱动本身的 Raft 状态机,因此当压力足够大的时候就会成为瓶颈。用户会看到整个 TiKV 的 CPU 并无用满,可是为何吞吐打不上去了?架构

<center>图 9 TiDB 3.0 Multi-thread RaftStore</center>并发

所以在 TiDB 3.0 中作了一个比较大的改进,就是将 RaftStore 这个线程,由一个线程变成一个线程池, TiKV 上全部 Raft Peer 的 Raft 状态机驱动都由线程池来作,这样就可以充分利用 CPU,充分利用多核,在 Region 特别多以及写入量特别大的时候,依然能线性的提高吞吐。异步

<center>图 10 TiDB 3.0 Beta oltp_insert</center>高并发

经过上图你们能够看到,随着并发不断加大,写入是可以去线性扩展的。在早期版本中,并发到必定程度的时候,RaftStore 也会成为瓶颈,那么为何咱们以前没有作这个事情?这个优化效果这么明显,之因此以前没有作,是由于以前 Raft 这块不少时候不会成为瓶颈,而在其余地方会成为瓶颈,好比说 RocksDB 的写入或者 gRPC 可能会成为瓶颈,而后咱们将 RaftStore 中的功能不断的向外拆,拆到其余线程中,或者是其余线程里面作多线程,作异步等等,随着咱们的优化不断深刻,用户场景下的数据量、吞吐量不断加大,咱们发现 RaftStore 线程已经成为须要优化的一个点,因此咱们在 3.0 中作了这个事情。并且以前保持单线程也是由于单线程简单,「先把事情作对,而后再作快」。工具

1.2 Batch Message

第二个改进是 Batch Message。咱们的组件之间通信选择了 gRPC,首先是由于 gRPC 是 Google 出品,有人在维护他,第二是用起来很简单,也有不少功能(如流控、加密)能够用。但其实不少人吐嘈它性能比较慢,在知乎上你们也能看到各类问题,包括讨论怎么去优化他,不少人也有各类优化经验,咱们也一直想怎么去优化他。之前咱们用的方法是来一个 message 就经过 gRPC 发出去,虽然性能可能没有那么好,或者说性能不是他最大的亮点,但有时候调性能不能单从一个模块去考虑,应该从架构上去想,就是架构须要为性能而设计,架构上的改进每每能带来性能的质变。

因此咱们在 TiDB 3.0 Beta 中设计了 Batch Message 。之前是一个一个消息的发,如今是按照消息的目标分队列,每一个队列会有一个 Timer,当消息凑到必定个数,或者是你的 Timer 到了时间(如今应该设置的是 1ms,Batch 和这个 Timer 数量均可以调),才会将发给同一个目的地的一组消息,打成一个包,一块儿发过去。有了这个架构上的调整以后,咱们就得到了性能上的提高。

<center>图 11 TiDB 3.0 Beta - Batch Message</center>

固然你们会想,会不会在并发比较低的时候变慢了?由于你凑不到足够的消息,那你就要等 Timer。实际上是不会的,咱们也作了一些设计,就是由对端先汇报「我当前是否忙」,若是对端不忙,那么选择一条一条的发,若是对端忙,那就能够一个 Batch 一个 Batch 的发,这是一个自适应的 Batch Message 的一套系统。图 11 右半部分是一个性能对比图,有了 Batch Message 以后,在高并发状况下吞吐提高很是快,在低并发状况下性能并无降低。相信这个改进能够给你们带来很大的好处。

1.3 Titan

第三点改进就是 Titan。CEO 刘奇在 Opening Keynote 中提到了咱们新一代存储引擎 Titan,咱们计划用 Titan 替换掉 RocksDB,TiDB 3.0 中已经内置了 Titan,但没有默认打开,若是你们想体验的话,能够经过配置文件去把 RocksDB 改为 Titan。咱们为何想改进 RocksDB 呢?是由于它在存储大的 Key Value 的时候,有存储空间放大和写放大严重的问题。

<center>图 12 TiDB 3.0 中内置的新存储引擎 Titan</center>

因此咱们尝试解决这个问题。当你写入的 Key Value 比较大的时候,咱们会作一个检查,而后把大的 Value 放到一个 Blob File 里去,而不是放到 LSM-Tree。这样的分开存储会让 LSM-Tree 变得很小,避免了由于 LSM-Tree 比较高的时候,特别是数据量比较大时出现的比较严重的写放大问题。有了 Titan 以后,就能够解决「单个 TiKV 服务大量数据」的需求,由于以前建议 TiKV 一个实例不要高于 1T。咱们后面计划单个 TiKV 实例可以支持 2T 甚至 4T 数据,让你们可以节省存储成本,而且能在 Key Value 比较大的时候,依然能得到比较好的性能。

除了解决写放大问题以外,其实还有一个好处就是咱们能够加一个新的 API,好比 KeyExist,用来检查 Key 是否存在,由于这时 Key 和 Value 是分开存储的,咱们只须要检查 Key 是否在,不须要把 Value Load 进去。或者作 Unique Key 检查时,能够不须要把 Key Value 取出来,只须要加个接口,看这个 Key 是否存在就行了,这样可以很好的提高性能。

1.4 Robust Access Path Selection

第四点是保持查询计划稳定。这个在数据库领域实际上是一个很是难的问题,咱们依然没有 100% 解决这个问题,但愿在 2019 年第一季度,最多到第二季度,能有一个很是好的解决方案。咱们不但愿当数据量变化 、写入变化、负载变化,查询计划忽然变错,这个问题在线上使用过程当中是灾难。那么为何会跑着跑着变错?首先来讲咱们如今是一个 Cost-based optimizers,咱们会参考统计信息和当前的数据的分布,来选择后面的 plan。那么数据的分布是如何得到的呢?咱们是经过统计信息,好比直方图、CM Sketch来获取,这里就会出现两个问题:

1. 统计信息多是不许的。统计信息毕竟是一个采样,不是全量数据,会有一些数据压缩,也会有精度上的损失。

2. 随着数据不断写入,统计信息可能会落后。由于咱们很难 100% 保证统计信息和数据是 Match 的。

<center>图 13 查询计划稳定性解决方案</center>

一个很是通用的思路是, 除了依赖于 Cost Model 以外,咱们还要依赖更多的 Hint,依赖于更多启发式规则去作 Access Path 裁减。举个例子:

select * from t where a = x and b = y;
idx1(a, b)
idx2(b) -- pruned

你们经过直观印象来看,咱们必定会选择第一个索引,而不是第二个索引,那么咱们就能够把第二个索引裁掉,而不是由于统计信息落后了,而后估算出第二个索引的代价比较低,而后选择第二个索引。上面就是咱们最近在作的一个事情,这里只举了一个简单的例子。

2. Usability

TiDB 3.0 第二个目标是可用性,是让 TiDB 简单易用。

2.1 Query Tracing

在 TiDB 2.0 中,你们看一个 Query 为何慢了依赖的是 Explain,就是看查询计划,其实那个时候你们不少都看不懂,有时候看了也不知道哪有问题。后来咱们在 TiDB 2.1 中支持了 Explain Analyze,这是从 PG  借鉴过来一个特性,就是咱们真正的把它执行一边,而后再看看每一个算子的耗时、处理的数据量,看看它到底干了一些什么事情,但其实可能还不够细,由于尚未细化到算子内部的各类操做的耗时。

<center>图 14 TiDB 3.0 - Query Tracing</center>

因此咱们又作了一个叫 Query Tracing 的东西,其实在 TiDB 2.1 以前咱们已经作了一部分,在 TiDB 3.0 Beta 中作了一个收尾,就是咱们能够将 Explain 结果转成一种 Tracing 格式,再经过图形化界面,把这个 Tracing 的内容展现出来,就能够看到这个算子具体干了一些什么事,每一步的消耗到底在哪里,这样就能够知道哪里有问题了。但愿你们都能在 TiDB 3.0 的版本中很是直观的定位到 Query 慢的缘由。

2.2 Plan Management

而后第二点 Plan Management 其实也是为了 Plan 不稳定这个问题作准备的。虽然咱们但愿数据库能本身 100% 把 Plan 选对,可是这个是很是美好的愿望,应该尚未任何一个数据库能保证本身能 100% 的解决这个问题。那么在之前的版本中,出现问题怎么办?一种是去 Analyze 一下,不少状况下他会变好,或者说你打开自动 Analyze 这个特性,或者自动 FeedBack 这个特性,能够必定程度上变好,可是还可能过一阵统计信息又落后了,又不许了,Plan 又错了,或者因为如今 cost 模型的问题,有一些 Corner Case 处理不到,致使即便统计信息是准确的, Plan 也选不对。

<center>图 15 TiDB 3.0 Beta - Plan Management</center>

那么咱们就须要一个兜底方案,让你们遇到这个问题时不要一筹莫展。一种方法是让业务去改 SQL,去加 Hint,也是能够解决的,可是跟业务去沟通可能会增长他们的使用成本或者反馈周期很长,也有可能业务自己也不肯意作这个事情。

另一种是用一种在线的方式,让数据库的使用者 DBA 也能很是简单给这个 Plan 加 Hint。具体怎么作呢?咱们和美团的同窗一块儿作了一个很是好的特性叫 Plan Management,就是咱们有一个 Plan 管理的模块,咱们能够经过 SQL 接口给某一条 Query,某一个 Query 绑定 Plan,绑定 Hint,这时咱们会对 SQL 作指纹(把 Where 条件中的一些常量变成一个通配符,而后计算出一个 SQL 的指纹),而后把这个 Hint 绑定在指纹上。一条 Query 来了以后,先解成 AST,咱们再生成指纹,拿到指纹以后,Plan Hint Manager 会解析出绑定的 Plan 和 Hint,有 Plan 和 Hint 以后,咱们会把 AST 中的一部分节点替换掉,接下来这个 AST 就是一个「带 Hint 的 AST」,而后扔给 Optimizer,Optimizer 就能根据 Hint 介入查询优化器以及执行计划。若是出现慢的 Query,那么能够直接经过前面的 Query Tracing 去定位,再经过 Plan Management 机制在线的给数据库手动加 Hint,来解决慢 Query 的问题。这样下来也就不须要业务人员去改 SQL。这个特性应该在 TiDB 3.0 GA 正式对外提供,如今在内部已经跑得很是好了。在这里也很是感谢美团数据库开发同窗的贡献。

2.3 Join Reorder

TiDB 3.0 中咱们增长了 Join Reorder。之前咱们有一个很是简单的 Reorder 算法,就是根据 Join 这个路径上的等值条件作了一个优先选择,如今 TiDB 3.0 Beta 已经提供了第一种 Join Reorder 算法,就是一个贪心的算法。简单来讲,就是我有几个须要 Join 的表,那我先从中选择 Join 以后数据量最小的那个表(是真正根据 Join 以后的代价来选的),而后我在剩下的表中再选一个,和这个再组成一个 Join Path,这样咱们就能必定程度上解决不少 Join 的问题。好比 TPC-H 上的 Q5 之前是须要手动加 Hint 才能跑出来,由于它没有选对 Join 的路径,但在 TiDB 3.0 Beta 中,已经可以自动的选择最好的 Join Path 解决这个问题了。

<center>图 16 TiDB 3.0 Beta - Join Reorder</center>

咱们接下来还要再作一个基于动态规划的 Join Reorder 算法,颇有可能会在 3.0 GA 中对外提供。 在 Join 表比较少的时候,咱们用动态规划算法能保证找到最好的一个 Join 的路径,可是若是表很是多,好比大于十几个表,那可能会选择贪心的算法,由于 Join Reorder 仍是比较耗时的。

3. Functionality

说完稳定性和易用性以外,咱们再看一下功能。

<center>图 17 TiDB 3.0 Beta 新增功能</center>

咱们如今作了一个插件系统,由于咱们发现数据库能作的功能太多了,只有咱们来作其实不太可能,并且每一个用户有不同的需求,好比说这家想要一个可以结合他们的监控系统的一个模块,那家想要一个可以结合他们的认证系统作一个模块,因此咱们但愿有一个扩展的机制,让你们都有机会可以在一个通用的数据库内核上去定制本身想要的特性。这个插件是基于 Golang 的 Plugin 系统。若是你们有 TiDB Server 的 Binary 和本身插件的 .so,就能在启动 TiDB Server 时加载本身的插件,得到本身定制的功能。

图 17 还列举了一些咱们正在作的功能,好比白名单,审计日志,Slow Query,还有一些在 TiDB Hackathon 中诞生的项目,咱们也想拿到插件中看看是否可以作出来。

4. Performance

<center>图 18 TiDB 3.0 Beta - OLTP Benchmark</center>

从图 18 中能够看到,咱们对 TiDB 3.0 Beta 中作了这么多性能优化以后,在 OLTP 这块进步仍是比较大的,好比在 SysBench 下,不管是纯读取仍是写入,仍是读加写,都有几倍的提高。在解决稳定性这个问题以后,咱们在性能方面会投入更多的精力。由于不少时候不能把「性能」单纯的看成性能来看,不少时候慢了,可能业务就挂了,慢了就是错误。

固然 TiDB 3.0 中还有其余重要特性,这里就不详细展开了。(TiDB 3.0 Beta Release Notes )

Next?

刚才介绍是 3.0 Beta 一些比较核心的特性,咱们还在继续作更多的特性。

1. Storage Layer

<center>图 19 TiDB 存储引擎层将来规划</center>

好比在存储引擎层,咱们对 Raft 层还在改进,好比说刚才我提到了咱们有 Raft Learner,咱们已经可以极大的减小因为调度带来的 Raft Group 不可用的几率,可是把一个 Learner 提成 Voter 再把另外一个 Voter 干掉的时间间隔虽然比较短,但时间间隔依然存在,因此也并非一个 100% 安全的方案。所以咱们作了 Raft Joint Consensus。之前成员变动只能一个一个来:先把 Learner 提成 Voter,再把另外一个 Voter 干掉。但有了 Raft Joint Consensus 以后,就能在一次操做中执行多个 ConfChange,从而把由于调度致使的 Region 不可用的几率降为零。

另外咱们还在作跨数据中心的部署。前面社区实践分享中来自北京银行的于振华老师提到过,他们是一个两地三中心五部分的方案。如今的 TiDB 已经有一些机制能比较不错地处理这种场景,但咱们可以作更多更好的东西,好比说咱们能够支持 Witness 这种角色,它只作投票,不一样步数据,对带宽的需求比较少,即便机房之间带宽很是低,他能够参与投票。在其余节点失效的状况下,他能够参与选举,决定谁是 Leader。另外咱们支持经过 Follower 去读数据,但写入仍是要走 Leader,这样对跨机房有什么好处呢? 就是能够读本地机房的副本,而不是必定要读远端机房那个 Leader,可是写入仍是要走远端机房的 Leader,这就能极大的下降读的延迟。除此以外,还有支持链式复制,而不是都经过 Leader 去复制,直接经过本地机房复制数据。

以后咱们还能够基于 Learner 作数据的 Backup。经过 learner 去拉一个镜像,存到本地,或者经过 Learner 拉取镜像以后的增量,作增量的物理备份。因此以后要作物理备份是经过 Learner 实时的把 TiKV 中数据作一个物理备份,包括全量和增量。当须要恢复的时候,再经过这个备份直接恢复就行了,不须要经过 SQL 导出再导入,能比较快提高恢复速度。

2. SQL Layer

<center>图 20 TiDB 存储引擎层将来规划</center>

在 SQL 层,咱们还作了不少事情,好比 Optimizer 正在朝下一代作演进,它是基于最早进的 Cascades 模型。咱们但愿 Optimizer 可以处理任意复杂的 Query,帮你们解决从 OLTP 到 OLAP 一整套问题,甚至更复杂的问题。好比如今 TiDB 只在 TiKV 上查数据,下一步还要接入TiFlash,TiFlash 的代价或者算子其实不同的,咱们但愿可以在 TiDB 上支持多个存储引擎,好比同一个 Query,能够一部分算子推到 TiFlash 上去处理,一部分算子在 TiKV 上处理,在 TiFlash 上作全表扫描,TiKV 上就作 Index 点查,最后汇总在一块儿再作计算。

咱们还计划提供一个新的工具,叫 SQL Tuning Advisor。如今用户遇到了慢 Query,或者想在上线业务以前作 SQL 审核和优化建议,不少时候是人肉来作的,以后咱们但愿把这个过程变成自动的。

除此以外咱们还将支持向量化的引擎,就是把这个引擎进一步作向量化。将来咱们还要继续兼容最新的 MySQL 8.0 的特性 Common Table,目前计划以 MySQL 5.7 为兼容目标,和社区用户一块儿把 TiDB 过渡到 MySQL 8.0 兼容。

说了这么多,我我的以为,咱们作一个好的数据库,有用的数据库,最重要一点是咱们有大量的老师,能够向用户,向社区学习。无论是分享了使用 TiDB 的经验和坑也好,仍是去提 Issue 报 Bug,或者是给 TiDB 提交了代码,都是在帮助咱们把 TiDB 作得更好,因此在这里表示一下衷心的感谢。最后再立一个 flag,去年咱们共写了 24 篇 TiDB 源码阅读文章,今年还会写 TiKV 源码系列文章。咱们但愿把项目背后只有开发同窗才能理解的这套逻辑讲出来,让你们知道 TiDB 是怎样的工做的,但愿今年能把这个事情作完,感谢你们。

1 月 19 日 TiDB DevCon 2019 在北京圆满落幕,超过 750 位热情的社区伙伴参加了这次大会。会上咱们首次全面展现了全新存储引擎 Titan、新生态工具 TiFlash 以及 TiDB 在云上的进展,同时宣布 TiDB-Lightning Toolset & TiDB-DM 两大生态工具开源,并分享了 TiDB 3.0 的特性与将来规划,描述了咱们眼中将来数据库的模样。此外,更有 11 位来自一线的 TiDB 用户为你们分享了实践经验与踩过的「坑」。同时,咱们也为新晋 TiDB Committer 授予了证书,并为 2018 年最佳社区贡献我的、最佳社区贡献团队颁发了荣誉奖杯。

相关文章
相关标签/搜索