分布式数据库 PolarDB-X: An Elastic Distributed Relational Database for Cloud-Native Applications

背景

PolarDB-X是阿里云开发的一款shard nothing的分布式数据库,其论文发表在2022年数据库顶会ICDE,论文题目是:PolarDB-X: 一种弹性的适用于云原生应用程序的分布式关系型数据库,周末抽空读了下,做一些总结和记录。

摘要

云计算正在蓬勃发展,这促使新一代数据库系统需要适应云环境。云原生数据库的发展揭示了三个趋势。
第一个趋势是采用多数据中心部署,以便在任何单个站点宕机时仍能正常运行。(可用性和服务连续性要求)
第二个趋势是将计算和存储资源分离,即存算分离,以实现更高的弹性和可伸缩性。
最后一个趋势,是支持 HTAP,以消除异构数据库中的数据冗余和系统复杂性。

为满足这些趋势,本文设计了名为 PolarDB-X 的分布式关系型数据库,该数据库建立在云原生数据库 PolarDB 之上。因此,它自然的继承了许多云原生特性,例如多数据中心部署和弹性。在跨数据中心的部署下,它利用 Paxos算法和混合逻辑时钟实现了低协调成本的持久性和快照隔离一致性。为实现资源弹性,由于底层 PolarDB 支持将租户快速迁移至节点之间,PolarDB-X 可以快速扩展集群以应对突发的流量增加。为了支持 HTAP,借助读副本和 HTAP 执行器,PolarDB-X 可以提高分析查询的延迟和并行性,而不影响同时运行的 TP 工作负载。使用其 MPP 引擎和内存列索引,还可以进一步提高分析查询的效率。

INTRODUCTION

为了满足对支持更大数据量和更高事务吞吐量的需求不断增加,企业应用程序倾向于采用shared-nothing架构的数据库,该数据库可以将数据分片分布在许多数据存储中,例如基于 KV 底层存储构建的事务系统(比如Tidb/Cockroachdb/Spanner)或类似 MySQL 和 PostgreSQL的关系型数据库。在这些数据库架构中,跨分片支持完全分布式 ACID 事务对于简化分布式系统的构建和推理以及提高应用程序的敏捷性至关重要。同时,随着云计算的普及,越来越多的企业应用程序和线下数据库正在迁移到云上。

在云上运行分布式关系型数据库服务面临三个挑战。

  1. 性能的挑战。首先,在迁移到云时,企业将遵循现成的多数据中心部署最佳实践。为了服务的可用性,典型的方法是在不同的数据中心分散数据库副本,使用共识算法协议(称为quorum ,例如Paxos 、Raft 算法)在副本之间同步修改,以防止数据丢失,并在单个数据中心故障时保证服务的连续性。然而,不同数据库分片的Leader节点可能位于不同的数据中心。当一个分布式事务涉及多个数据库分片(例如更新主索引和/或全局二级索引)时,跨数据中心的两阶段提交协议(2PC)和共识协调的成本可能是禁止的,并且由于潜在的高网络延迟,性能可能受损。因此,减轻跨数据中心网络延迟的影响,降低跨数据中心分布式事务的执行代价是设计云原生环境下分布式数据库的关键考虑因素。

  2. 第二,可扩展性。大规模的互联网应用程序,如电子商务和社交网络,在业务高峰期往往会出现十倍甚至百倍的流量增长,例如阿里巴巴的双11。阿里巴巴数据库在2018年11月11日午夜的总事务吞吐量,该事务吞吐量突然增加,达到了每秒5,680,000个事务,比前一秒的速度增加了45%。为了应对这种高峰期,分布式数据库服务必须能够快速响应并自动扩展以满足更高的负载需求。在传统的shared nothing架构中,扩展集群和添加节点等操作需要在服务器之间迁移数据,这些操作本质上是繁琐且耗时的。因此,企业通常必须在高峰到来前几周甚至几个月就要扩展他们的数据库系统,导致资源浪费。传统的shared-nothing数据库无法有效地解决这些应用对快速资源弹性的需求,这为云原生分布式数据库带来了新的机遇。

  3. 第三个挑战,根据“one size doesnot fit all”的原则(即很少有一个系统能够满足所有需求,比如TP交易和AP分析),企业通常使用分离的数据库系统处理OLTP和OLAP工作负载。客户依赖ETL工具从OLTP系统导入数据到OLAP系统,但这增加了数据存储的冗余和系统维护的复杂性。如果在单个数据库系统中同时处理事务和分析工作负载将是理想的,但资源隔离成为主要的挑战。运行分析查询时,由于解决读写冲突的同时还有CPU、内存和网络带宽等系统资源的争用,事务吞吐量通常会受到不利影响。对于云原生分布式数据库,有机会利用弹性资源同时处理OLTP和OLAP工作负载,以便及时生成BI报告,而不会影响来自前端应用程序的事务。

为了解决这些挑战,本文介绍了阿里云的分布式关系型数据库PolarDB-X。它建立在云原生共享存储数据库PolarDB之上,继承了许多云原生设计,另外还采用了一种新颖的CN-DN-SN(计算-数据库-存储节点)三层架构。主要的设计目标是满足前述的三个需求,即优化跨数据中心的分布式事务,为云应用提供快速弹性和高效的混合OLTP和OLAP工作负载处理。
关键点如下:

跨数据中心事务(分布式事务)

PolarDB-X 使用基于混合逻辑时钟的 HLC-SI 来实现跨分片数据一致性。由于 HLC-SI 符合快照隔离(SI)的要求,将其与其他 SI 实现(如集中式时间戳排序 TSO 进行比较。具体而言,跨数据中心访问 TSO 的网络延迟会增加事务延迟并且严重影响吞吐量。实验表明,在三个数据中心的部署中,HLC-SI 优于 TSO,并且其峰值写入吞吐量比 TSO 高19%。

弹性

主要云厂商的云原生关系型数据库都使用计算和存储分离架构,这使得弹性具有显著优势,即在几分钟内添加 RO(只读)节点来扩展读吞吐量。此外,阿里巴巴的 PolarDB 支持多租户(PolarDB-MT),其中每个租户表示一组schema/数据库或表。 PolarDB-MT 允许通过将不同的租户绑定到多个 RW(读写)节点并在这些节点之间分配写请求来扩展写入。通过利用这些功能,PolarDB-X集群可以快速扩展。评估显示,对于1.6亿行和40 GB数据量,PolarDB-X可以在4-5秒内将其大小增加一倍,这比传统的shared-nothing架构广泛使用的传统数据传输方法快了一百多倍。

HTAP

PolarDB-X的优化器可以根据查询的预计成本确定查询是否属于TP或AP工作负载。 AP查询将被隔离并在单独的线程池中执行,并且正在执行的查询可以被抢占以防止其他查询饿死。当查询使用的CPU和网络资源达到其配额时,它将被放入等待队列以进行重新调度。由于PolarDB可以快速添加只读副本而无需复制数据,因此可以在新扩展的RO节点上完全与RW节点中的TP工作负载隔离地运行分析查询。在混合运行TPC-C和TPC-H基准测试的实验中,启用HTAP模式后,TPC-C的吞吐量变得非常稳定,并且几乎不受并发TPC-H测试的影响。平均TPC-H查询延迟缩短了2.7、5.0和5.7倍,相应地添加了1到3个额外的只读副本。使用MPP执行引擎和PolarDB的内存列索引,性能提升明显。

将PolarDB-X作为面向企业客户的云服务运营,并且从中积累了经验,加入了一系列方便DBA和开发人员使用的功能,例如反热点技术、自动流量控制和索引建议。

系统概述

架构

PolarDB-X 是一个与 MySQL 兼容的分布式数据库。如图 2 所示,PolarDB-X 由以下组件构成:全局元数据服务(Global Meta Service,GMS)、负载均衡组件、计算节点(Computation Node,CN)、数据库节点(Database Node,DN)和存储节点(Storage Node,SN)。通过将关系型数据库内核拆分为三个层次(CN-DN-SN),该架构实现了出色的弹性和可伸缩性:CN 处理分布式事务和查询;DN 解决单个分片事务和跨数据中心复制;SN 在单个数据中心中持久化数据;每个层次可以独立扩展。
分布式数据库 PolarDB-X: An Elastic Distributed Relational Database for Cloud-Native Applications_第1张图片

GMS

GMS 是 PolarDB-X 的控制平面。它管理系统的元数据,例如集群成员目录表、表/索引分区规则、分片位置和统计信息。它还维护系统表,例如序列、存储的 SQL 计划基线、帐户和权限。此外,一些后台任务在 GMS 上运行。例如,它根据负载安排数据重新分布,并管理节点注册和故障转移。在生产环境中将 3AZ PolarDB 部署为 GMS。

负载均衡器

负载均衡器是云网络基础设施。它为每个 PolarDB-X 实例公开单个入口点(即虚拟 IP)以供 SQL 客户端使用。它具有位置感知性,倾向于将客户端连接分散到附近的后端(例如,同一数据中心中部署的 CN 服务器)。例如,在图 2 中,DC1 的负载均衡器将首先将连接重定向到 CN 服务器 A 和 B。仅当它们不可用时,才会将请求路由到 DC2 和 DC3 中的 CN 服务器。

CN-计算节点

CN始终部署在与底层的DN(PolarDB)和SN(PolarFS)相同的数据中心中。表被划分为分片并存储在DN中。每个分片通过Paxos协议进行至少三个DN的复制,并具有领导lease。每个分片的领导者可能位于不同的数据中心。CN将一个分片的写请求转发到其领导所在的数据中心,并将读请求转发到本地数据中心(当它有足够新的快照时)以消除跨数据中心的网络延迟。

CN包含以下组件:分布式事务协调器、基于成本的优化器和查询执行器(具有TP和AP流量隔离、用于AP查询的MPP执行器等功能)。客户端建立与CN服务器的连接并向其发送SQL查询。CN服务器解析SQL语句,分析数据位置并充当事务协调器。对于分布式事务,它首先启动事务并将语句转发到相应的DN服务器。然后,它合并结果,提交事务并将最终结果返回给客户端。由于CN服务器是无状态的,它们可以快速扩展以同时处理更多的事务和查询。

DN/SN(Database Node and Storage Node)

PolarDB-X中的Database Node(DN)和Storage Node(SN)与PolarDB中的相应组件实现相同,PolarDB是一种具有shared storage架构的云原生数据库。 DN相当于PolarDB中的数据库节点(RW和RO节点),而SN对应于PolarFS中的块服务器。对于基本的PolarDB,一个实例包括一个主节点(即RW节点)和多个只读副本(即RO节点)。 RO副本可以水平扩展,以提高处理分析查询的能力,而对事务工作量影响较小。后续章节会介绍PolarDB-MT,这是PolarDB的一种变体,其中一个实例可以具有多个RW节点(支持多写,而不是单写),以提高聚合写入吞吐量。像传统的数据库内核一样,RW / RO节点包含SQL处理器、事务引擎(如InnoDB )和缓冲池,用于提供本地事务和查询。

PolarFS

所有数据都持久存储在PolarFS 中。PolarFS是一个持久、原子性和水平可扩展的分布式存储服务。它提供虚拟卷,这些卷被分成10GB大小的块,分布在多个存储节点中。每个DN都有一个卷,而PolarDB-X可以有多个卷。每个卷最多包含10K个块,可提供最大容量为100TB。块是按需分配的,因此卷空间会动态增长。每个块在每个数据中心都有三个副本,并通过来自Raft的共识协议的Parallel Raft保证线性可串行化。

整个系统的吞吐量可以通过添加更多的CN和DN服务器来扩展。由于CN没有持久状态,因此不涉及数据移动。对于DN,PolarDB通过添加RO节点将读取吞吐量几乎线性增加,而PolarDB-MT允许使用多个RW节点来扩展写入吞吐量。上述任何操作都不会移动数据。因此,与始终需要复制数据的传统share nothing数据库相比,扩展PolarDB-X集群的速度要快得多。扩展存储容量和I / O吞吐量的任务被解耦到SN,对上层透明,并可以通过添加更多的SN节点来实现。

数据分区方式(Data Partition)

PolarDB-X使用基于主键的哈希分区将表/索引切片为分片。如果表没有指定主键,则会添加一个隐藏主键,该主键是自增的BIGINT类型,对用户不可见。哈希分区的优点是可以将数据均匀地分布在分片中,降低热点的出现几率。例如,当主键是自增整数(或递增的时间戳)并使用范围分区时,批量数据插入会导致大部分写操作集中在最后一个分片上,使其成为热点。
与Google F1系统类似,PolarDB-X支持本地和全局索引。本地索引也通过分区键进行分区,以便更新索引时不会导致分布式事务。全局索引通过索引列进行分区,并存储为隐藏表,支持聚集索引和非聚集索引。

更新记录时,为了保证 ACID,PolarDB-X 会在单个分布式事务中更新主键索引和相关的次要索引。聚集索引可以减少跨分片的数据检索次数。例如,在查询从全局次要索引检索出的一组主键之后,需要从不同分片上的主索引中读取相应的行。使用聚集索引可以高效地从索引中读取所有所需列,避免散乱的读取。

一组表可以具有相同的分区键。针对这些表的查询通常会在分区键上指定一个值,无论是明确地(通过“等于”条件)还是隐式地(在分区键上进行等值连接)。在 PolarDB-X 中,这些表可以声明为一个表组,并遵循完全相同的分区规则和分片放置策略。在表组中,来自同一分区的一组表分片被定义为一个分区组。分区组中的分片总是位于同一个 DN 上。在数据重新分配期间,分区组中的所有表片都将作为一个整体进行重新分片(分割或合并)或迁移。表组有助于减少由于存在远程数据而引入的延迟。例如,在表组中,基于分区键的等值连接可以通过执行分区连接进行优化,无需读取远程数据或在分片之间重新分配数据。Google Spanner 和 F1 系统 引入了一个名为分层模式的新数据模型,用于解决类似问题。相反,PolarDB-X 选择坚持使用与 MySQL 兼容的模式,并添加表组作为语法扩展。

PolarDB

为了简单和清晰起见,仅描述在单个数据中心中部署的olarDB设置。下面展示了如何将对RW节点的写操作同步到RO节点,从而提供快照读取和会话一致性。RW节点和RO节点通过redo log同步内存状态。

分布式数据库 PolarDB-X: An Elastic Distributed Relational Database for Cloud-Native Applications_第2张图片
它们通过日志序列号(LSN)进行保证一致性,该序列号指示InnoDB中重做redo log的偏移量。

  1. 在一个事务中,当RW将所有redo log记录刷新到PolarFS中后,该事务就可以提交了。(对应图上1、2、3)
  2. RW异步地向所有RO节点广播消息,表示redo log已更新以及最新的LSN lsnRW。(对应图上4)
  3. ROi节点收到来自RW的消息后,从PolarFS中拉取redo log的更新,并将其应用于缓冲池中的缓冲页面,以便ROi与RW保持同步。(对应图上5、6)
  4. 然后,ROi将已经消费到的Redo log偏移量lsnROi打包在RPC回复中发送回RW。(对应图上7)
  5. RW可以基于维护的min(lsnROig)位置之前清除redo log,并在后台刷新早于min(lsnROig)的脏页。(对应图上8,因为最小的日志点,可以回收)
  6. ROi可以使用版本lsnROi的快照为读事务提供服务。(对应图上9)

由于CPU利用率高或网络拥塞等原因,一些RO节点可能会落后。假设有一个名为ROk的节点,其LSN lsnROk比RW的lsnRW低得多(假设滞后大于一百万),那么ROk将被检测到并从集群中删除,以避免减慢RW刷新脏页的速度。RO节点的最新快照版本通常会落后于RW的版本数几毫秒。对于需要跨RW和RO节点实现会话一致性的客户端,CN跟踪RW的最新时间戳LSNRW,并在将读取请求转发给RO节点时携带该时间戳。RO节点将等待其快照版本号不小于LSNRW,然后才处理并响应查询。通过这种方式,RO副本可以横向扩展以提高系统的读取吞吐量,而对RW节点的性能影响很小,而不会增加存储成本和承担数据复制开销

数据复制

为了支持PolarDB-X的多数据中心部署,PolarDB-X使用带有领导者租约的Paxos增强了PolarDB(作为DN)。通过使用异步提交、批处理和流水线等优化技术,有效地在多个数据中心之间同步更改。它包含以下角色:

Leader领导者:所有写操作都在此完成。
Follower跟随者:它从领导者接收redo log记录并回放redo log。如果原始领导者失败,跟随者可以被选为新领导者。
Logger:它是一个特殊的跟随者节点,只记录redo log记录,没有数据,即不能提供数据库服务。它可以参与领导者选举,但不能被选为领导者。

与AWS Aurora不同的是,为了实现极低的存储I/O延迟(通过RDMA),PolarDB-X的跨数据中心数据复制不是在SN层实现的,而是在DN层实现的。PolarDB-X内的PolarDB实例在数据中心之间传输redo log。RO节点可以在领导者和跟随者实例上创建。

上述具体实现过程:

  1. Leader负责执行事务。在事务提交之前,redo log条目被刷新到PolarFS,并通过Paxos发送到Follower;
  2. 一旦Follower接收到日志条目,它将它们复制到日志缓冲区并将它们写入日志文件。在PolarFS中持久化日志后,Follower将向leader确认。当大多数节点保存了更改后,领导者将推进DLSN信息(持久的LSN)。在发生数据中心灾难时,DLSN之前的日志条目不会丢失。因此,Leader可以安全地将DLSN之前修改的脏页从缓冲池刷新到PolarFS,并在下一次传输中通知FollowerDLSN已经提前。
  3. 在接收到来自Leader的最新DLSN之后,Follower将应用其之前的所有的redo log条目。不能应用DLSN之后的更改,因为如果单个数据中心失败并重新选举Leader,则新Leader可能会截断DLSN之后的重做日志条目。当Follower从崩溃中恢复时,它必须确保不会应用任何比DLSN更早(大)的redo log。

异步事务提交

事务提交是由DLSN信息的推进而驱动的。一个事务被分成多个小事务(MTR),它们会写一组连续的redo log。当DLSN超过事务的最后一个MTR的最大LSN时,该事务被视为已提交。然后Leader将成功的提交语句返回给其SQL客户端。

需要注意的是,多个数据中心中redo log的持久性需要往返通信。如果前台线程被阻塞等待Follower的ACK消息,Leader可能会有大量线程阻塞在等待状态,这将严重影响事务吞吐量。为解决此问题,采用异步事务提交。具体来说,在前台线程通过Paxos算法将redo log发送给跟随者后,它将事务的上下文存储在一个map数据结构中,然后继续处理其他事务。新增了一个async_log_committer线程来监视DLSN的变化。当Follower返回确认并推进DLSN时,async_log_committer会遍历map,找到最后一个MTR的LSN超过DLSN的事务列表。它恢复它们的上下文,提交它们并将结果返回给客户端。

流水线和批处理

异步提交依赖于Paxos来支持redo log的流水线传输。Leader不断地将redo log发送给Follower,它可以发送新的日志批次,而无需等待前一个批次的确认。当传输延迟远低于传播延迟(例如,在高延迟和高吞吐量的网络环境中),流水线可以有效地提高吞吐量。通过Paxos同步数据需要额外的控制信息。为了将Paxos集成到redo log中,添加了一个特殊的redo log类型,称为MLOG_PAXOS,以批量方式管理Paxos元数据。此日志为64字节,包含元数据,例如epoch、索引、redo log日志的LSN范围和校验和。由于每个MTR可能仅仅包含少量更改(最多几百字节),为每个MTR添加MLOG_PAXOS redo log日志是昂贵的。因此,多个MTR在单个MLOG_PAXOS(最大16KB)中进行批处理,以扩大有效载荷,从而大大提高日志复制吞吐量。

Leader选举

Paxos负责活性检测和新的Leader选举。当检测到Leader故障且超过半数节点是active的时候,Paxos将启动领导者选举。Paxos保证新选择的领导者在DLSN之前具有完整的日志。然而,旧领导者可能有一些LSN大于DLSN的redo log日志。它们可能还没有被持久化到其他数据中心,但已经被旧领导者应用。这将导致旧领导者缓冲池中的脏页面与新领导者的脏页面发生冲突。因此,在领导者选举之后,旧领导者重新加入Paxos组后,需要对旧领导者进行一些额外的内存状态清理。它需要与新领导者进行同步,确定未提交的重做日志条目范围,清除与它们相关的脏页,并从PolarFS重新加载干净页。相比之下,旧的Follower节点的情况要简单得多,它只需要丢弃所有大于DLSN的redo log条目并连接到新的领导者以重新同步日志。

事务

分布式事务的关键是确定事务之间的顺序并保证正确的可见性(即因为事务的隔离级别要求,比如避免脏读、可以读到已提交事务的修改)。

在分布式数据库中使用集中式时钟是实现快照隔离的传统方法。TSO-SI是Percolator 和TiDB 使用的解决方案,它提供升序时钟作为快照时间戳和提交时间戳。前者确定要读取的适当记录版本,而后者在所有节点上全局排序事务。多个数据节点上的原子更新通过两阶段提交协议(2PC)实现。然而,集中式时钟服务器可能成为单点故障和潜在的性能瓶颈(因为单点问题)。频繁访问TSO会增加事务处理的延迟,特别是在跨数据中心部署的情况下。为了缓解以上问题,Clock-SI 则依赖于每个节点上松散同步的物理时钟,但它可能会受到时钟偏差引起的延迟的影响。在PolarDB-X,提出了HLC-SI,它利用混合逻辑时钟(HLC)来跟踪分布式数据库中的事件因果关系。

HLC原语

HLC在单个时间戳中同时包含了物理时钟和逻辑时钟。它不仅可以跟踪跨节点事件的因果关系,而且可以使逻辑时钟值接近实际物理时钟值。将HLC时间戳(表示为hlc)实现为64位整数,占位情况如下freserved:2;pt:46;lc:16g。

其中较低的16位(lc)表示逻辑时钟,而较高的46位(pt)存储物理时间。物理时钟的最细粒度为1毫秒,即每毫秒计数65535次,支持每秒超过数千万个事务,这足以应对极大规模的数据库。如果需要,可以为hlc:lc分配更多位。freserved为保留位。

集群中的每个节点都有一个本地物理时钟节点pt(以毫秒为单位),并维护自己的HLC时间戳节点hlc。HLC时钟有三个原语:

  • ClockUpdate(e:hlc)
    在事件e传输已经发生的HLC时间戳(例如在2PC协议中),参与者接收来自协调器的快照ts和提交ts。如果传入的HLC时间戳e:hlc比节点自身维护的node:hlc更高,它将会提升node:hlc。

  • ClockAdvance()
    它将HLC的逻辑时钟部分加1以获取下一个HLC时间戳。如果本地物理时钟节点:pt高于node:hlc,则覆盖HLC时间戳。此函数确保HLC值接近节点的物理时钟,并且两者之间的差异受到限制。

  • ClockNow()
    它类似于ClockAdvance,获取最新的HLC时间戳,但不会递增逻辑时钟。

Sandeep等人提出的原始HLC算法,每次节点之间交换消息时将逻辑部分的HLC时间戳加1。在HLC-SI中,引入了维护HLC时间戳的优化。

  1. 首先,在ClockUpdateClockNow中不递增逻辑时钟部分,这可以防止16位逻辑时钟空间过快耗尽。
  2. 其次,最小化对ClockUpdate的调用。例如,在2PC中,当协调者收到参与者的所有prepare ts时间戳后,协调者不会调用ClockUpdate来更新本地的HLC时间戳。相反,只调用一次ClockUpdate,其参数为所有参与者中看到的最大时间戳。由于全局变量node:hlc的修改可能成为多核系统中的瓶颈,较少的更新显著减少了锁争用。
  3. 最后,在应用了上述优化之后,HLC-SI仍然保留快照隔离的属性。

HLC-SI

这里解释下HLC-SI是如何为分布式事务获取时间戳、以及在节点之间同步和推进HLC时间戳的。如下图4所示:

  1. 在一个事务开始时,协调节点(CN)nodei调用ClockNow获取事务的快照时间戳snapshot ts1(步骤1)
  2. CN随后将事务连同快照时间戳发送到所有参与者的数据节点(DN)nodej(步骤2)
  3. 假设其中一个节点是 nodej,并且它调用 ClockUpdate(snapshot ts) 来更新 nodej:hlc。这确保之后 nodej:hlc 大于或等于 snapshot ts(步骤3)
  4. 当参与者到其读集中的事务处于PREPARED状态时(读操作),需要等待该事务完成,因为处于PREPARED状态的事务的提交时间戳是不确定的,可能大于快照时间戳(即不可见),也可能不大于快照时间戳(即可见)。在2PC的第一阶段中,当DN基于事务写集完成冲突验证后,DN将事务的状态更改为PREPARED,并调用ClockAdvance以获得准备时间戳nodej:prepare ts,然后将其返回给CN(步骤4)
  5. 在2PC的第二阶段中,CN在接收到所有参与者的prepare ts之后,选择最大的值作为commit ts(与Clock-SI相同),然后调用ClockUpdate(commit ts)同步其本地的HLC时间戳,并将commit ts发送给所有DN(步骤6)
  6. DN在接收到commit ts后,也会调用ClockUpdate(commit ts)更新其本地的HLC时间戳(步骤7)

分布式数据库 PolarDB-X: An Elastic Distributed Relational Database for Cloud-Native Applications_第3张图片

正确性证明

这个章节证明了HLC-SI遵循SI的属性。也就是说,对于任意一对事务T1和T2,如果T1:commit ts小于等于T2:snapshot ts,则T1必须对T2可见,否则T1对T2不可见。当T2访问已被T1修改的记录时,可能会出现三种情况:
(1)T1已经提交,此时可见性由T1的commit ts确定;
(2)T1处于PREPARED状态,T2将等待T1完成,然后根据情况1确定T1修改的记录的可见性;
(3)如果T1尚未进入PREPARED状态(即处于ACTIVE状态,还未来prepare),则T1对T2不可见。

证明如下:假设在节点k上,事务T2观察到T1处于ACTIVE状态。当协调者将T2的相关信息发送到节点k时,节点k:hlc被更新为等于或大于T2:snapshot ts:

T2:snapshot ts <= nodek:hlc

由于T1处于ACTIVE状态,存在如下关系:

nodek:hlc < nodek:prepare tsT1

根据commit ts的定义,存在如下关系:

nodek:prepare tsT1 <= T1:commit ts

最后,根据传递律,可以得到

T2:snapshot ts < T1:commit ts

事实上,除非T1已经在参与者(T1)\参与者(T2)的每个节点上进入了PREPARED状态,否则T1不可能对T2可见。因为如果有任何节点上T1仍处于ACTIVE状态,根据以上推论,T1:commit ts必须大于T2:snapshot ts,即T1对T2不可见, T2看不到T1的任何修改

弹性

多租户架构

一些客户运营着SaaS(软件即服务)业务,主张需要多租户数据库。一个典型的SaaS应用程序有大量的订阅者。为了简化对许多订阅者的支持,SaaS应用程序为每个订阅者提供了不同的schema/database(逻辑上)。每个订阅者的数据可以视为数据库中的一个租户。由于每个租户是逻辑上隔离和独立的,因此不存在跨租户事务。与此同时,为了降低成本,通常将多个租户合并存储在一个数据库实例中。然而,当某个租户的访问成为热点,需要更多资源时,该租户应该迁移到其他数据库实例。这个过程很昂贵,需要花费与涉及数据量成正比的时间。

在PolarDB-X中,跨分片的事务是通过分布式事务在CN中实现的。因此,在DN中不存在跨分片事务,因此可以将一个分片或一个分区组视为租户的单位。多个租户存储在一个DN中,并且在水平扩展数据库时,可以将租户迁移到新的可用DN以实现负载均衡。一个租户被定义为一个shema/数据库或表的集合。在这两种情况下,PolarDB-X内部的PolarDB实例都会遇到瓶颈,即只有一个RW节点可以处理所有租户的写请求。因此,我们扩展了PolarDB以支持多租户,允许使用多个RW节点进行可扩展的写入(这里很重要,支持多写,而不仅仅是单节点),但代价是不支持跨租户事务。这些RW节点仍然共享存储,但不同的RW节点操作不相交的数据部分(由租户分割)。严格的限制是,RW节点之间的DML不会互相冲突。为了强制执行这一点,在任何给定时间,每个租户必须绑定到一个特定的RW节点。对于弹性,可以在运行时调整RW节点和租户-RW绑定的数量,并且租户可以快速在RW节点之间迁移。

PolarDB-MT的设计

对于每个数据表,只有一个RW节点可以对其进行写入,因此在B+树页面和行的修改过程中不存在冲突。如图5所示,每个RW节点都有自己的私有redo log,即redo log 中没有写入争用。此外,这些日志之间没有全局排序序列或依赖关系,因为它们记录了对不同租户的修改,这些租户在逻辑上是不相关的。因此,属于不同租户的redo log可以并发地回放以并行恢复数据库状态。事实上,如果一个RW节点失败,一个或多个其他RW节点可以接管它的redo log。它们按照租户划分日志和重放,完成恢复过程并恢复服务。与PolarDB类似,在PolarDB-MT中,每个RW节点也可以拥有可配置数量的RO节点,它们以可重复读或读取已提交隔离级别为读事务提供服务。
分布式数据库 PolarDB-X: An Elastic Distributed Relational Database for Cloud-Native Applications_第4张图片

在PolarDB-MT中,所有的RW节点共享一个全局数据字典,而不是为每个节点维护一个独立的私有数据字典。只有一个RW节点可以获取lease。lease持有者(即主RW节点)管理数据字典,并被授权执行所有字典的修改。其他RW节点维护数据字典的读取缓存,只缓存打开的表的元数据。由于每个表都绑定并由单个RW节点写入,因此表的元数据最多由一个RW节点缓存。在执行DDL语句的开始和结束时,所有者RW节点需要获取独占的MDL(元数据锁),这将阻塞所有后续的DML / DDL语句。然后,它修改数据字典并将元数据修改请求转发给主RW。主RW节点检查修改请求是否有效,即只有租户的所有者有权修改其内部表/数据库的元数据。通过这个检查并写入成功后,发起写请求的RW节点中缓存的内存表元数据也将被更新。然后释放MDL,所有阻塞的DML / DDL语句继续执行。以上过程确保RW节点之间表元数据的一致性。表元数据也被缓存在RO节点中,并通过redo log复制与RW节点同步。

租户迁移

RW节点和租户的绑定信息存储在一个内部系统表中,该表与上层组件(例如代理或CN)共享。因此,知道应将DML语句路由到哪个RW节点。每个RW节点订阅绑定信息的更新并从主RW节点获取lease,以确保绑定信息的正确性和新鲜度。当RW节点接收到一个事务时,它首先检查所有相关表是否绑定到该节点,并保留lease,否则立即返回错误。当RW节点发现lease丢失时,它将暂停所有未完成的事务提交并尝试重新获取lease。当超时或在获取lease后,如果刷新绑定信息并发现某些租户已迁移到其他RW节点,则会立即中止所有受影响的事务。

在迁移或转移一个租户从源 RW 到目标 RW 的过程中,代理或 CN 负责保持客户端的连接。他们暂停向租户发起新的事务并停止将它们转发到源 RW。然后会等待源 RW 节点优雅地完成所有正在进行的 DML/DDL 语句。之后,源 RW 将所有与租户相关的脏页刷新到 PolarFS,清除表的缓存元数据并关闭属于租户的文件等资源。最后,要求系统表更新绑定信息。与此同时,目标 RW 打开租户的文件,从主 RW 节点获取必要的元数据,对自己进行初始化并开始为该租户提供新的事务服务。完成所有上述步骤后,代理或 CN 服务器连接到目标 RW 节点,恢复会话状态,然后将暂停的事务转发以执行。整个过程对应用程序是透明的,就像即将到来的事务由于迁移 DDL 语句而被阻塞一段时间一样。

PolarDB-X集群的扩展性

使用PolarDB-MT,可以快速添加一个DN节点来增加PolarDB-X的读写吞吐量,步骤如下:
(1)创建一个空的RW节点。经过的时间取决于查找可用资源的时间(例如虚拟机)
(2)节点被注册到GMS中。GMS然后计算负载分布并生成包含要移动到新节点的租户的迁移计划
(3)执行计划。可以并行地安排使用不同源和目标RW的租户迁移。如前所述,在迁移过程中,新事务将被暂停数秒钟,但读取请求可以转发到RO节点以减轻影响。

HATP

通过使用解耦式存储架构,PolarDB-X可以在单个系统中tong shi处理OLTP和OLAP工作负载。本节介绍PolarDB-X的主要设计,如何设计HTAP系统。

分布式数据库 PolarDB-X: An Elastic Distributed Relational Database for Cloud-Native Applications_第5张图片

A 整体设计

图6展示了PolarDB-X的HTAP设计的整体框架。PolarDB-X为应用程序提供了单一的访问入口,所有的OLTP和OLAP流量都通过这个入口处理。入口处的HTAP优化器自动区分OLTP和OLAP请求,并将它们分配给不同的计算节点。

PolarDB-X中RW和RO节点的概念自然地将不同的工作负载分离,即OLTP请求由RW节点处理,OLAP请求由RO节点处理。这种物理资源的分离保留了对OLTP工作负载的高稳定性。同时,分布式事务和副本一致性固有地处理了OLAP工作负载的数据可见性。

以上HTAP架构有两个主要优势。

  1. 首先,不同的工作负载被分离并保持一致,其中OLTP工作负载不会受到日志复制的延迟影响。其次,可以通过增加只读节点来提高系统的OLAP性能,而不会影响OLTP工作负载的性能。
  2. 其次,分布式并行计算技术可以被利用,从而使 OLAP 工作负载具有好的可扩展性 。

B. HTAP Optimizer

请求分类和路由

现有的 HTAP 解决方案主要建立在分离的 OLAP 和 OLTP 数据库系统之上,应用了读写分离或 ETL 流程。它们面临着许多挑战,例如分析查询新鲜度差和高维护成本等问题。相比之下,PolarDB-X 的优化器配备了查询分类功能,能够适当地路由工作负载,以在单个系统中处理 HTAP 工作负载。当请求到达时,优化器首先估算请求所需的核心资源消耗成本(例如 CPU、内存、I/O、网络)。基于此成本和一个经验阈值,每个请求都被分类为 OLTP 请求或 OLAP 请求。然后,所有 OLTP 请求都被路由到主 RW 节点,而 OLAP 请求则进一步进入 MPP 优化阶段,生成分布式执行计划以在多个 RO 节点上运行。因此,应用程序可以依靠 PolarDB-X 透明地处理 HTAP 工作负载。

算子下推

计算和存储的分离带来了极佳的可扩展性,但同时也引入了显著的计算和存储节点之间的网络流量。为了减轻这种开销,将计算操作下推向更靠近数据存储的地方是一种很有前途的优化方法。因此,PolarDB-X 支持算子下推。充分考虑存储和数据的特征(例如行存储或列存储,列是否有索引)来估算执行成本。这指将查询的特定部分(例如 Join、Agg、Sort 操作符)推送到相应的存储节点进行近数据计算。

C. HTAP Executor

MPP

PolarDB-X 使用 MPP 模型处理 OLAP 查询,该模型涉及许多 CN 节点从许多 DN 节点访问数据。总体流程如下:
(1) 用户连接到 CN 节点,随后该节点充当查询协调器。
(2) 查询发送到查询协调器,由其优化器生成执行计划。计划被分割成多个片段(即子计划),每个片段进一步包含多个操作符(例如扫描、聚合、连接)。
(3) 任务调度器在查询协调器中将每个片段封装为一个任务,然后将所有任务安排到适当的 CN 节点执行。
(4) 每个涉及的 CN 节点申请所需的资源。构建上下文,启动执行任务,并定期向查询协调器报告其状态。
(5) 每个已执行的任务与其他任务交换必要的数据。当所有任务完成时,部分结果发送回查询协调器,协调器组装最终结果并将其返回给用户。
(6) 查询协调器和所有涉及的 CN 节点都被清理,所有资源都被释放。

由于PolarDB-X将数据分片到多个DN节点,执行计划考虑底层数据的本地性。在可能的情况下,子计划将被推送到相应的DN节点,而计划的其余部分将被转换为由CN节点执行的片段。

Timesharing Scheduler

生成的执行计划需要在CN节点上进行仔细的调度,以更好地利用资源。PolarDB-X中的CN节点有两个调度器,即任务调度器和本地调度器。

  1. 任务调度程序负责在不同CN节点之间进行任务调度。
  2. 本地调度程序负责在CN节点内进行任务调度。

任务调度和执行资源没有强烈的联系。一个已安排的任务不会完全占用相应执行线程的所有资源。为了更好地利用线程资源,PolarDB-X采用时间分片执行模型。每个CN节点都有一个线程池,用于执行已安排的作业,每个作业都排队等待进入线程池进行执行。当作业被阻塞时,它将被放入一个阻塞队列中等待被唤醒。主要有三个原因导致作业被阻塞:
(1)来自操作依赖图的派生任何;
(2)已经缺乏执行资源(例如内存);
(3)或等待来自DN节点的响应。

此外借鉴了Linux内核的时间分片策略,当一个作业在单个轮次中运行足够长时间(例如500毫秒)时,我们将暂停该作业。

D. Resource isolation

除了上述队列和时间分片机制来促进许多正在进行的查询的并行执行之外,资源管理对于并发查询也很重要。特别当正在运行的查询可能消耗大量资源(如CPU和内存)时,它将严重影响其他并发查询的进展。
PolarDB-X以一种抢占式的方式为不同的工作负载隔离资源。

CPU隔离

对于CPU资源,我们将其使用分为两组,即AP组和TP组,并使用cgroups进行资源隔离。 TP组的CPU资源不受限制,而AP组的资源受到cgroups的严格控制(使用cpu.min.cfs配额和cpu.max.cfs配额)。

此外,查询任务分配给不同的线程池:TP core Pool,AP core Pool和慢查询AP core Pool。后两个池属于AP组,受到严格的CPU限制。可能会错误地将AP查询识别为TP查询,因此我们应确保TP查询不受影响。一旦TP core Pool中的查询运行了意外长的时间,它将终止当前时间片并重新分配到AP core Pool中进行后续执行。同样,当AP core Pool中的查询意外长时间运行时,它将被重新分配到慢查询池中,该池具有较低的时间片份额。

内存隔离

对于内存资源,CN节点中的堆内存分为四个主要区域:

  1. TP Memory用于存储TP查询的临时数据;
  2. AP Memory用于存储AP查询的临时数据;
  3. Other用于存储数据结构、临时对象、元数据等信息;
  4. System Reserved用于特权使用。

同时,TP Memory和AP Memory都有相应的最大和最小使用限制,它们可以在需要时抢占彼此的资源。具体而言,TP Memory只有在查询完成后才会释放被抢占的内存(来自AP Memory),而AP Memory必须在TP Memory请求它时立即释放被抢占的内存。

这里本质上,TP的优先级比AP的更高

E. In-Memory Column Index

PolarDB-X支持在其DN上的位于内存的列索引,以从列存储中受益。当处理复杂查询时,可以显着提高性能。该索引实现为基于行存储中所选列或索引列的内存中列形式表示。从日志中捕获对索引列的逻辑操作(例如插入,更新,删除),并将其转换为对索引的相应操作。具体流程如下:

  1. 日志将传输到RO节点,因此可以在负责AP查询的选定RO节点上构建列索引。为了避免维护RW节点上的列索引的内存开销,它仅捕获日志但不会实现列索引。
  2. 列索引中的记录的trx_id(事务ID)与InnoDB中的一致。这有助于重用InnoDB read view以在一致的快照上实现行和列存储的混合执行计划。
  3. 为了进一步减轻列索引的维护开销,可以延迟和批处理其更新。在这种情况下,它的版本落后于行存储的版本,并且AP查询运行在快照的版本上,该版本受到列索引的影响。

PolarDB-X的优化器在CBO阶段需要枚举各种可行的执行计划。在PolarDB-X的优化器内部,执行计划以关系代数表达式树的形式表示。在这种情况下,存储层是解耦的,可以利用不同的存储类型(即行存储和列存储)。具体而言,优化器通过根据存储特征采用成本模型,将逻辑表达式计划转换为物理执行计划。

例如,在行存储中,当索引命中时,可以快速获取符合条件的行;但当索引缺失或扫描了大量数据时,IO成本往往会非常高。

类似地,在列存储中,存储更紧凑,处理大量数据的I/O和计算更加高效,某些操作(如过滤、连接、聚合)的执行速度更快。

在对行存储和列存储的物理执行计划(即PolarDB-X的内存列索引)进行全面比较之后,优化器将最终选择成本最低的执行计划来进行。在实践中,大数据扫描和具有连接或聚合的下推计划使用内存列索引更合适,而点查询则选择InnoDB行存储。

你可能感兴趣的:(分布式系统与数据库理论,数据库,分布式,后端)