TiDB: A Raft-based HTAP Database

ABSTRACT

混合事务和分析处理 (HTAP) 数据库需要单独处理事务和分析查询,以消除它们之间的干扰。为此,需要维护为两种类型的查询指定的不同数据副本。但是,为存储系统中的分布式副本提供一致的视图是具有挑战性的,因为分析请求可以有效地从大规模和高可用性的事务工作负载中高效读取一致和新的数据。

为了迎接这一挑战,我们建议扩展复制的基于状态机的一致一致算法,为 HTAP 工作负载提供一致的副本。基于这一新颖的想法,我们提出了一个基于漂流的HTAP数据库:TiDB。在数据库中,我们设计了一个多层存储系统,该系统由行存储和列存储组成。行存储基于 Raft 算法构建。它可以扩展,以实现高可用性的事务请求的更新。 特别是,它异步地将 Raft 日志复制到学员中,学员将行格式转换为元组列格式,从而形成实时可上座的列存储。此列存储允许分析查询,以便有效地读取新鲜且一致的数据,同时与行存储区中的事务强隔离。 基于此存储系统,我们构建一个 SQL 引擎来处理大型分布式事务和昂贵的分析查询。SQL 引擎以最佳方式访问数据的行格式和列格式副本。 我们还包括一个强大的分析引擎 TiSpark,以帮助 TiDB 连接到 Hadoop 生态系统。 综合实验表明,TiDB 实现了隔离的高性能非 der CH-benCHmark,这是一个专注于 HTAP 工作负载的基准。

  1. INTRODUCTION

关系数据库管理系统 (RDBMS) 受其关系模型、强大的事务性保证和 SQL 接口的欢迎。它们在传统应用中被广泛采用,如业务系统。但是,旧的 RDBMS 不支持可伸缩性和高可用性。因此,在2000年初[11],互联网应用程序更喜欢NoSQL系统,如谷歌大表[12]和DynamoDB[36]。NoSQL 系统放宽了一致性要求,并提供高可扩展性和替代数据模型,如键值对、图形和文档。但是,许多应用程序还需要强大的事务、数据一致性和 SQL 接口,因此出现了 NewSQL 系统。NewSQL 系统(如CockroachDB [38] 和 GoogleSpanner [14])为在线事务处理 (OLTP) 读/写工作负载提供了 NoSQL 的高可扩展性,并且仍然维护对交易的 ACID保证 [32]。此外,基于 SQL 的在线分析处理 (OLAP) 系统正在快速开发,就像许多 SQL on Hadoop 系统 [16] 一样。

这些系统遵循"一刀切"的范式[37],使用不同的数据模型和技术,用于 OLAP 和 OLTP 的不同目的。但是,多个系统开发、部署和维护的成本非常昂贵。此外,实时分析最新版本的数据也极具吸引力。这就在工业界和学术界引起了混合OLTP和OLP(HTAP)系统[30]。 HTAP 系统应实现可扩展性、高可用性和跨国一致性,如 NewSQL 系统。此外,HTAP 系统需要高效地读取最新数据,以确保 OLTP 和 OLAP 请求的吞吐量和延迟,满足两项额外要求:新鲜度和隔离性。

新鲜度是指分析查询处理最近数据的过程 [34]。实时分析最新数据具有极大的业务价值。但是,在某些 HTAP 解决方案中,例如基于提取-转换加载 (ETL) 处理的解决方案,它不能保证。通过 ETL 流程,OLTP 系统定期向 OLAP 系统重新更新一批最新数据。ETL 需要几个小时或几天的时间,因此无法提供实时分析。ETL 阶段可以通过将最新更新流式传输到 OLAP 系统来替换,以减少同步时间。但是,由于这两种方法缺少全局数据治理模型,因此考虑一致性语义更为复杂。与多个系统连接会引入额外的开销。

隔离是指保证单独的 OLTP 和 OLAP 查询的隔离性能。某些内存中数据库(如 HyPer [18])允许分析查询从同一服务器上的事务处理读取最新版本的数据。尽管此方法提供了新的数据,但它无法同时实现 OLTP 和 OLAP 的高性能。这是因为数据同步处罚和工作负载干扰。通过运行CH-benCHmark[13](Hy-Per和SAP HANA上的HTAP基准),在[34]中研究了这种效应。研究发现,当系统共同运行分析查询时,其最大可达到的 OLTP 吞吐量会显著降低。SAP HANA [22] 吞吐量至少减少了三倍,HyPer 至少减少了五倍。类似的结果在 MemSQL [24] 中得到证实。此外,如果内存中的数据库仅部署在单个服务器上,则无法提供高可用性和可伸缩性。

为了保证隔离性能,有必要在不同的硬件资源上运行 OLTP and OLAP 请求。 基本困难在于在单个系统中维护来自 OLTP 工作负载的 OLAP 请求的最新副本。此外,系统需要保持更多复制之间的数据一致性。 请注意,可用性 [29] 还需要维护一致的副本。 可以使用众所周知的协商一致算法(如 Paxos [20] 和 Raft [29])实现高可用性。 它们基于复制的状态计算机来同步副本。 可以扩展这些共识算法,为 HTAP 工作负载提供一致的副本。 据我们所知,这个想法以前没有研究过。

按照这个想法,我们提出了一个基于漂流的HTAP数据库:TiDB。它将专用节点(称为学员)引入 Raft 共识算法。学员异步从领导节点复制事务日志,为 OLAP 查询构造新的副本。特别是,学员将日志中的行格式元数转换为列格式,以便副本更适合分析查询。这种日志复制在领导者节点上运行的事务查询上很少产生开销。此外,这种复制的延迟非常短,可以保证 OLAP 的数据新鲜度。我们使用不同的数据副本分别处理 OLAP 和 OLTP 请求,以避免它们之间的干扰。我们还可以基于行格式和列格式的数据副本优化 HTAP 请求。基于 Raft 协议,TiDB 提供高可用性、可扩展性和数据一致性。

TiDB 提供了一个创新的解决方案,可帮助基于共识算法的 NewSQL 系统演变为 HTAP 系统。New-SQL 系统通过复制 Google Spanner 和Cockroach DB 等数据库,确保 OLTP 请求的高可用性、可扩展性和数据持久性。它们通过通常来自共识算法的复制机制跨数据副本同步数据。基于日志复制,NewSQL 系统可以提供专用于 OLAP 请求的列副本,以便它们可以像 TiDB 那样单独支持 HTAP 请求。

我们的贡献如下。

•我们建议基于共识算法构建 HTAP 系统,并实现了基于 Raft 的 HTAP 数据库 TiDB。这是一个开源项目 [7],为 HTAP 工作负载提供高可用性、一致性、可扩展性、数据新鲜度和隔离性

•我们将学员角色引入 Raft 算法,以生成用于实时 OLAP 查询的列存储。

•我们实施多 Raft 存储系统并优化其读取和写入,以便系统在扩展到更多节点时提供高性能。

•我们为大型 HTAP 查询定制 SQL 引擎。引擎可以最佳地选择使用基于行的存储和列存储。

•我们使用 HTAP 基准 CH-benCHmark 进行综合实验,以评估 TiDB 在 OLTP、OLAP 和 HTAP 方面的表现。

本文的其余部分按如下方式组织。我们在第 2 节中描述了主要思想,基于 Raft 的 HTAP,并在第 3 节中说明了 TiDB 的体系结构。第 4 节第 5 节阐述了 TiDB 的多负载存储和 HTAP 引擎。实验评价在第6节中介绍。我们总结了第 7 节中的相关工作。最后,我们在第8节中结束我们的论文。

TiDB: A Raft-based HTAP Database_第1张图片

  1. RAFT-BASED HTAP

共识算法(如 Raft 和 Paxos)是构建一致、可扩展且高度可用的分布式系统的基础。其优势在于使用复制的状态机在服务器之间实时可靠地复制数据。我们调整此功能,将数据复制到不同的服务器,用于不同的 HTAP 工作负载。这样,我们保证 OLTP 和 OLAP 工作负载彼此隔离,但 OLAP 请求也会为数据提供全新且一致的视图。据我们所知,以前没有使用这些共识算法来构建 HTAP 数据库的工作。

由于 Raft 算法设计为易于理解和实现,因此我们专注于 Raft 扩展,实现生产就绪的 HTAP 数据库。如图 1 所示,在高级别上,我们的想法如下:数据存储在多个 Raft 组中,使用行格式提供事务查询。每个小组由一名领导者和追随者组成。我们添加每个组的学员角色,以异步地从领导者复制数据。这种方法是低开销,并保持数据一致性。复制到学员的数据将转换为基于列的格式。查询优化器被扩展,以探索访问基于行和基于列的副本的物理计划。

在标准 Raft 组中,每个关注者都可以成为提供读写请求的领导者。简单地添加更多的追随者,因此,不会隔离资源。此外,添加更多关注者将影响组的性能,因为领导者必须等待来自较大仲裁节点的响应,然后才能响应客户端。因此,我们向 Raft 共识算法引入了一个学员角色。学员不参与领导者选举,也不参与日志复制的法定人数。从领导者到学员的日志复制是异步的;领导者不需要等待成功,然后再响应客户端。领导者和学员之间的强一致性在读取期间强制执行。根据设计,领导者和学员之间的日志复制延迟较低,如评估部分所演示。

事务查询需要高效的数据更新,而分析查询(如联接或聚合)需要读取列的子集,但需要读取大量行。基于行的格式可以利用索引有效地为事务查询服务。基于列的格式可以有效地利用数据压缩和矢量化处理。因此,在复制给 Raft 学员时,数据会从基于行的格式转换为基于列的格式。此外,学习者可以部署在单独的物理资源中。因此,事务查询和分析查询在隔离资源中处理。

我们的设计也提供了新的优化机会。由于数据在基于行的格式和基于列的格式之间保持一致,因此我们的查询优化器可以生成访问两个存储区的物理计划。

我们提出了扩展 Raft 以满足 HTAP 数据库的新鲜度和隔离要求的想法。为使 HTAP 数据库生产准备就绪,我们克服了许多工程挑战,主要包括:

TiDB: A Raft-based HTAP Database_第2张图片

(1) 如何构建可扩展的 Raft 存储系统以支持高度并发读/写?如果数据量超过 Raft 算法管理的每个节点上的可用空间,我们需要一个分区策略来在服务器上分发数据。此外,在基本 Raft 过程中,请求按顺序处理,并且任何请求都必须在响应客户端之前由 Raft 节点的仲裁批准。此过程涉及网络和磁盘操作,因此非常耗时。这种过头使领导者成为处理重新任务(尤其是在大型数据集上)的瓶颈

(2) 如何以低延迟将日志同步到学员中,以保持数据新鲜?正在执行的事务可以生成一些非常大的日志。这些日志需要在学员中快速重播和实现,以便可以读取新的数据。将日志数据转换为列格式可能会由于架构不匹配而遇到错误。这可能延迟日志同步。

(3) 如何有效地处理具有保证性能的事务和分析查询?大型事务查询需要读取和写入分布在多台服务器中的大量数据。分析查询也会消耗密集的重新源,不应影响联机事务。为了减少执行开销,他们还需要在行格式存储和列格式存储上选择最佳计划。

在以下各节中,我们将详细阐述 TiDB 的设计和实现,以应对这些挑战。

  1. ARCHITECTURE

在本节中,我们将介绍 TiDB 的高级别结构,如图 2 所示。TiDB 支持 MySQL 协议,并且可由与 MySQL 兼容的客户端访问。它有三个核心组件:分布式存储层、放置驱动程序(PD)和计算引擎层。

分布式存储层由行存储 (TiKV) 和列存储 (TiFlash) 组成。从逻辑上讲,存储在 TiKV 中的数据是有序的键值映射。每个元组都映射到键值对。密钥由其表 ID 和行 ID 组成,值是实际行数据,其中表 ID 和行 ID 是唯一的整数,行 ID 将来自主键列。例如,包含四列的元组编码为:
Key:{table{tableID}record{rowID}}
Value:{col0, col1, col2, col3}

为了横向扩展,我们采取范围分区策略,将大键值映射拆分为许多连续范围,每个区域称为区域。每个区域都有多个副本,用于高可用性。Raft 共识算法用于保持每个区域副本之间的一致性,从而形成一个 Raft 组。不同 Raft 组的领导者异步将数据从 TiKV 复制到 TiFlash。TiKV 和 TiFlash 可以部署在单独的物理资源中,因此在处理事务和分析查询时提供隔离。

放置驱动程序 (PD) 负责管理区域,包括提供每个键的区域和物理位置,并自动移动区域以平衡工作负载。PD 也是我们的时间戳预言家,提供严格增加和全球唯一的时间戳。这些时间戳也用作我们的交易 ID。PD 可能包含多个 PD 成员,用于鲁棒性和性能。PD 没有持久状态,在启动时,PD 成员从其他成员和 TiKV 节点收集所有必要的数据。

TiDB: A Raft-based HTAP Database_第3张图片

计算引擎层是无状态的,是可扩展的。我们定制的 SQL 引擎具有基于成本的查询优化器和分布式查询执行器。TiDB 实现了基于渗透器 [33] 的两阶段提交 (2PC) 协议,以支持事务处理。查询优化器可以根据查询最佳选择从 TiKV 和 TiFlash 读取。

TiDB 的体系结构满足 HTAP 数据库的要求。TiDB 的每个组件都设计为具有高可用性和可扩展性。存储层使用 Raft 算法实现数据副本之间的一致性。TiKV 和 TiFlash 之间的低延迟复制使分析查询能够使用新的数据。查询优化器以及 TiKV 和 TiFlash 之间的强一致性数据可提供快速的分析查询处理,对事务处理影响很小。

除了上述组件外,TiDB 还与 Spark 集成,这有助于集成存储在 TiDB 和 Hadoop 分布式文件系统 (HDFS) 中的数据。TiDB 具有一组丰富的生态系统工具,用于将数据导入 TiDB 并导出数据,以及将数据从其他数据库迁移到 TiDB。

在以下部分中,我们将对分布式存储层、SQL 引擎和 TiSpark 进行深入深入探讨,以演示 TiDB(一个生产就绪的 HTAP 数据库)的功能。

  1. MULTI-RAFT STORAGE

图 3 显示了 TiDB 中分布式存储层的体系结构,其中具有相同形状的对象扮演相同的角色。存储层由基于行的存储、TiKV 和基于列的存储 TiFlash 组成。存储将一个大表映射到一个大键值映射,该映射被拆分为存储在TiKV中的多个区域。每个区域使用 Raft 共识算法来保持副本之间的一致性,以实现高可用性。当数据复制到 TiFlash 时,多个区域可以合并到一个分区中,以便于表扫描。通过异步日志复制,TiKV 和 TiFlash 之间的数据保持一致。由于多个 Raft 组管理分布式存储层中的数据,因此我们称之为多 Raft 存储。在以下各节中,我们将详细介绍 TiKV 和 TiFlash,重点介绍优化,使 TiDB 成为生产就绪的 HTAP 数据库。

4.1 Row-based Storage (TiKV )

TiKV 部署由许多 TiKV 服务器组成。使用 Raft 在 TiKV 服务器之间复制区域。每个 TiKV 服务器都可以是不同区域的 Raft 领导者或跟随者。在每个 TiKV 服务器上,数据和元数据都保留到 RocksDB,这是一个可嵌入的、持久的、键值存储 [5]。每个区域都有可配置的最大大小,默认情况下为 96 MB。Raft 领导服务器的 TiKV 服务器处理相应区域的读/写请求

当 Raft 算法响应读写请求时,在领导者及其跟随者之间执行基本的 Raft 过程:

(1) 区域领导接收来自 SQL 引擎层的请求。

(2) 领导者将请求附加到其日志。

(3) 领导者将新的日志条目发送给其关注者,而关注者又将条目附加到其日志中。

(4) 领导者等待其追随者做出回应。如果节点的仲裁响应成功,则领导者将commit请求并在本地应用它。

(5) 领导者将结果发送给客户端,并继续处理传入的请求。

此过程可确保数据一致性和高可用性。但是,它不提供有效的性能,因为这些步骤按顺序执行,并且可能会产生较大的 I/O 开销(磁盘和网络)。以下各节介绍我们如何优化此过程以实现高读/写吞吐量,即解决第 2 节中描述的第一个挑战。

4.1.1 Optimization between Leaders and Followers

在上述过程中,第二步和第三步可以并行进行,因为它们之间没有依赖关系。因此,领导者在本地追加日志,并同时向关注者发送日志。如果在领导者追加日志失败,但关注者仲裁成功追加日志,则仍然可以提交日志。在第三步中,当向关注者发送日志时,领导者会缓冲日志条目,并分批将其发送给其关注者。发送日志后,领导者不必等待关注者响应。相反,它可以假设成功,并发送更多日志与预测的日志索引。如果发生错误,领导者将调整日志索引并重新发送复制请求。在第四步中,应用已提交日志条目的领导可以由不同的线程异步处理,因为在此阶段,一致性没有风险。根据上述优化,Raft 过程将更新如下:

(1) 领导者接收来自 SQL 引擎层的请求。

(2) 领导者将相应的日志发送给关注者,并并行地在本地追加日志。

(3) 领导者继续收到客户的请求,并重新执行步骤 (2)。

(4) 领导者提交日志并将其发送到要应用的另一个线程。

(5) 应用日志后,领导者将结果返回给客户端。

在此最佳流程中,来自客户端的任何请求仍运行所有 Raft 步骤,但来自多个客户端的请求并行运行,因此总体吞吐量会增加。

4.1.2 Accelerating Read Requests from Clients

从 TiKV 领导读取数据提供线性语义。这意味着,当值在时间 t 从区域领导读取时读取时,领导者不得在 t 之后返回读取请求值的早期版本。这可以通过使用如上所述的 Raft 实现:为每个读取请求发出日志条目,并等待该条目在返回前提交。但是,此过程非常昂贵,因为日志必须在 Raft 组中的大多数节点上复制,这会造成网络 I/O 的开销。为了提高性能,我们可以避免日志同步阶段。

Raft 保证领导者成功写入数据后,领导者可以响应任何读取请求,而无需跨服务器同步日志。但是,在领导者选举后,领导者角色可能会在 Raft 组中的服务器之间移动。为了实现来自领导者的读取,TiKV 实现了以下读取优化,如 [29] 中所述。

第一种方法称为读取索引。当领导者响应读取请求时,它会将当前提交索引记录为本地读取索引,然后向关注者发送检测信号消息以确认其领导角色。如果它确实是领导,则一旦应用的索引大于或等于读取索引,它就可以返回该值。此方法可提高读取性能,但会导致很少的网络开销。

另一种方法是租约读取,它减少了由读取索引引起的网络过头心跳。领导者和追随者同意租赁期,在此期间,追随者不发出选举请求,以便不会更改领导者。在租赁期间,领导者可以响应任何读取请求,而无需连接到其关注者。如果每个节点上的 CPU 时钟差异并不很大,那么此方法效果良好。

除了领导者之外,关注者还可以响应来自客户的重新请求,这称为关注者阅读。跟随者收到读取请求后,它会向领导者询问最新的读取索引。如果本地应用的索引等于或大于读取索引,则跟随者可以将值返回给客户端;如果本地应用的索引等于或大于读取索引,则跟随者可以将值返回给客户端。否则,它必须等待日志的应用。跟随者阅读可以减轻热区领导者的压力,从而提高阅读性能。然后,通过添加更多关注者,可以进一步提高读取性能。

4.1.3 Managing Massive Regions

大量区域分布在服务器群集上。服务器和数据大小在动态变化,区域可能会群集在某些服务器中,尤其是领导者副本。这会导致某些服务器的磁盘过度使用,而其他服务器磁盘则是免费的。此外,服务器可以添加到群集或从群集中移动。

若要在服务器之间平衡区域,计划驱动程序 (PD) 会安排具有副本数量和位置限制的区域。一个关键的约束是将一个区域的至少三个副本放在不同的 TiKV 实例上,以确保高可用性。PD 通过从服务器通过检测信号收集特定信息进行初始化。它还监视每台服务器的工作负载,并在不会影响应用程序的情况下将热区域迁移到不同的服务器。

另一方面,维护海量区域涉及发送检测信号和管理元数据,这可能会导致大量的网络工作和存储开销。但是,如果 Raft 组没有任何工作负荷,则不需要检测信号。根据区域工作负载的繁忙情况,我们可以调整发送检测信号的频率。这降低了出现网络延迟或节点过载等问题的可能性。

4.1.4 Dynamic Region Split and Merge

一个大区域可能会变得太热,无法在合理的时间内阅读或书写。热区域或大区域应拆分为较小的区域,以便更好地分配工作负载。另一方面,许多地区可能很小,很少被访问;但是,系统仍然需要维护检测信号和元数据。在某些情况下,维护这些小区域会产生大量的网络和 CPU 开销。因此,有必要合并较小的区域。请注意,为了维护区域之间的顺序,我们仅在键空间中合并相邻区域。根据观察到的工作负荷,PD 动态地向 TiKV 发送拆分和合并命令。

拆分操作将区域划分为几个新的较小区域,每个区域涵盖原始区域中的连续键范围。覆盖最右侧范围的区域可重用原始区域的 Raft 组。其他区域使用新的 Raft 组。拆分过程类似于 Raft 进程中的正常更新请求:

TiDB: A Raft-based HTAP Database_第4张图片
(1) PD 向区域领导者发出拆分命令。

(2) 收到拆分命令后,领导者将该命令转换为日志,并将日志复制到所有跟随节点。日志仅包括拆分命令,而不是修改实际数据。

(3) 一旦仲裁复制日志,领导者将提交拆分命令,该命令将应用于 Raft 组中的所有节点。应用过程包括更新原始区域的范围和纪元元数据,以及创建新的区域以覆盖其余范围。请注意,该命令以原子方式应用并同步到磁盘。

(4) 对于拆分区域的每个副本,将创建一个 Raft 状态机并开始工作,从而形成一个新的 Raft 组。原始区域的领长将拆分结果报告给 PD。拆分过程完成。

请注意,当大多数节点提交拆分日志时,拆分过程将成功。类似于提交其他 Raft 日志,而不是要求所有节点完成区域拆分。拆分后,如果对网络进行分区,则具有最近纪元的节点组将获胜。由于只需要元数据更改,因此区域分割的开销较低。拆分命令完成后,由于 PD 的常规负载平衡,新拆分区域可能会跨服务器移动。

合并两个相邻区域与拆分一个区域相反。PD 移动两个区域的副本,以在单独的服务器上协调它们。然后,通过两阶段操作,将两个区域的合并副本合并到每台服务器上的本地区域;也就是说,停止一个区域的服务,并将其与另一个区域合并。此方法与拆分区域不同,因为它不能使用两个 Raft 组之间的日志复制过程来商定合并它们。

4.2 Column-based Storage (TiFlash)

尽管我们优化了上述 TiKV 的读取数据,但 TiKV 中的行格式数据并不适合快速分析。因此,我们将列存储 (TiFlash) 合并到 TiDB 中。TiFlash 由学员节点组成,这些节点只从 Raft 组接收 Raft 日志,并将行格式的元组转换为列数据。他们不参与 Raft 协议来提交日志或选举领导者,因此它们很少在 TiKV 上产生开销。

用户可以使用 SQL 语句为表设置列格式副本:

ALTER TABLE x SET TiFLASH REPLICA n;

其中 x 是表的名称,n 是副本的数量。默认值为 1。添加列副本类似于向表添加异步列形索引。TiFlash 中的每个表被划分为多个分区,每个分区根据 TiKV 的几个连续区域覆盖一个连续的元组范围。较大的分区便于范围扫描。

4.2.1 Log Replayer

根据 Raft 算法,学员节点接收的日志是线性的。为了保持已提交数据的线性语义,根据先出先出 (FIFO) 策略重播它们。日志重播有三个步骤:

(1) 压缩日志:根据第 5.1 节中描述的事务模型,事务日志分为三种状态:预写、提交或回滚。回滚日志中的数据不需要写入磁盘,因此压缩进程会根据回滚日志删除无效的预先写入的日志,并将有效的日志放入缓冲区。

(2) 解码元数:缓冲区中的日志被解码为行格式的元数,删除有关事务的冗余信息。然后,将解码的元数放入行缓冲区中。

(3) 转换数据格式:如果行缓冲区中的数据大小超过大小限制或其时间持续时间超过 val 间限制,则这些行格式的元数将转换为列数据并写入本地分区数据池。转换是指本地缓存架构,这些架构会定期与 TiKV 同步,如下文所述。

若要说明日志重播过程的详细信息,请考虑以下示例。我们将每个 Raft 日志项抽象为事务 ID 操作类型 transaction status[#committs]操作数据。根据典型的 DML,操作类型包括插入、更新和删除元组。事务状态可能预先写、提交或回滚。操作数据可以作为特定插入或更新的元组,也可以是已删除的密钥。

在表 1 所示的示例中,原始日志包含八个项目,它们尝试插入两个元组、更新一个元组和删除一个元组。但是插入 k1 会回滚,因此仅保留了八个原始日志项中的六个,其中三个元组被解码。最后,三个解码的元组被转换成五列:操作类型、提交时间戳、键和两列数据。这些列将追加到增量树。

4.2.2 Schema Synchronization

要实时将元组转换为列格式,学员节点必须了解最新的架构。这种架构过程不同于TiKV上的无架构操作,它把元数编码为字节数组。最新的架构信息存储在 TiKV 中。为了减少 TiFlash 向 TiKV 请求最新架构的数量,每个学员节点都维护一个架构缓存。

TiDB: A Raft-based HTAP Database_第5张图片
缓存通过架构同步器与 TiKV 的架构同步。如果缓存的架构已过期,则要解码的数据与本地架构之间存在不匹配,并且必须重新转换数据。架构同步的频率和架构不匹配的数量之间有一个权衡。我们采取两阶段策略:

•定期同步:架构同步器定期从 TiKV 提取最新的架构,并应用更改到其本地缓存。在大多数情况下,这种临时同步会降低同步架构的频率。

•强制同步:如果架构同步器检测到不匹配的架构,它将主动从 TiKV 获取最新的架构。当元数和架构之间不同或列值溢出时,可以触发此情况。

4.2.3 Columnar Delta Tree

为了有效地写入和读取具有高直通投入的列数据,我们设计了一个新的列存储引擎 Delta Tree,它立即追加增量更新,然后将它们与每个分区的先前稳定版本合并。增量更新和稳定数据分别存储在增量树中,如图 4 所示。在稳定空间中,分区数据存储为块,每个区块都覆盖分区元组较小范围。此外,这些行格式的元对按列存储。相反,增量直接追加到增量空间中,以 TiKV 生成它们的顺序排列。TiFlash 中柱数据的商店格式类似于 Parquet [4]。它还将行组存储到列块中。不同地,TiFlash 将行组的列数据及其元数据存储到不同的文件中以同时更新文件,而不是仅在 Parquet 中存储一个文件。TiFlash 只是使用常见的 LZ4 [2] 压缩数据文件来保存其磁盘大小。

新的传入增量是插入数据的原子批处理或删除的范围。这些增量缓存到内存中并化为磁盘。它们按顺序存储,因此它们实现了提前写入日志 (WAL) 的功能。这些增量通常存储在许多小文件中,因此在读取时会导致较大的 IO 开销。为了降低成本,我们定期将这些小增量压缩成一个更大的增量,然后将较大的增量刷新到磁盘中,并替换以前实现的小增量。传入增量的内存副本便于读取最新数据,如果旧增量达到有限大小,则删除它们。

读取某些特定元组的最新数据时,有必要将所有增量文件及其稳定的元组(即读取放大)合并,因为相关增量分布的位置并不事先知道。由于读取大量文件,这样的过程非常昂贵。此外,许多增量文件可能包含无用的数据(即空间放大),这些数据会浪费存储空间并减慢将它们与稳定元组合并的速度。因此,我们定期将增量合并到稳定空间中。每个增量文件及其相关区块都读取到内存中并合并。增量中插入的元组将添加到稳定中,修改后的元组将替换原始元组,并移动已删除的元组。合并的区块以原子方式替换磁盘中的原始区块。

合并增量非常昂贵,因为相关键在增量空间中是无序的。这种混乱还减缓了将增量与稳定区块集成以返回读取请求的最新数据的速度。因此,我们在增量空间的顶部构建一个 B+ 树索引。每个增量更新项都按其键和时间戳排序插入 B+ 树中。此订单优先级有助于有效地查找一系列键的更新,或在响应读取请求时在增量空间中查找单个键。此外,B+ 树中的有序数据很容易与稳定区块合并。

我们进行一项微观实验,将 Delta Tree 的性能与 TiFlash 中的日志结构化合并 (LSM) 树 [28] 进行比较,在该树根据 Raft 日志更新数据时读取数据。我们设置了三个TiKV节点和一个TiFlash节点,在实验部分列出了硬件结构。我们在 TiKV 上运行 Sysbench [6] 的唯一写入工作负载,并在 TiFlash 上运行"从 sbtest1 中选择计数(id)、count(k)"。为了避免数据压缩的大量写入放大,我们使用通用压缩而不是水平样式压缩来实现 LSM 存储引擎。此实现在 ClickHouse [1] 中也采用,这是一个面向列的 OLAP 数据库。

如表 2 所示,无论有 1 亿还是 2 亿个元组以及事务工作负载,从增量树读取的速度比 LSM 树快两倍。这是因为在增量树中,每个读取访问最多在 B+ 树中索引的增量文件的一个级别,同时访问 LSM 树中更多的过度映射文件。在不同的写入工作负载下,性能几乎保持稳定,因为增量文件的比例几乎相同。虽然 Delta 树 (16.11) 的写入放大大于 LSM 树 (4.74),但它也是可以接受的。
TiDB: A Raft-based HTAP Database_第6张图片

4.2.4 Read Process

与跟随者读取一样,学员节点提供快照隔离,因此我们可以在特定时间戳中从 TiFlash 读取数据。收到读取请求后,学员向其领导发送读取索引请求,以获取涵盖请求的时间戳的最新数据。作为响应,领导者将引用的日志发送给学员,学员重播并存储日志。将日志写入 Delta 树后,将读取来自增量树的特定数据以响应读取请求。

  1. HTAP ENGINES

为了解决第 2 节中提到的第三个挑战,即处理大型事务和分析查询,我们提供了一个 SQL 引擎来评估事务和分析查询。SQL 引擎调整 Percolator 模型,在分布式群集中实现乐观和悲观锁定。SQL 引擎通过使用基于规则和成本的优化器、索引和将计算推送到存储层来加速分析查询。我们还实施 TiSpark 与 Hadoop 生态系统连接,并增强 OLAP 能力。HTAP 请求可以在隔离存储和引擎服务器中单独处理。特别是,SQL引擎和TiSpark受益于同时使用行和列存储,以获得最佳结果。

TiDB: A Raft-based HTAP Database_第7张图片

5.1 Transactional Processing

TiDB 提供具有快照隔离 (SI) 或可重复读取 (RR) 语义的 ACID 事务。SI 允许事务中的每个请求读取数据的一致版本。RR 表示事务中的不同语句可能会读取同一键的不同值,但重复读取(即具有相同时间戳的两次读取)将始终读取相同的值。我们的实现基于多版本并发控制 (MVCC),避免了读写锁定,并可防止写入冲突。在 TiDB 中,事务是 SQL 引擎、TiKV 和 PD 之间的协作事务。在跨行动期间,每个组件的责任如下:

•SQL 引擎:协调事务。它接收来自客户端的写入和读取请求,将数据转换为键值格式,并使用two-phase commit(2PC) 将事务写入 TiKV。

•PD:管理逻辑区域和物理位置;提供全局、严格增加的时间戳。

•TiKV:提供分布式事务接口,实现 MVCC,并将数据保留到磁盘。

TiDB 实现乐观和悲观锁定。它们根据 Percolator 模型改编,该模型选择一个键作为主键,并使用它代表事务的状态,并基于 2PC 执行事务。乐观事务的过程图 5 的左侧说明了这一过程。(为简单起见,图忽略异常处理。

(1) 从客户端收到"开始"命令后,SQL 引擎要求 PD 使用时间戳作为事务的开始时间戳(start_ts)。

(2) SQL 引擎通过从 TiKV 读取数据并写入本地内存来执行 SQL DML。TiKV 在跨操作的发布commit_ts之前提供最新的提交时间戳(start_ts)。

(4) 当 SQL 引擎从客户端收到提交命令时,它将启动 2PC 协议。它随机选择主键,并行锁定所有密钥,并将预写入发送到 TiKV 节点。

(3) 如果所有预写成功,SQL 引擎会向 PD 请求为事务的提交提供时间戳,并向 TiKV 发送提交命令。TiKV 提交主密钥并将成功响应发送回 SQL 引擎。

(5) SQL 引擎将成功返回给客户端。

(6) SQL 引擎提交辅助键,通过向 TiKV 发送进一步的提交命令,以异步和并行方式清除锁。

乐观事务和悲观事务的主要区别在于获取锁时。在乐观事务中,锁在预写入阶段(上面的步骤 3)以增量方式获取。在悲观事务中,锁是在预写前执行 DML 时获取的(步骤 2 的一部分)。这意味着,一旦预写开始,事务不会因为与另一个跨操作冲突而失败。(由于网络分区或其他问题,它仍可能失败。

在悲观事务中锁定键时,SQL 引擎将获取一个新的时间戳,称为for_update_ts。如果 SQL 引擎无法获取锁,它可以从该锁开始重试事务,而不是回滚和重试整个跨操作。读取数据时,TiKV for_update_ts而不是start_ts来决定可以读取键的哪些值。这样,悲观事务仍维持 RR 隔离级别,即使具有部分事务重应。

使用悲观事务,用户还可以选择仅要求读取提交 (RC) 隔离级别。这会导致事务之间的冲突减少,从而提高性能,而牺牲较少的孤立事务。实现区别在于,如果读取尝试访问被另一个事务锁定的密钥,则 RR TiKV 必须报告冲突;因此,如果读取内容尝试访问被另一事务锁定的密钥,则必须报告冲突。对于 RC,可以忽略锁以进行读取。

TiDB 实现分布式事务,无需集中式锁管理器。锁存储在 TiKV 中,具有很高的可扩展性和可用性。此外,SQL 引擎和 PD 服务器可扩展,可处理 OLTP 请求。跨服务器同时运行多个事务实现了高度并行性。

从 PD 请求时间戳。每个时间戳都包含物理时间和逻辑时间。物理时间是指具有毫秒精度的当前时间,逻辑时间需要 18 位。因此,从理论上讲,PD可以分配218次的每毫秒。实际上,它每天可以产生大约 100 万次的 tamps,因为分配时间戳只需几个周期。客户端要求一次时间戳,以摊销开销,尤其是网络延迟。目前,获取时间戳并不是我们实验和许多生产环境中的性能瓶颈。

5.2 Analytical Processing

在本节中,我们将介绍针对 OLAP 查询的优化,包括优化器、索引和推送我们定制的 SQL 引擎和 TiSpark 中的计算。

5.2.1 Query Optimization in SQL Engine

TiDB 实现了一个查询优化器,具有两个阶段的查询优化:基于规则的查询优化 (RBO),该优化生成逻辑计划,然后是基于成本的优化 (CBO),将逻辑计划转换为物理计划)。我们的 RBO 具有一组丰富的转换规则,包括裁剪不必要的列、消除投影、向下推谓词、派生谓词、不断折叠、消除"分组通过"或外部联接,以及取消取消子联接。我们的 CBO 根据执行成本从候选计划中选择最便宜的计划。请注意,TiDB 支持使用两个数据存储库,TiKV 和 TiFlash,因此扫描表通常有三个选项:扫描 TiKV 中的行格式表、在 TiKV 中使用索引扫描表以及在 TiFlash 中扫描列。

索引对于提高数据库中的查询性能非常重要,这些查询通常用于点获取或范围查询,为哈希联接和合并联接提供更便宜的数据扫描路径。TiDB 实现可扩展索引,以在分布式环境中工作。由于维护索引会消耗大量重新源,并且可能会影响联机事务和分析,因此我们在后台异步生成或删除索引。索引按区域按数据以相同的方式拆分,并存储于 TiKV 中的键值。唯一键索引上的索引项编码为:

Key:{table{tableID}index{indexID}indexedColValue}
Value:{rowID}
An index item on a nonunique index is decoded as:
Key:{table{tableID}index{indexID}indexedColValuerowID}
Value:{null}

使用索引需要二进制搜索来查找包含索引相关部分的区域。为了提高索引选择的稳定性,减少物理优化的开销,我们使用天际线修剪算法来消除无用的候选索引。如果有多个候选索引匹配不同的查询条件,我们将合并部分结果(即一组合格的行 ID)以获得精确的结果集。

物理计划(CBO 的结果)由 SQL 引擎层使用拉拔器模型 [17] 执行。通过将一些计算向下推送到存储层,可以进一步优化执行。在存储层中,执行计算的组件称为协处理器。协处理器并行在不同的服务器上执行执行计划的子树。这将减少必须从存储层发送到引擎层的元组数。例如,通过评估协处理器中的筛选器,在存储层中筛选出被拒绝的元组,并且只需要将接受的元组发送到引擎层。协处理器可以计算逻辑操作、算术运算和其他常见函数。在某些情况下,它可以执行聚合和 TopN。协处理器可以通过矢量化操作进一步提高性能:而不是遍数整个行,对行进行批处理,按列组织数据,从而实现更高效的迭代。

5.2.2 TiSpark

为了帮助 TiDB 连接到 Hadoop 生态系统,TiDB 在多 Raft 存储上添加了 TiSpark。除了 SQL 之外,TiSpark 还支持强大的计算,如机器学习库,并且可以处理来自 TiDB 外部的数据。

图 6 显示了 TiSpark 如何与 TiDB 集成。在 TiSpark 中,Spark 驱动程序从 TiKV 读取元数据以构造 Spark 目录,包括表架构和索引信息。Spark 驱动程序要求 PD 提供时间戳,以读取来自 TiKV 的 MVCC 数据,以确保它获得数据库的一致快照。与 SQL 引擎一样,Spark 驱动程序可以将计算推送到存储层上的共同处理器并使用可用的索引。这是通过修改 Spark 优化器生成的计划完成的。我们还自定义一些读取操作以从 TiKV 和 TiFlash 读取数据,并将它们组装成 Spark 工作人员的行。例如,TiSpark 可以同时从多个 TiDB 区域读取,它可以并行从存储层获取索引数据。为了减少对 Spark 特定版本的依赖,大多数这些函数都是在附加包中实现的。

TiSpark 在两个方面不同于普通连接器。它不仅可以同时读取多个数据区域,还可以并行从存储层获取索引数据。读取索引有助于 Spark 中的优化器选择最佳计划以重新降低执行成本。另一方面,TiSpark 修改了从 Spark 中的原始优化器生成的计划,将执行部分向下推送到存储层中的协处理器,从而进一步降低了执行开销。除了从存储层读取数据外,TiSpark 还支持在存储层中加载具有事务的大型数据。为此,TiSpark 采用两阶段提交和锁定表。

5.3 Isolation and Coordination

资源隔离是保证事务查询性能的有效方法。分析查询通常消耗大量资源,如 CPU、内存和 I/O 带宽。如果这些查询与事务查询一起运行,则后者可能会严重延迟。这一一般原则在以前的工作中已经得到验证[24,34]。为了避免在 TiDB 中出现此问题,我们在不同的引擎服务器上安排分析和事务查询,并在单独的服务器上部署 TiKV 和 TiFlash。事务查询主要访问TiKV,而分析查询主要访问TiFlash。通过 Raft 保持 TiKV 和 TiFlash 之间的数据一致性的开销较低,因此使用 TiFlash 运行分析查询对事务处理的性能影响很小。

数据在 TiKV 和 TiFlash 之间是一致的,因此可以通过从 TiKV 或 TiFlash 读取来提供查询。因此,我们的查询优化器可以从更大的物理计划空间进行选择,并且最佳计划可能同时从 TiKV 和 Ti-Flash 中读取。当 TiKV 访问表时,它提供行扫描和索引扫描,而 TiFlash 支持列扫描。

这三个访问路径的执行成本和数据顺序属性之间不同。行扫描和列扫描按主键提供顺序;索引扫描提供从键的编码中的多个排序。不同路径的成本取决于平均元组/列/索引大小(Stuple/col/index)和估计元数/区域数(Ntuple/reg)。我们表示数据扫描 asfscan 的 I/O 开销,以及寻求成本 asfseek 的文件。查询优化器根据方程 (1) 选择最佳访问路径。如公式 (2) 所示,行扫描的成本来自扫描连续行数据和寻找区域文件。列扫描的成本(公式 (3)))是扫描 m 列的总和。如果索引列不能满足表扫描所需的列,则索引扫描(公式 (4)) 应考虑扫描索引文件的成本和扫描数据文件的成本(即双重读取)。请注意,双读通常随机扫描元组,这涉及到在方程 (5) 中查找更多文件。

TiDB: A Raft-based HTAP Database_第8张图片
例如,当查询优化器将同时选择行格式和列格式存储以访问同一查询中的不同表时,请考虑"从 T.*,S.a 从 T.b_s.bwhere T.a 之间的 1 和 100"。这是一个典型的联接查询,其中 T 和 S 在行存储中的列 a 上具有索引,以及列副本。最好使用索引从行存储访问 T,从列存储访问 S。这是因为查询需要一系列来自 T 的完整元组,并且通过元组访问数据比列存储便宜。另一方面,使用列存储时获取两列完整的 S 更便宜。

TiDB: A Raft-based HTAP Database_第9张图片
TiKV 和 TiFlash 的协调仍然可以保证隔离性能。对于分析查询,只有小范围扫描或点获取扫描只能通过跟随读取访问 TiKV,这对领导者影响很小。我们还将 TiKV 上的默认访问表大小限制为最多 500 MB。事务查询可能会从 TiFlash 访问列数据,以检查某些约束,如唯一性。我们为特定表设置了多个列副本,一个表副本专用于事务查询。在单独的服务器上处理事务性查询可避免影响分析查询。

  1. EXPERIMENTS

在本节中,我们首先分别评估 TiDB 的 OLTP 和 OLAP 能力。对于 OLAP,我们调查 SQL 引擎选择 TiKV 和 T8lash 的能力,并将 TiSpark 与其他 OLAP 系统进行比较。然后,我们测量 TiDB 的 HTAP 性能,包括 TiKV 和 TiFlash 之间的日志复制延迟。最后,我们将 TiDB 与 MemSQL 进行比较。在隔离方面。

6.1 Experimental Setup

集群。我们在六台服务器集群上执行全面的实验;每个处理器具有 188 GB 内存和两个 IntelR© XeonR©CPU E5-2630 v4 处理器,即两个 NUMA 节点。每个处理器有 10 个物理内核(20 个线程)和一个 25 MB 的共享 L3cache。服务器运行 Centos 版本 7.6.1810,并且通过 10 Gbps 以太网网络连接。

工作量。我们的实验是在混合 OLTP 和 OLAP 工作负载下使用 CH-benCH 标记进行的。源代码在线发布 [7]。基准由标准 OLTP 和 OLAP 基准组成:TPC-C 和 TPC-H。它是根据 TPC-C 基准的未修改版本构建的。OLAP 部分包含 22 个分析查询,这些查询受 TPC-H 的启发,其架构从 TPC-H 调整为 CH-benCH 标记架构,外加三个缺失的 TPC-H 关系。在运行时,两个工作负载同时由多个客户端进行管理;在实验中,客户端的数量各不相同。吞吐量分别以查询/秒 (QPS) 或事务/秒 (TPS) 来衡量。CH-benCH 标记中的数据单位称为仓库,与 TPC-C 相同。100 个仓库需要大约 70 GB 的内存。

6.2 OLTP Performance

我们评估TiDB的独立OLTP性能,在CH-benCH标记的OLTP部分下乐观锁定或悲观锁定;即 TPC-C 基准。我们将 TiDB 的可见度与另一个分布式 NewSQL 数据库" Cockroach DB "CRDB 进行比较。CRDB 部署在六台同质服务器上。对于 TiDB,SQL 引擎和 TiKV 部署在六台服务器上,它们的实例分别绑定到每台服务器上的两个 NUMA 节点。PD 部署在六台服务器中的三台。为了平衡请求,通过 HAProxy 负载均衡器可以访问 TiDB 和 CRDB。我们使用各种数量的客户端测量 50、100 和 200 个仓库的吞吐量和平均延迟。

TiDB: A Raft-based HTAP Database_第10张图片

图 7(b)和图 7(c) 中的吞吐量图与图 7(a)不同。在图 7(a)中,对于少于 256 个客户端,TiDB 的吞吐量随乐观锁定和悲观锁定的客户端数而增加。对于超过 256 个客户端,具有乐观锁定的吞吐量保持稳定,然后开始下降,而悲观锁定的吞吐量达到其最大 512 个客户端,然后下降。图 7(b)和 7(c) 中的 TiDB 吞吐量不断增加。由于资源争用最激烈且并发性和小数据大小,因此预期会出现此结果。

通常,乐观锁定的性能优于悲观锁定,但较小的数据大小和高并发性(50 或 100 个仓库的 1024 个客户端),其中资源争用很重,导致重试许多乐观事务。由于资源争用较轻,有 200 个仓库,因此乐观锁定仍会产生更好的性能。

在大多数情况下,TiDB 的吞吐量高于 CRDB,尤其是在对大型仓库使用乐观锁定时。即使采用悲观锁定进行公平比较(CRDB 始终使用悲观锁定),TiDB 的性能仍然更高。我们相信TiBD的性能优势在于事务处理和 Raft 算法的优化。

图 7(d) 显示,更多的客户端会导致更多的延迟,尤其是在达到最大吞吐量之后,因为更多的请求必须等待更长的时间。这也意味着延迟越高,仓库越少。对于某些客户端,较高的通投入可减少 TiDB 和 CRDB 的延迟。50 个和 100 个仓库也存在类似的结果。

我们评估从 PD 请求时间戳的性能,因为这可能是一个潜在的瓶颈。我们使用 1200 个客户端持续请求时间戳。客户端位于群集中的不同服务器上。模拟 TiDB,每个客户端分批向 PD 发送时间戳请求。如表 3 所示,六台服务器中每个服务器每台可接收 602594 次时间戳/秒,这是运行 TPC-Cbench 标记时所需速率的 100 倍以上。运行 TPC-C 时,TiDB 请求每台服务器最多为 6000 次。增加服务器数量时,每台服务器上接收的时间戳数将减少,但时间戳总数几乎相同。这个比率大大超过任何实际需求。关于延迟,只有一小部分请求花费 1 ms 或 2 ms。我们的结论是,从 PD 获取时间戳目前不是 TiDB 的性能瓶颈。

TiDB: A Raft-based HTAP Database_第11张图片
6.3 OLAP Performance
我们评估从 PD 请求时间戳的性能,因为这可能是一个潜在的瓶颈。我们使用 1200 个客户端持续请求时间戳。客户端位于群集中的不同服务器上。模拟 TiDB,每个客户端分批向 PD 发送时间戳请求。如表 3 所示,六台服务器中每个服务器每台可接收 602594 次时间戳/秒,这是运行 TPC-Cbench 标记时所需速率的 100 倍以上。运行 TPC-C 时,TiDB 请求每台服务器最多为 6000 次。增加服务器数量时,每台服务器上接收的时间戳数将减少,但时间戳总数几乎相同。这个比率大大超过任何实际需求。关于延迟,只有一小部分请求花费 1 ms 或 2 ms。我们的结论是,从 PD 获取时间戳目前不是 TiDB 的性能瓶颈。

Q8, Q12,和Q22会产生有趣的结果。与 Q8 和 Q22 中仅 TiFlash 案例的时间成本要长,但Q22需要花费更多时间。TiKV 和 TiFlash 案例的性能优于仅 TiKV 和仅 TiFlash 案例。

Q12 主要包含两个表联接,但在每个存储类型中需要不同的物理实现。在仅 TiKV 的情况下,它使用索引联接,该联接从表 ORDERLINE 扫描多个合格的元数,并使用索引查找表 OORDER。索引读取器的成本低得多,因此它优于在 TiFlash 只例例中使用哈希联接,该案例扫描两个表中所需的列。使用TiKV和TiFlash时的成本进一步降低,因为它使用从TiFlash扫描ORDERLINE的更便宜的索引联接,并使用TiKV中的索引搜索OORDER。在 TiKV 和 TiFlash 案例中,读取列存储可将仅 TiKV 案例的执行时间减少一半。

在 Q22 中,其存在() 子查询转换为反半联接。它在仅 TiKV 大小写例中使用索引联接,在仅 TiFlash 的情况下使用哈希联接。但与 Q12 中的执行不同,使用索引联接比哈希联接更昂贵。从 TiFlash 获取内部表并使用 TiKV 的索引查找外部表时,索引联接的成本会降低。因此,TiKV 和 TiFlash 案例再次占用的时间最少。

Q8 更为复杂。它包含一个包含九个表的联接。在仅 TiKV 的情况下,需要两个索引合并联接和六个哈希联接,并使用索引查询两个表(CUSTOMER 和 OORDER)。此计划需要 1.13 秒,优于在 TiFlash 只例中采用 8 个哈希联接,该案例需要 1.64 秒。在 TiKV 和 TiFlash 案例中,其过头性进一步减少,其中物理计划几乎保持不变,除了在六个哈希联接中扫描来自 TiFlash 的数据。此改进将执行时间缩短到 0.55 秒。在三个查询中,仅使用 TiKV 或 Ti-Flash 获得不同的性能,并组合它们可获得最佳结果。

对于Q1, Q4, Q6, Q11, Q13, Q14,和 Q19,仅 TiFlash 案例的性能优于仅 TiKV 案例,而 TiKV 和 TiFlash 案例的性能与仅 TiFlash 案例的性能相同。这七个查询的原因不同。Q1 和 Q6 主要是单个表上的聚合组合,因此在 TiFlash 中的列存储上运行成本更低,是最佳选择。这些结果突出了先前工作中描述的柱存储的优点。Q4 和 Q11 在每种情况下都使用相同的物理计划单独执行。但是,从 TiFlash 扫描数据比 TiKV 便宜,因此仅 TiFlash 情况下的执行时间更少,也是最佳选择。Q13、Q14 和 Q19 都包含一个双表联接,该联接作为哈希联接实现。尽管仅 TiKV 大小写在探测哈希表时采用索引读取器,但它也比从 TiFlash 扫描数据更昂贵。

Q9 是多联接查询。在仅 TiKV 的情况下,它使用索引对某些表采用索引合并联接。它比在 TiFlash 上执行哈希联接便宜,因此它成为最佳选择。Q7、Q20 和 Q21 会产生类似的结果,但由于空间有限,结果被消除。其余 8 个 22 个 TPC-H 查询在三个存储设置中具有可比的性能。

此外,我们使用 500 个仓库的 CH-benCH 标记的 22 个分析查询将 TiSpark 与 SparkSQL、PrestoDB 和 Greenplum 进行比较。每个数据库都安装在六台服务器上。对于 SparkSQL 和 PrestoDB,数据存储为 Hive 中的柱形镶木地板文件。图 9 比较了这些系统的性能。TiSpark 的性能可与 SparkSQL 媲美,因为它们使用相同的引擎。性能差距相当小,主要来自访问不同的存储系统:扫描压缩镶木地板文件更便宜,因此 SparkSQL 通常优于 TiSpark。但是,在某些情况下,TiSpark 可以将更多计算推送到存储层,但这种优势是抵消的。将 TiSpark 与 PrestoDB 和 Greenplum 进行比较是 SparkSQL(TiSpark 的基础引擎)与其他两个引擎的比较。但是,这超出了本文的范围,我们不会详细讨论。

6.4 HTAP Performance
除了调查事务处理 (TP) 和分析处理 (AP) 性能,我们还根据整个 CH-benCHmark 以及单独的事务客户端 (TC) 和分析客户端 (AC) 评估 TiDB 的混合工作负载。这些实验是在100个仓库进行的。数据加载到 TiKV 中,并同时复制到 TiFlash 中。TiKV 部署在三台服务器上,由 TiDB SQL 引擎实例访问。TiFlash 部署在其他三台服务器上,并与其他 TiSpark 实例并合。此配置分别提供分析和事务查询。每次跑步时间为 10 分钟,热身时间为 3 分钟。我们测量了 TP 和 AP 工作负载的吞吐量和平均延迟。

图 10(a) 和 10(b) 显示了具有不同数量的 TP 客户端和 AP 客户端的事务的吞吐量和平均延迟(分别)。吞吐量随着 TP 客户端的增加而增加,但达到最大值时略低于 512 个客户端。对于相同数量的 TP 客户端,与没有 AP 客户端相比,更多的分析处理客户端最多会降低 TP 吞吐量 10%。这证实了 TiKV 和 TiFlash 之间的日志复制实现了高隔离,尤其是与第 6.6 节中的 MemSQL 性能相反。此结果与 [24] 中的结果类似。

TiDB: A Raft-based HTAP Database_第12张图片

事务的平均延迟在没有上限的情况下增加。这是因为即使更多的客户端发出更多的重任务,它们也不能立即完成,必须等待。等待时间是延迟增加的一部分。

图 10(c)和 10(d) 所示的类似吞吐量和延迟结果显示了 TP 对 AP 请求的影响。AP 吞吐量很快达到 16 个 AP 客户端以下的最大容量,原因 AP 查询成本高昂,并且争用资源。此类争用会降低使用更多 AP 客户端的吞吐量。对于相同数量的 AP 客户端,吞吐量几乎保持不变,最多只能减少 5%。这表明 TP 不会显著影响 AP 执行。分析查询的平均延迟增加会导致与更多客户端的等待时间增加。

6.5 Log Replication Delay

为了实现实时分析处理,TiFlash 应立即看到事务更新。此类数据新鲜度由 TiKV 和 TiFlash 之间的日志复制延迟决定。我们使用不同数量的事务客户端和分析客户端运行 CH-ben CHmark 时测量日志复制时间。我们记录运行 CH-benCHmark 10 分钟内每个复制的延迟,并计算每 10 秒的平均延迟。我们还计算日志复制延迟在 10 分钟内的分布,如表 4 所示。

如图 11(a)所示,10 个仓库的日志复制延迟始终小于 300 ms,并且大多数延迟小于 100 毫秒。图11(b)显示,延迟增加与100个软件屋;大多数小于 10ms 。表4提供了更精确的细节。在 10 个仓库中,无论客户端设置如何,几乎 99% 的查询成本都低于 500 ms。在 100 个仓库中,分别有 2 个和 32 个分析客户端的查询量低于 99% 和 85%。这些指标强调 TiDB 可以保证 HTAP 工作负载的数据新鲜度约为 1 秒。

在比较图 11(a) 和图 11(b) 时,我们观察到延迟时间与数据大小有关。仓库越大,延迟越大,因为更多的数据会引入更多的日志进行同步。此外,延迟还取决于分析请求的数量,但由于事务客户端的数量,因此损失较少。这可以在图 11(b)中清楚地看到。32 个 AC 比两个 AC 导致更多的延迟。但是,对于相同数量的分析客户端,延迟没有很大差异。我们在表 4 中显示更精确的结果。拥有 100 个仓库和两个 AC,超过 80% 的查询需要小于 100 ms,但 32 个 AC 少于 50% 的查询需要不到 100 ms。这是因为更多的分析查询会以更高的频率诱导日志复制。

6.6 Comparison to MemSQL

我们使用 CH-benC -Hmark 将 TiDB 与 MemSQL 7.0 进行比较。本实验旨在强调最先进的HTAP系统的隔离问题,而不是OLTP和OLAP性能。MemSQL 是一个分布式关系数据库,可大规模处理事务和实时分析。MemSQL 部署在六台服务器上:一台主服务器、一台聚合器和四台服务器。我们将 100 个仓库加载到 MemSQL 中,并运行了各种数量的 AP 和 TP 客户端的基准。基准运行10分钟,5分钟的热身期。

与图 10 相比,图 12 说明了工作负载干扰对 MemSQL 的性能有显著影响。特别是,随着 AP 客户端数量的增加,事务吞吐量显著减慢,下降五倍以上。AP 吞吐量也会随着 TP 客户端的增加而降低,但这种效果没有标记,因为事务查询不会重新询问分析查询的大量资源。

  1. RELATED WORK

构建 HTAP 系统的常见方法包括:从现有数据库演变、扩展开源分析系统或从头开始构建。TiDB 是从头开始构建的,在体系结构、数据发起、计算引擎和一致性保证方面与其他系统不同。

从现有数据库演变。成熟的数据库可以基于现有产品支持 HTAP 解决方案,并且它们尤其侧重于加速分析查询。他们采用自定义方法,分别实现数据一致性和高可用性。相比之下,TiDB 自然会从 Raft 中的日志复制中获得好处,以实现数据一致性和高可用性。

Oracle [19] 于 2014 年推出了数据库内存选项,成为业界首款双格式内存 RDBMS。此选项旨在打破分析查询工作负荷中的性能障碍,同时不影响(甚至改善)常规事务工作负载的性能。列存储是只读快照,在时间点一致,并使用完全联机的重新填充机制进行更新。Oracle 的后期工作 [27] 介绍了其分布式体系结构的高可用性方面,并提供容错分析查询执行。

SQL Server [21] 将两个专用存储引擎集成到其核心中:用于分析工作负载的阿波罗列存储引擎和用于事务性工作负载的 Hekaton 内存引擎。数据迁移任务定期将数据从 Hekaton 表的尾部复制到压缩列存储区。SQL Server 使用列存储索引和批处理来高效地处理分析查询,使用 SIMD [15] 进行数据扫描。

SAP HANA 支持高效评估单独的 OLAP 和 OLTP 查询,并为每个查询使用不同的数据组织。为了扩展 OLAP 性能,它异步地将行存储数据复制到分布在服务器群集上的列存储 [22]。此方法提供具有子秒可见性的 MVCC 数据。但是,处理错误和保持数据一致性需要付出大量努力。重要的是,事务引擎缺乏高可用性,因为它只部署在单个节点上。

转换开源系统。Apache Spark 是用于数据分析的开源框架。它需要一个事务模块来实现HTAP。下面列出的许多系统都遵循此理念。TiDB 并不深深依赖于 Spark, 因为 Tispark 是一种前紧张。TiDB 是一个独立的 HTAP 数据库,没有 TiSpark。

Wild-fire [10, 9] 基于 Spark 构建 HTAP 引擎。它处理同一列数据组织(即 Parquet)上的分析和事务请求。它采用最后写入获胜语义进行并发更新,对读取采用快照隔离。对于高可用性,分片日志将复制到多个节点,而无需共识算法的帮助。分析查询和事务查询可以在单独的节点上处理;但是,在处理最新更新时存在明显的延迟。对于大型 HTAP 工作负载,Wild-fire 使用统一的多版本和多区域索引方法 [23]。

SnappyData [25] 为 OLTP、OLAP 和流分析提供了一个统一的平台。它集成了用于高吞吐量分析 (Spark) 的计算引擎和扩展内存事务存储 (GemFire)。最近的更新以行格式存储,然后老化为分析查询的列格式。使用 GemFire 的 Paxos 实现遵循 2PC 协议,以确保在整个群集中达成共识和一致的观点。

从头开始构建。许多新的 HTAP 系统已经调查了混合工作负载的不同方面,包括利用内存计算来提高性能、优化数据存储和可用性。与 TiDB 不同,它们不能同时提供高可用性、数据一致性、可扩展性、数据新鲜度和隔离性。

MemSQL [3] 具有可扩展的内存 OLTP 和快速分析查询的引擎。MemSQL 可以以行或列格式存储数据库表。它可以以行格式保留部分数据,并将其转换为列格式,以在将数据写入磁盘时进行快速分析。它将重复查询编译为低级 ma-chine 代码以加速分析查询,并使用许多无锁结构来帮助事务处理。但是,在运行 HTAP 工作负载时,它不能为 OLAP 和 OLTP 提供隔离性能。

HyPer [18] 使用操作系统的分叉系统调用来为分析工作负载进行亲 vide 快照隔离。其较新版本采用 MVCC 实现,提供可序列化、快速的跨动作处理和快速扫描。ScyPer [26] 扩展 HyPer,通过使用逻辑或物理重做日志传播更新,在远程副本上大规模评估分析查询。

BatchDB [24] 是专为 HTAP 工作负载设计的内存数据库引擎。它依赖于具有专用副本的初级辅助复制,每个副本针对特定工作负载类型(即 OLTP 或 OLAP)进行了优化。它最大限度地减少了事务引擎和分析引擎之间的负载交互,从而在 HTAP 工作负载的严格 SLA 下,能够对全新数据进行实时分析。请注意,它在行格式副本上执行分析查询,并且不保证高可用性。

基于血统的数据存储 (L-Store) [35] 通过引入基于更新的、基于血统的存储体系结构,将实时分析和事务查询处理结合在一个统一引擎中。该存储在本机多版本列存储模型上启用无争用更新机制,以便从写入优化的列格式到读取优化的列式布局中,以平稳和独立地将稳定数据阶段为读取优化的列式布局。

Peloton [31] 是一个自动驾驶的 SQL 数据库管理系统。它尝试在运行时为 HTAP 工作负载调整数据发起 [8]。它使用无锁的多版本并发控制支持端口实时分析。但是,它是由设计的单节点内存数据库。

Cockroach DB [38] 是一个分布式 SQL 数据库,提供高可用性、数据一致性、可扩展性和隔离性。与 TiDB 一样,它建立在 Raft 算法之上,支持分布式事务。它提供了更强的隔离属性:可序列化,而不是快照隔离。但是,它不支持专用 OLAP 或 HTAP 功能。

  1. CONCLUSION

我们提出了一个生产就绪的 HTAP 数据库:TiDB。TiDB 构建在 TiKV 之上,TiKV 是一个分布式、基于行的存储,它使用 Raft 算法。我们引入列学员进行实时分析,这些分析异步地从 TiKV 复制日志,并将行格式数据转换为列格式。TiKV 和 TiFlash 之间的此类日志副本提供实时数据一致性,且开销很小。TiKV 和 TiFlash 可以部署在 sepa 速率的物理资源上,以便高效处理事务和分析查询。TiDB 可以最好地选择它们,以在扫描事务查询和分析查询的表时访问它们。实验结果表明,TiDB在HTAP基准CH-benCHmark下表现良好。TiDB 提供了一个通用解决方案,将 NewSQL 系统发展为 HTAP 系统。

你可能感兴趣的:(数据库)