PolarDB技术内幕

更新时间:2025-03-06 06:49:10

本文档介绍PolarDB云数据库的性能优点以及共享存储、物理复制和使用场景中优化的过程。

背景信息

传统的关系型数据库有着悠久的历史,从上世纪60年代开始就已经在航空领域发挥作用。因为其严谨的一致性保证以及通用的关系型数据模型接口,获得了越来越多的应用。2000年以后,随着互联网应用的出现,很多场景下,并不需要传统关系型数据库提供的一致性以及关系型数据模型。由于快速膨胀和变化的业务场景,对可扩展性(Scalability)以及可靠性(Reliability)更加需要,而这又正是传统关系型数据库的薄弱之处。此时,新的适合这种业务特点的数据库出现,就是我们常说的NoSQL。但是由于缺乏一致性及事务支持,NoSQL被很多业务场景拒之门外。缺乏统一的高级的数据模型和访问接口,又让业务代码承担了更多的负担。数据库的历史就这样经历了多重否定,又螺旋上升的过程。

PolarDB就是在这种背景下出现的,由阿里巴巴自主研发的下一代关系型分布式云原生数据库。在兼容传统数据库生态的同时,突破了传统单机硬件的限制,为用户提供大容量、高性能、高弹性的数据库服务。

核心技术之共享存储

核心技术之共享存储

PolarDB采用了共享存储(Share Storage)的整体架构。采用远程直接数据存取(Remote Direct Memory Access,以下简称RDMA)高速网络互连的众多区块服务器(Chunk Server)一起向上层计算节点提供块设备服务。一个集群可以支持一个主(Primary)节点和多个二级(Secondary)节点,分别以读写和只读的挂载模式通过RDMA挂载在Chunk Server上。

PolarDB的计算节点通过libpfs挂载在PolarStores上,数据按照Chunk为单位拆分,再通过本机的PolarSwitch分发到对应的Chunk Server。每个Chunk Server维护一组Chunk副本,并通过ParallelRaft保证副本间的一致性。PolarCtl则负责维护和更新整个集群的元信息。

Bypass Kernel

PolarDB诞生于2015年,由于RDMA高速网络的出现,使得网络带宽接近于总线带宽。PolarDB作出大胆的假设,那就是未来数据库的瓶颈将由网络转向软件栈。因此PolarStore中采用了大量的Bypass Kernel的设计。首先是新硬件NVMERDMA的使用,摆脱了IO访问过程中的用户态内核态交互。Bypass Kernel

软件设计中,在绑定CPU,非阻塞IO的模式下, 通过状态机代替操作系统的线程调度,达到Bypass Kernel的目的。

ParallelRaft

PolarStore中采用三副本的方式来保证数据的高可用,需要保证副本间的一致性。工业界有成熟的Raft协议及实现,但Raft由于对高一致性的追求,要求顺序确认以及顺序提交。而副本的确认提交速度会直接影响整个PolarStore的性能。为了获得更好的访问速度,PolarStore提出了ParallelRaft协议,在Raft协议的框架下,利用块设备访问模式中方便判定访问冲突的特点,允许一定程度的乱序确认和乱序提交。

如下图所示,在所有已经确认的提案中,那些对前序访问有访问Range冲突的提案会被暂时Block,而没有冲突的提案会进入Ready状态并commit,commit以后的提案会继续反馈给当前的Scheduler,之前被Block的提案有可能会进入Ready状态,进而继续被提交。ParallelRaft

核心技术之物理复制

采用了共享存储的模式之后,Secondary上依然需要从Primary上的复制逻辑来刷新内存结构,包括Buffer Pool以及各种Cache。由于读写节点和只读节点访问的是同一份数据,传统的基于Binlog的逻辑复制方式不再可用,这时逻辑复制由于最终执行顺序的变化,导致主从之间出现不同的物理数据结构。因此DB层基于Redo Log的物理复制的支持是必不可少的。

核心技术之物理复制

不同于逻辑复制自上而下的复制方式,物理复制方式是自下而上的。从共享存储中读取并重放REDO,重放过程会直接修改Buffer Pool中的Page,同步B+Tree及事务信息,更新Secondary上的各种内存Cache。除了支持共享存储外,物理复制还可以减少一份日志写入。由于整个复制过程不需要等到事务提交后才开始,显著的减少了复制延迟:

image

交易场景优化

针对双十一峰值交易场景,PolarDB也做了大量优化。

热点行优化技术

在秒杀抢购等高峰交易场景中,热点行的更新行为经常出现,这会引发一系列挑战和问题。主要面临以下几个方面的难题:

  • 在数据库中,那些频繁执行增删改查操作的数据行被称为热点行。当一个事务对某一行数据进行更新时,会对目标数据行进行加锁,直到事务提交或回滚时才释放锁。同一时段内,对于同一个数据行,只有一个事务能够进行更新,其余的事务需要排队等待。因此,对单一热点行的更新请求其实是串行执行的,传统的分库分表策略在性能提升方面并不会带来显著效果。

  • 在电商平台的业务中,限购和秒杀是常见的促销手段。在这些场景下,大量针对热点行的更新请求会在极短的时间内涌入后台数据库系统,必然导致严重的行锁竞争和等待,进而影响系统性能。如果一个更新请求等待执行的时间变长,将对业务层面产生显著的负面影响。

针对上述问题,单一提高计算机硬件配置已无法满足低延迟的需求。因此,PolarDB在数据库内核层进行了创新性优化。这些优化不仅能够自动识别热点行的更新请求,还能将一定时间间隔内对同一数据行的更新操作进行分组。不同分组会采用流水线方式并行处理,从而显著提升系统性能。经测试,在单个热点行配备8CPU的场景下,引入热点行优化后,库存热点性能在高并发情况下提升了近64倍。

PolarDB热点行优化的具体方案如下:

  • 串行处理转变为流水线处理

    为了提升数据库系统的性能,最直接的方法是采用并行处理。然而,对于同一热点行的更新操作,完全实现并行是相对困难的。为此,PolarDB创新性地引入了流水线处理方式,最大限度地将热点行的更新操作进行并行化。

    image

    热点行的更新操作所使用的SQL语句会被标记为autocommitcommit_on_success。优化后的MySQL内核会自动识别这些带有特定标记的更新操作。在一定时间间隔内,系统会根据主键或唯一键对收集到的更新操作进行Hash处理。对于被Hash到同一个桶中的更新操作,系统将按照到达的先后顺序进行分组,并批量处理和提交。

    为了使用流水线方式处理这些更新操作,需要设置两个执行单元来对其进行分组。当第一个分组收集完毕并准备提交时,第二个分组会立即开始收集更新操作。当第二个分组收集完毕并准备提交时,第一个分组已经完成提交并开始收集新一批的更新操作。两个分组不断切换,从而实现并行执行。

    如今,多核CPU的使用已经非常普遍。这种流水线式的处理方式能够充分利用硬件资源,提升CPU的使用率,进而提高数据库系统的并行处理能力,从而最大限度地提升系统的吞吐量。

  • 消除申请行锁时的等待

    为了确保数据的逻辑一致性,在对某一数据行进行更新时,首先会对该行加锁。如果加锁请求无法立即满足,则该请求将进入等待状态。这不仅增加了处理延迟,还可能触发死锁检测,从而导致额外的资源消耗。

    如前所述,我们会按照时间顺序将对同一数据行的更新操作进行分组,其中组内的第一个更新操作被称为Leader。Leader负责读取目标数据行并加锁,而后续的更新操作则被称为Follower。在Follower对目标数据行加锁时,如果发现Leader已经持有行锁,则无需等待,可以直接获得该行锁。通过这一优化,可以减少行锁的加锁次数和时间开销,从而显著提升整个数据库系统的性能。

  • 减少B-tree索引的遍历

    MySQL通过使用B-tree索引来管理数据。在每次执行查询时,系统都需要遍历索引才能定位到目标数据行。随着数据表的增大,索引层级也会随之增加,从而导致遍历所需的时间变得更长。

    在前面提到的更新操作分组机制中,只有每组的Leader需要遍历索引来定位数据行。随后,更新后的数据行会被缓存(Row Cache)在内存中。当同组的Follower成功加锁后,可以直接从内存中读取目标数据行,而无需再次遍历索引。这样一来,整体上减少了索引遍历的次数和时间开销。关于热点行的更多介绍,请参见热点行优化

Blink Tree

在峰值交易场景中,会有大量涉及热点Page的更新及访问,会导致大量关于这些热点PageSMO(Split Merge Operation)操作,之前PolarDBSMO场景下由于B+Tree实现有如下的加锁限制:

  • 同一时刻,整个B+Tree只能有一个SMO操作。

  • 正在执行SMO操作的B+Tree分支上的读取操作会被阻塞,直到整个SMO操作完成。

针对这个问题PolarDB做了如下优化:

  • 通过优化加锁,支持同一时刻多个SMO同时进行操作,这样原本等待在其它分支执行SMO的插入操作就无需等待,从而提高写入性能;

  • 引入Blink Tree来替换B+Tree,通过缩小SMO的加锁粒度,将原本需要将所有涉及SMO的各层Page加锁直到整个SMO完成后才释放的逻辑,优化成Ladder Latch,即逐层加锁。修改完一层即可放锁然后去加上一层Page锁继续修改。这样原本被SMO阻塞的读操作会有机会在SMO操作过程中执行。通过对每个节点增加一个后继链接的方式,使得在Page Split的中间状态也可以完成对Page安全的访问,如下图所示,传统的B+Tree必须通过一把锁来Block整个Page Split过程中对所影响的Page的访问。而Blink Tree则不需要,即使Split还在进行中,父节点到子节点的链接还没有完成建立,依然可以通过前一个节点的后继链接找到正确的子节点。并且通过特殊处理确保访问到正确的Page,从而提高读取性能。Blink Tree

通过对B+Tree的优化,可以将交易场景下PolarDB的读写性能提升20%。

Simulated AIO

InnoDB中存在simulated AIO逻辑,用于支持运行在不包含AIO的系统下。PolarDB下的共享存储文件系统没有AIO,所以采用的是simulated AIO的逻辑。但是原版中的simulated AIO基于本地存储设计,与分布式存储的特性并不适配。为了进行IO合并,原版的simulated IO设计,将所有异步IO请求按照目标地址进行组织,存放在同一个IO数组中,方便将目标地址连续的小IO合并成大IO来操作,以提升IO的吞吐。但是这个设计与分布式存储是不相适配的,连续的大IO操作,会使得同一时刻,只有一个或少量存储节点处在服务状态,浪费了其它存储节点资源;

另外,分布式存储的网络延迟较大,在高负载下,网络中的Inflight IO会较多,IO组中的IO请求数量也会很多,而这种组织方式下,IO数组中的槽位状态都无序的,向数组中添加IO请求和移除IO请求的开销都很大。所以,PolarDB在高负载下的性能比较差且不稳定,为此PolarDB专门对simulated AIO进行了重新的设计,主要包括:

  • 合理的选择IO合并和拆解,充分利分布式存储的多节点优势;

  • 建立状态有序的IO服务队列,减少高负载下的IO服务开销。

Simulated AIO

通过重新设计,性能有很大幅度的提升,如下图:

Simulated AIOSimulated AIO

稳定性也有了很大的提升,如下图:

1212Partitioned Lock System

PolarDB采用的是2PL+MVCC的并发控制方式。也就是用多版本数据构建Snapshot来服务读请求,从而避免读写之间的访问冲突。而写之间的冲突需要通过两阶段锁来保证,包括表锁、记录锁和谓词锁等。当需要加锁时,之前的做法都需要在log_sys中先获得一把全局的mutex保护。

在峰值的交易场景中,大量的写入会导致mutex成为瓶颈。因此PolarDB采取了Partitioned Lock System的方式,将lock_sys改造成由多个LockSysShard组成,每个Shard中都有自己局部的mutex,从而将这个瓶颈打散。尤其是在这种大压力的写入场景下明显的提升写入性能。

11PolarTrans事务系统

PolarDB中支持Snapshot Isolation的隔离级别,通过保留使用的Undo版本信息来支持对不同版本的记录的访问,即MVCC。而实现MVCC需要事务系统有能力跟踪当前Active及已经Commit的事务信息。在之前的实现中每当有写事务开始时,需要分配一个事务ID,并将此ID添加到Transaction System中的一个活跃事务列表中。当有读请求需要访问数据时,会首先分配一个ReadView,其中包括当前已分配最大的事务ID,以及当前活跃事务列表的一个备份。每当读请求访问数据时,会通过从Index开始的Roll PTR访问到此记录所有的历史版本,通过对比某个历史版本的事务IDReadView中的活跃事务列表,可以判断是不是需要的版本。

1

然而,这就导致每当有读事务开始时,都需要在整个拷贝过程中对当前活跃事务列表加锁,从而阻塞了新的写事务的ID加入。同样写事务和写事务之间也有访问活跃事务列表的冲突。从而活跃事务列表在这里变成一个明显的性能瓶颈,在双十一这种大压力的读写场景下尤为明显。

PolarTrans中,事务状态更新无需维护活跃事务列表,传统的事务状态的拷贝过程通过获取当前集群的最大提交时间戳来替代。这使得事务状态的迭代、获取和查询(可见性判断)等逻辑更加轻量化。同时,PolarTrans对大部分事务逻辑进行了无锁优化,从而在读写混合场景和纯写场景下都实现了显著的性能提升。

image

  • 本页导读 (1)
  • 背景信息
  • 核心技术之共享存储
  • Bypass Kernel
  • ParallelRaft
  • 核心技术之物理复制
  • 交易场景优化
  • 热点行优化技术
  • Blink Tree
  • Simulated AIO
  • Partitioned Lock System
  • PolarTrans事务系统