伴鱼技术团队

Technology changes the world

伴鱼开放平台 上线了! 源于实践的解决方案,助力企业成就未来!

读 TiDB 论文有感:数据强一致性且资源隔离的 HTAP 数据库

引言

PingCAP 团队的论文《TiDB: A Raft-based HTAP Database》入选 VLDB 2020,这是对 TiDB 数据库阶段性成果的肯定,非常为国内数据库技术的快速发展而感到高兴。由于关于 TiDB 数据库在高可用、水平扩展和 ACID 事务的实现方案很久以前就已经公布出来了,对于这些主题大家都比较熟悉,所以就不再赘述了,下面主要谈谈论文中关于如何实现数据强一致性且资源隔离的 HTAP 数据库的一些启发和感想。

OLTP or OLAP

大家都知道数据库分为 OLTP 和 OLAP 类型,那么数据库为什么要分成这两类型呢?

首先,OLTP 和 OLAP 是定义数据处理方式的,这是两个差异特别明显的工作负载,OLTP 操作是涉及数据少,但是实时性和事务要求高,并发量大;OLAP 操作是实时性和事务要求低,但是涉及数据量大,并且查询模式不固定,很难通过索引来覆盖。其次,早期的数据库是没有 OLTP 和 OLAP 类型之分的,在一个数据库(主要为关系数据库)里进行 OLTP 和 OLAP 类型的数据相关的操作,后来数据量慢慢变大,直接在关系数据库中同时处理 OLTP 和 OLAP 类型的请求开始力不从心,更坏的情况可能还会影响到 OLTP 类型的请求,所以针对 OLAP 场景设计了更符合其工作负载的 OLAP 类型数据库,通过将 OLTP 类型的数据同步到 OLAP 类型的数据库,然后再进行 OLAP 类型的操作。

通过上面的方式,解决了 OLTP 和 OLAP 类型工作负载冲突的问题,但是引入了一次额外的外部数据复制(从 OLTP 到 OLAP),因而也导致了 OLAP 类型操作数据的实时性和一致性丢失的问题。这是从数据库系统外部通过异构系统来解决这一个问题,牺牲了数据的实时性和一致性,在论文中,TiDB 提出了一个新的方案,从数据库系统内部来解决这个问题,同时避免数据的实时性和一致性的丢失。

数据强一致性 or 资源隔离

前文中,我们谈到 OLTP 和 OLAP 是两种差异非常大的工作负载,通常要两者兼得的方案分为:
1、设计一套同时适合 OLTP 和 OLAP 工作负载的存储引擎,在这一个存储引擎来处理所有的数据请求,这样数据的实时性和一致性的问题很好解决,但是 OLTP 和 OLAP 工作负载相互不影响是一个很难解决的问题,并且一套存储引擎要同时适合 OLTP 和 OLAP 工作负载,在存储引擎的设计与优化上的限制是比较多的,感觉这是在设计一种五彩斑斓的黑;
2、在数据库内部同时存在两套存储引擎,分布负责 OLTP 和 OLAP 工作负载,这可以很好避免上面的问题,但是数据需要从两套存储引擎中复制,这样会导致同时满足数据强一致性和资源相互隔离是一个很难解决的问题;

论文中,TiDB 选择的是方案 2,针对 OLTP 工作负载提供一个行存引擎 TiKV,针对 OLAP 工作负载负载提供一个列存引擎 TiFlash,那么数据强一致性和资源相互隔离怎么解决呢?

一般来说,对于一个分布式存储系统,数据强一致性和资源相互隔离经常是一个二选一的选择题,选择数据强一致性,一般是通过同步复制的方式(比如同步双写等)将数据复制到多个相关的实例上,那么将导致所有的计算与存储资源都会紧耦合在一个系统中,一个局部的小问题可能会导致其他的部分受到影响,牵一发而动全身;而选择资源相互隔离,一般是通过异步复制的方式(比如主从同步等)将数据复制到其他的相关实例上来实现的,这样可以确保资源的相互隔离,但是数据强一致性得不到保证。

对于这个问题,TiDB 论文给出的解决方案是:扩展 Raft 算法,增加 Learner 角色。

Follower or Learner

TiKV 将每一段连续的数据称为一个 Region(默认 96 M),每一个 Region 是一个 Raft Group,通过 Raft 协议从 Leader 节点向 Follower 节点复制数据,这是一个同步复制的过程(超过半数节点复制完成就算成功复制),如果 TiFlash 也采用 Follower 的方式来同步数据,那么 TiKV 和 TiFlash 之间的数据复制可以简单理解为同步复制(其实严格来说算是介于同步和异步之间的复制,因为如果 TiFlash 的 Follower 同步慢或者挂掉后,相当于增加了一个复制失败的节点,这样就降低了多数派节点复制成功的概率,也就降低了 TiKV 的可用性)的,这样两个存储引擎之间就会相互影响,无法达到资源隔离的目标。

因此,TiDB 扩展了 Raft 算法,增加 Learner 角色。Learner 角色只异步接收 Raft Group 的 Raft 日志,它不参与 Raft 协议来提交日志或选举领导人,这样,在数据的复制过程中,TiFlash 的 Learner 节点对 TiKV 的性能开销非常小。TiFlash 通过 Learner 角色接受到数据后,将行格式元组转换为列式数据存储,达到了数据在 TiDB 集群中同时行存储和列存储的目的。

到这里,大家可能会发现一个问题,TiFlash 的 Learner 角色是异步接受 Raft Group 的 Raft Log,那么怎么保证 TiFlash 的数据强一致性呢?

这个问题是在 TiFlash 读数据的时候来解决的。类似 Raft 的 Follower Read 的机制,Learner 节点提供快照隔离级别,我们可以通过特定时间戳从 TiFlash 读取数据。在接收到读取请求后,Learner 向其 Leader 发送一个 readindex 请求,Learner 根据收到的 readindex 阻塞等待相应的 Raft Log 同步成功并且回放到本地存储后,再根据请求的时间戳过滤出满足要求的数据。

这样,TiDB 就将同步的数据复制机制转变成异步的数据复制机制了,并且保证了数据的强一致性。在 TiFlash 读数据的时候,TiFlash 的 Learner 只需要和 TiKV Leader 节点做一次 readindex 操作,这是一个非常轻量的操作,所以 TiKV 和 TiFlash 之间的影响会非常小。论文中实验的数据也验证了这一点,TiDB 中,同时进行 AP 和 TP 操作,AP 操作对 TP 吞吐量的影响最多不到 10%,TP 操作对 AP 吞吐量的影响最多不到 5%。

另外,论文的实验表明中,TiDB 在从 TiKV 到 TiFlash 异步数据复制机制导致的数据延迟也非常小:在 10 个 warehouses 的数据量下,数据复制的延迟大部分在 100 ms 以下,最大不超过 300 ms;在 100 个 warehouses 的数据量下,数据复制的延迟大部分在 500 ms 以下,最高不超过 1500 ms。并且这个数据的延迟不会影响 TiFlash 的一致性级别,只会让 TiFlash 上的请求稍微慢一点,因为接受到读请求的时候需要去做一次数据同步。

HTAP or (OLTP and OLAP)

到这里,TiDB 有了两个存储引擎:对 OLTP 友好的行存 TiKV,对 OLAP 友好的列存 TiFlash,其实这个不关键,关键的是这个两个存储引擎的数据同步是强一致性的,能对外提供一致的快照隔离级别,这对于计算层的查询优化器来说是一个非常大的优势,对于一个请求,查询优化器可以有三种扫描方式选择:TiKV 的行扫描和索引扫描,TiFlash 的列扫描,并且对于同一个请求,可以针对不同的部分采用不同的扫描方式,这为查询优化器提供了巨大的优化空间。从论文的实验数据也表明,同时使用 TiKV 和 TiFlash 的 AP 请求比单独只使用任何一个都是更优。

我们再回到文章的开头,当初由于数据库由于需要好处理 OLTP 和 OLAP 工作负载,将数据库按工作负载分为 OLTP 和 OLAP 类型数据库,然后让使用者再将请求分类成 OLTP 和 OLAP 类型请求相应类型的数据库,在这里却是另一种解决方式:对于使用者来说只有一个数据库,数据库通过对请求进行分析后决定用哪一个或者同时使用两个存储引擎,将数据库和查询按工作负载进行分类的方式消除,这个一个更高层次的抽象。

人们碰到一个问题,在当前找不到根本的解决方案时,总是先将问题按场景解构,在每一个小场景中一一解决而达到解决问题的目的,这只是一个权宜之计,等待理论或者技术进步后,再从根本上解决问题。比如在通信技术的发展过程中,先用有线电话解决在固定地点通信的问题,然后用寻呼机移动接受信息,再加有线电话来解决移动通信的问题,最后手机的出现,直接解决了远距离通信的问题,在这之前通过解构通信场景针对性的解决方案有线电话和寻呼机就慢慢退出历史舞台了。对于数据库来说也是一样,先分成不同的工作负载一一解决,最后肯定会形成统一来方案来解决的,TiDB 向前走了一大步,我们拭目以待。

单点 or 水平扩展

论文中也发现了一个比较有意思的地方,由于在分布式架构中,任何不能水平扩展的单点问题都是原罪,因为只要有一个不能水平扩展的单点,理论上就有可能成为整个系统的瓶颈,TiDB 作为一个可水平扩展的分布式数据库,在架构上是有一个单点依赖的:从 PD 获得时间戳。在论文中,通过严格的性能测试证明这个地方不会成为整个系统的瓶颈,感受到 TiDB 满满的求生欲了。

完全的去中心化 or 统一的中心化调度

目前的分布式存储系统,国外出现了很多完全的去中心化架构设计,比如 Cassandra 和 CockroachDB,但是 TiDB 的架构设计不是完全去中心化的,它有一个中心大脑角色 PD,正好和东西方的意识形态对应上了,小政府与大政府的方案,这也是一个比较有意思的地方。

Vitalik Buterin 指出选择完全去中心化设计的主要原因:fault tolerance, attack resistance, and collusion resistance。由于数据库都是部署在内部可信赖的网络,所以 attack resistance, collusion resistance 都不会是问题,这和比特币等数字货币为了确保不能被某一些人或者组织控制,出于社会和政治方面的原因采用去中心化架构是不一样的,并且 fault tolerance 在中心化的架构也是可以解决的。

另外,更重要的是对于 TiDB 等 NewSQL 或者 HTAP 数据库本身就是为海量数据设计的,数据库集群管理的节点和数据量会越来越大,特别是与云原始的弹性能力结合后,数据库的智能调度能力将是决定数据库性能和稳定性的关键因素,但是去中心化的架构会让调度决策变得困难,特别是需要全局视角和多节点协同的调度决策会更加困难。

所以,只要中心化角色不是系统瓶颈,那么中心化的调度是有其天然的优势的,毕竟对于调度来说,最重要的是全局视角和多节点协调能力。TiDB 对于其中心化的大脑角色 PD 定位是非常轻量,没有持久化调度相关状态的信息,所以不会是影响整个系统的水平扩展能力。

现在 or 未来

从 TiDB 的内部来看,我们可以看到 TiDB 是彻底的存储计算分离架构,目前计算层有两个引擎:SQL Engine 和 TiSpark,存储层也有两个引擎:TiKV 和 TiFlash,未来,不论是计算层还是存储层都能很方便的扩展到新的生态中,所以 TiDB 的目标感觉不仅仅只是一个数据库,同时也是在打造一个分布式存储的生态。

从单集群 TiDB 的角度来看,数据强一致性但资源相互隔离的 HTAP 是一个非常高效的能力,省去了数据从 OLTP 数据库同步到 OLAP 数据库的过程,也省去了将 OLAP 数据库计算结果需要提供在线业务使用时,再将数据同步到 OLTP 数据库的过程,这样,工程师都开开心心的搬砖和写 SQL,而不是频繁的搬数据。比起搬砖来说,搬数据像运水,更容易出现洒水、渗水等问题。

从多集群 TiDB 的角度来看,虽然 TiDB 提供了 HTAP 数据库水平扩容的能力,但是没有提供租户隔离能力,导致出于业务隔离、数据级别隔离和运维保障(比如备份和恢复)等方面的原因,所以从目前来看,不可能将整个公司的所有数据都放入一个 TiDB 集群中,那么虽然 TiDB 提供了 OLAP 能力,但是如果需要做 AP 操作的数据分布在多个集群中,这样依然需要将多个集群的数据从外部同步到一个提供 OLAP 能力的数据库中(可以是 TiDB),导致前面 TiDB 通过 HTAP 解决的问题又再一次出现。感觉一个可行的思路是在 TiDB 集群之上增加一个类似 Google F1 层,这样在一个统一的 F1 层下,可以有很多个 TiDB 集群,每个 TiDB 集群之间是彻底隔离的,每一个 TiDB 集群相当于一个租户,F1 层提供元数据的管理、读写请求的路由能力和跨 TiDB 集群的 AP 能力;另一个思路是在 TiDB 内部来解决,在存储层增加租户的概念,每个租户对应一组存储节点,这样租户之间存储层是隔离的,计算层可以做跨租户的 AP 操作。总的来说,这是一个存储层希望资源隔离,但是计算层希望统一视角的问题,期待 TiDB 后续的解决方案。

最后

最后,我们聊聊这篇论文的价值,一般来说工程论文的价值和学术论文是不一样的,工程论文的贡献,很多时候不是思想、理论等方面有特别大的创新,而是告诉大家,这个方向是可以行的,通过确定性的工程实现来减少了很多不确定的探索。比如 Google 的 GFS、MapReduce、Bigtable 相关的论文,在学术上创新是不大的,几乎所有的思想和理论都是已经存在的,但是它告诉大家,将其分布式实现是可行的,这个意义就非常大了,极大地推进了分布式存储系统的普及与发展。

所以,TiDB 的论文作为业界第一篇 Real-time HTAP 分布式数据库工业实现的论文,希望能加速 Real-time HTAP 分布式数据库的普及与发展,从这个层面来说,这个意义是非常大的。

欢迎关注我的其它发布渠道