背景意义

云计算为如今的互联网时代提供了更多的计算能力,乃至创造能力,关系型数据库作为所有应用不可或缺的重要部件,开箱即用,高性价比特性的云数据库深受开发者的喜爱。作为一线的开发和运维人员,在阿里云的线上值班中,我们经常会碰到以下经典的客户抱怨。

“你好,你们RDS的产品很棒,但是就是有点小贵,尤其是我要买只读实例的时候,成本是线性增加的,你们什么时候能便宜点?”

“我在4天前,手工做了一个备份,数据库比较大,3T,你们说差不多要70个小时备份,这个。。有没有什么办法加快一点,我老板还着急要数据呢”

“我昨天花钱买了一个只读实例,但是实例到今天还没创建好,3T数据量是大了一点,有没有方法能加快一点?”

“我拿你们的RDS MySQL测试了一下性能,但是貌似不太给力,跟我之前用的Oracle/SQL Server等一些商业数据库差的有点多,有什么办法提高性能么?如果不行,我还是迁回自建的数据中心吧”

“你好,我们公司有个数据库,想迁到阿里云RDS上,对RDS的产品品质我们很满意,只是我们的数据库有10T,请问一下,支持这么大的实例么?”

“你好,我用了你们的MySQL数据库,最近几天在做活动,主库压力比较大,只读实例就延迟了,现在看过去貌似很难跟上,有什么办法么?”

上述抱怨/吐槽都来自用户真实的案例,总结起来,传统的云数据库由于自身架构原因,会遇到如下问题:

  1. 读写实例和只读实例各自拥有一份独立的数据,用户购买只读实例,不仅需要付出计算的成本,也需要付出存储资源的成本。
  2. 传统备份技术,由于也涉及到拷贝数据,并上传廉价存储,速度因此也受网络影响。
  3. 读写实例和只读实例各自拥有一份独立的数据,新建一个只读实例需要重新拷贝数据,考虑到网络限流,速度不会很快。
  4. MySQL数据库早期的版本,对早期的系统/硬件做了很多优化,但是并没有考虑到现代主流的系统/硬件的优秀特性,在高并发环境下,性能还有很大的提升空间。此外,与其他关系型数据库不同,MySQL为了兼容性,需要写两份日志(事务日志和复制日志),与其他商业数据库相比,性能相对较差。
  5. 由于物理机磁盘限制以及备份等策略,数据库的数据大小不能太大,太大的实例是运维的灾难。
  6. 读写实例和只读实例通过增量逻辑数据同步,读写实例上所有的SQL需要在只读实例上重新执行一遍(包括SQL解析,SQL优化等无效步骤),同时,复制并发读最高是基于表维度,导致主备延迟非常普遍,进而影响各种切换任务。

随着数据库数据量的增大,这些小麻烦会不断的加剧,给DBA,开发乃至CTO带来困恼。 如今,这些困扰大家已久的问题,在阿里云即将推出去的POLARDB中将得到解决,注意,是从本质上解决,而不是想个trick绕过去。

POLARDB是阿里云ApsaraDB数据库团队研发的基于云计算架构的下一代关系型数据库(暂时仅支持MySQL,PostgreSQL正在紧锣密鼓的开发中),其最大的特色是计算节点(主要做SQL解析以及存储引擎计算的服务器)与存储节点(主要做数据块存储,数据库快照的服务器)分离,其次,与传统的云数据库一个实例一份数据拷贝不同,同一个实例的所有节点(包括读写节点和只读节点)都访问存储节点上的同一份数据,最后,借助优秀的RDMA网络以及最新的块存储技术,POLARDB的数据备份耗时可以做到秒级别(备份时间与底层数据量无关),这三点相结合,我们可以推断出POLARDB不但满足了公有云计算环境下用户业务快速弹性扩展的刚性需求(只读实例扩展时间与底层数据量无关),同时也满足了互联网环境下用户对数据库服务器高可用的需求(服务器宕机后无需搬运数据重启进程即可服务)。

架构分析

image.png image.png

图1是POLARDB公测版本的总体架构图。图2是POLARDB数据库内核上的架构。 解释一下图1中的各个组件。 上部分三个黑色框表示三台计算节点,下部分三个蓝色框表示三台存储节点。 在计算节点上,主要有三个重要部件:

DB Server: 即数据库进程(Polar DataBase, 简称POLARDB)。POLARDB数据库内核区分实例角色,目前包括三种角色,Primary,Standby和Replica。Primary即为拥有读写权限的读写库,Replica即为只读实例,仅仅拥有读取数据的权限(后台线程也不能修改数据),Primary和Replica采用Shared Everything架构,即底层共享同一份数据文件和日志文件。StandBy节点拥有一份独立的数据和日志文件(如图2所示),虽然用户线程依然只有读取数据的权限,但是后台线程可以更新数据,例如通过物理复制的方式从Primary节点更新增量数据。StandBy节点主要用来机房级别的容灾以及创建跨可用区的只读实例,公测阶段暂时不开放。由于只读实例的扩展不需要拷贝数据,创建新的只读实例不但速度快,而且很便宜,用户只需要支付相应计算节点的成本即可。我们称StandBy和Replica节点为Slave节点,Primary节点也可称为Master节点。

User Space File System: 即用户态文件系统(Polar File Sytem, 简称PolarFS)。由于多个主机的数据库实例需要访问块存储上的同一份数据,常用的Ext4等文件系统不支持多点挂载,POLARDB数据库团队自行研发了专用的用户态文件系统,提供常见的文件读写查看接口,便于MySQL和相关的外围运维工具使用文件系统支持类似O_DIRECT的非缓存方式读写数据,还支持数据页原子写,IO优先级等优秀的特性,为上层数据库的高性能提供了结实的保障。传统的文件系统,由于嵌入在操作系统内核中,每次系统文件读写操作都需要先陷入内核态,完成后再返回用户态,造成效率低下。PolarFS以函数库形式编译在MySQL中,因此都运行在用户态,从而减少了操作系统切换的开销。

Data Router & Cache: 即块存储系统客户端(Polar Store Client, 别名PolarSwitch)。PolarFS收到读写请求后,会通过共享内存的方式把数据发送给PolarSwitch,PolarSwith是一个计算节点主机维度的后台守护进程,接收主机上所有实例以及工具发来的读写块存储的请求。PolarSwith做简单的聚合,统计后分发给相应的存储节点上的守护进程。由此可见PolarSwitch是一个重资源的进程,如果处理不好,对计算节点上的数据库实例有很大的影响,因此我们的管控程序对其使用了CPU绑定,内存预分配,资源隔离等一些手段,并且同时部署了高效可靠的监控系统,保证其稳定运行。

Data Chunk Server: 即块存储系统服务器端(Polar Store Server, 别名ChunkSever)。上述三个部件都运行在计算节点上,这个部件则运行在存储节点上。主要负责相应数据块的读取。数据块的大小目前为10GB,每个数据块都有三个副本(位于三台不同的存储节点上),两个副本写成功,才给客户端返回成功。支持数据块维度的高可用,即如果一个数据块发生不可用,可以在上层无感知的情况下秒级恢复。此外,PolarStore使用了类似Copy On Write技术,支持秒级快照,即对数据库来说,不管底层数据有多大,都能快速完成全量数据备份,因此POLARDB支持高达100T的磁盘规格。

计算节点和存储节点之间通过25G RDMA网络连接,保证数据的传输瓶颈不会出现在网络上。

此外,POLARDB还有一套完善的基于docker的管控系统,处理用户下发的创建实例,删除实例,创建账号等任务,还包括完善详细的监控,以及可靠的高可用切换。管控系统还维护了一套元数据库,用以记录各个数据块的位置信息,提供给PolarSwitch,便于其转发。 可以说,POLARDB整个项目用了很多很多的新技术黑科技,给用户直接的感受是,又快(性能是官方MySQL6倍)又大(磁盘规格支持高达100T)又便宜(价格只有商业数据库的1/10)。

接下来,我们重点分析POLARDB在MySQL内核方面的功能增强、性能优化以及未来的规划。便于读者了解POLARDB数据库内部运行的机制。

功能增强

物理复制

简单的想,如果读写实例和只读实例共享了底层的数据和日志,只要把只读数据库配置文件中的数据目录换成读写实例的目录,貌似就可以直接工作了。但是这样会遇到很多问题,随便列举几条:

  1. 如果读写实例上对某条数据进行了修改,由于Buffer Pool缓存机制,数据页可能还没有被刷新到磁盘上,这个时候只读实例就会看不到数据,难道每次都刷盘,那跟文件有什么区别。
  2. 再仔细想一想事务,一个读写事务,修改了3个数据页,2个数据页刷下去了,但是1个数据页还没有刷盘,这个时候只读节点因为需要查询也需要这几个数据页,然后数据就不一致了?也就是说只读节点上的MVCC机制似乎会被破坏。
  3. 考虑一下DDL,如果在读写实例上把一个表给删除了,万一只读实例上还有对这个表的查询呢?只读实例去哪里查询数据哈?要知道IBD文件已经被读写实例这个家伙干掉了,可怜的只读实例只能coredump以示不满?
  4. 如果读写实例正在写一个数据页,然而刚好只读实例也要读取这个数据页,然后只读实例有可能读了一个写了一半的数据页上来,然后checksum校验不过,数据库crash。

所以,仔细思考一下,多个数据库实例共享同一份数据不是一件容易的事情。为了解决上述问题,我们需要只读实例也按照读写实例更新数据的节奏来更新数据并且需要一套完善的脏页刷盘机制。现代关系型数据库中其实已经有一份记录数据页更新的Redolog事务日志了,在MySQL中,就是ib_logfileXX类似这种文件,因此参考Binlog复制框架,POLARDB使用这种事务日志构建了一套数据复制方法,在只读实例上只需要顺序应用读写实例产生的日志即可,StandBy节点和Replica节点两者都需要更新内存中的数据结构,但是StandBy节点由于独立维护一份数据和日志,所以需要把更新的增量数据写入到自己的数据文件和日志中,而Replica则不需要。由于MySQL的Redolog相比于Binlog记录的都是物理的数据页(当然也有逻辑部分),所以我们把这种复制称为物理复制。

由于Redolog并没有记录用户的SQL,仅仅记录了最终的结果,即这个SQL执行后造成的数据页变化,所以依赖这种复制架构,不需要SQL解析SQL优化,MySQL直接找到对应的文件中的数据页,定位到指定偏移,直接更新即可。因此性能上可以做到极致,毕竟并发粒度从之前的表级别变为了数据页级别。当然也会带进来很多新的问题,简单列举几个: 物理复制中的MVCC。MySQL的MVCC依赖Undo来获取数据的多版本,如果Primary节点需要删除一个Undo数据页,这个时候如果Replica节点还在读取的话就会有问题,同理,StandBy节点也有这个问题,因此我们给客户提供两种方式,一种是所有Slave定期向Primary汇报自己的最大能删除的Undo数据页,Primary节点统筹安排,另外一种是当Primary节点删除Undo数据页时候,Slave接收到日志后,判断即将被删除的数据页是否还在被使用,如果在使用则等待,超过一个时间后直接给客户端报错。此外,为了让Slave节点感知到事务的开始和结束以及时间点,我们也在日志中增加了不少逻辑日志类型。

物理复制中的DDL。Primary节点删除一个表之后,Replica可能还会有对此表的请求。因此,我们约定,如果主库对一个表进行了表结构变更操作,在操作返回成功前,必须通知到所有的Replica(有一个最大的超时时间),告诉他们,这个表已经被删除了,后续的请求都失败吧,具体实现上可以使用MDL锁来控制。当然这种强同步操作会给性能带来极大的影响,后续我们会对DDL做进一步的优化。

物理复制中的数据复制。除了复制引擎层的数据之外,POLARDB还需要考虑MySQL Server层表结构的一些文件复制,例如frm, opt文件等。此外,还需要考虑一些Server层的Cache一致性问题,包括权限信息,表级别的统计信息等。 物理复制中的日志并发应用。既然物理日志可以让我们按照数据页的维度来支持并发,那么POLARDB需要充分的利用这个特性,同时保证在数据库奔溃后也能正确的应用日志和回滚事务。其实这部分的代码逻辑与MySQL崩溃恢复的逻辑很像,POLARDB对其进行了复用和改造,做了大量的性能上的优化,保证物理复制又快有稳。

物理复制中的Change Buffer问题。Change Buffer本质上是为了减少二级索引带来的IO开销而产生的一种特殊缓存机制。当对应的二级索引页没有被读入内存时,暂时缓存起来,当数据页后续被读进内存时,再进行应用,这个特性也带来的一些问题,例如Primary节点可能因为数据页还未读入内存,相应的操作还缓存在Change Buffer中,但是StandBy节点则因为不同的查询请求导致这个数据页已经读入内存,发生了Change Buffer Merge操作,从而导致数据不一致,为了解决这个问题,我们引入shadow page的概念,把未修改的数据页保存到其中,将change buffer记录合并到原来的数据页上,同时关闭该Mtr的redo log,这样修改后的Page就不会放到flush list上了。此外,为了保证Change Buffer merge中,不被用户线程看到中间状态,我们需要加入新的日志类型来控制。

物理复制中的脏页控制。Primary节点不能毫无控制的刷脏页,因为这样Replica会读取到不一致的数据页,也会读取到未提交事务的数据页。我们给出了一个策略,replica需要缓存所有未刷盘的数据变更(即RedoLog),只有primary节点把脏页刷入盘后,replica缓存的日志才能被释放。采用这个策略后,replica内存的使用率与primary的刷脏速度就相关量起来,所以需要对primary节点的刷脏算法进行调整,同时也要考虑一些频繁更新的数据页导致primary节点刷脏缓慢的问题。

物理复制中的Query Cache问题,discard/import表空间问题,都需要考虑。

此外,在兼容性方面,由于POLARDB存储引擎暂时只兼容InnoDB数据表,Myisam和Tokudb暂时都不兼容,而系统表很多都是Myisam的,所以也需要转换。

物理复制能带来性能上的巨大提升,但是逻辑日志由于其良好的兼容性也并不是一无是处,所以POLARDB依然保留了Binlog的逻辑,方便用户开启。

实例角色

传统的MySQL数据库并没有实例角色这个概念,我们只能通过查询read_only这个变量还判断实例当前的角色。POLARDB中引入三种角色,满足不同场景下的需求。Primary节点在初始化的时候指定,StandBy和Replica则通过后续的配置文件指定。

故障切换。

目前支持三种类型的切换,Primary降级为StandBy,StandBy提升为Primary以及Replica提升为Primary。每次切换后,都会在系统表中记录,便于后续查询。Primary节点和StandBy节点由于拥有各自的数据文件,在异步复制的模式下,容易造成数据不一致,我们提供了一种机制,保证新StandBy的数据一定与新Primary的数据一致。想法很简单,当新StandBy再次启动时,去新Primary查询,回滚掉多余的数据即可,只是我们这里不使用基于Binlog的SQL FlashBack,而是基于Redolog的数据页FlashBack,更加高效和准确。Primary节点和Replica节点,由于共享同一份数据,不存在数据不一致的分享,当发生切换的时候,新Primary只需要把老Primary未应用完的日志应用完即可,由于这个也是并行的操作,速度很快。

这里简单提一下,目前公测阶段的故障切换逻辑:首先是管控系统检测到Primary不可用(或者发起主动运维操作),则连接上Primary(如果还可以),kill所有用户连接,设置read_only,设置PolarStore对此IP只读(Primary节点和Replica节点一定在不同的计算节点上),接着,连接到即将升级的Replica上,设置PolarStore对此IP读写,MySQL重新以读写模式挂载PolarFS,最后执行Replica提升为Primary的语句。正常情况下,整个过程时间很短,30秒内即可切换完成,但是当Primary和Replica延迟比较大的时候,需要更多的时间,但是相比于之前Binlog的复制,延迟时间至少下降2个数量级。后续我们会对这块继续进行深度优化,进一步提高实例可用性。

复制模式

传统的Binlog复制模式只有异步复制和半同步复制两种。在POLARDB中,我们还增加了强同步复制,即要求Slave节点应用完数据才返回成功,这个特性对复制特别敏感的用户来说,是个好消息,能保证只读实例查询出来的数据一定与读写库上查询出来的一样。此外,POLARDB还支持延迟复制功能,复制到指定事务,复制到指定时间点等,当然这几个特性主要是提供给灾备角色StandBy的。此外,为了进一步减少复制延迟,如果Replica发现自身延迟超过某个阈值,就会自动开启Boost模式(会影响实例上的读),加速复制。如果Primary节点发现某个Replica延迟实在太大,出于安全考虑,会暂时把这个Replica踢出复制拓扑,这个时候只需要重新启动一下Replica即可(由于Replica不需要做奔溃恢复,重启操作很快)。POLARDB还支持复制过滤功能,即只复制指定的几个数据库或者指定几个表,这样由于不需要复制查询不感兴趣的数据,从而可以进一步降低复制延迟。这里提到的各种有趣的特性,在公测阶段还暂时不支持,在不久的将来,会陆续开放给大家。

日志管理

类似Binlog的日志管理,POLARDB也提供了类似的机制和工具来管理Redolog。首先,与传统MySQL循环使用Redolog不同,与Binlog类似,POLARDB中Redolog也是以文件编号递增的方式来管理,并提供相应的删除命令给管控系统使用。POLARDB依据PolarStore的全量备份和Redolog增量日志为客户提供还原到任意时间点的功能。POLARDB还有一个专门用来解析Redolog日志的工具mysqlredolog,类似Binlog解析工具mysqlbinlog,这个工具在系统诊断中非常有用。Redolog中,POLARDB也增加了不少独特的日志类型,为了做到兼容性,Redolog也有版本管理机制。传统的MySQL仅仅数据文件支持O_DIRECT,POLARDB为了适配新的文件系统,Redolog日志也支持O_DIRECT方式写入。

此外,POLARDB对Undo日志也进行了深度的开发,目前支持Online Undo Truncate,妈妈再也不用担心我的ibdata数据文件过大啦。

监控信息

POLARDB增加了那么多功能,自然也会提供很多新的命令,例如用户可以使用SHOW POLAR STATUS来查看相关信息,使用SHOW POLAR REPLICAS来查看所有已经连接的replica节点。使用START POLAR SLAVE来启动复制,使用SHOW POLAR LOGS来查看产生的Redolog文件,等等等。POLARDB在information_schema中也增加了好多表,有兴趣的读者可以去看一下,这里面到底存的是什么。

实例迁移

目前数据库内核支持(大概再过几个月会提供给用户使用)从RDS 5.6迁移到POLARDB上,流程主要如下,首先在RDS 5.6上使用xtrabackup做个全量的数据备份,接着在计算节点上,xtrabackup做恢复,成功后,通过PolarFS提供的运维工具把所有的数据文件放到PolarStore上,然后使用特定的参数启动POLARDB,POLARDB会把RDS 5.6日志文件格式转换成POLARDB的Redolog,接着可以使用Binlog方式追增量数据,当追上RDS 5.6的读写库且到达用户指定的切换时间,设置RDS 5.6读写库只读,并且POLARDB做一次重启,关闭Binlog复制,最后把VIP切换到POLARDB上即可完成迁移工作。

周边工具

除了上文提到的解析Redolog日志工具外,由于对源码进行了大幅度的改动,POLARDB还对MySQL原生的TestCase Framework进行了改动,保证在共享数据日志的场景下(Local FS/Disk and PolarFS/PolarStore)所有的Testcase能通过。

性能优化

POLARDB除了拥有大量新的特性外,我们还做了很多性能上的优化,这里简单列举一些:

  1. 在高并发的场景下,POLARDB对很多latch做了优化,把有些latch分解成粒度更小的锁,把有些latch改成引用计数的方式从而避免锁竞争,例如Undo segment mutex, log system mutex等等。POLARDB还把部分热点的数据结构改成了Lock Free的结构,例如Server层的MDL锁。
  2. 针对用户在SQL语句中直接指定主键的SQL语句,POLARDB也做了相应的优化,这样可以减少一些优化器的工作,从而得到更好的性能。
  3. POLARDB中物理复制的性能至关重要,我们不仅通过基于数据页维度的并行提高了性能,还对复制中的必要流程进行了优化,例如在MTR日志中增加了一个长度字段,从而减少了日志Parse阶段的CPU开销,这个简单的优化就能减少60%的日志Parse时间。我们还通过复用Dummy Index的内存数据结构,减少了其在Malloc/Free上的开销,进一步提高复制性能。
  4. Redolog的顺序写性能对数据库性能的影响很大,为了减少Redolog切换时对性能的影响,我们后台采用类似Fallocate的方式预先分配日志文件,此外,现代的SSD硬盘很多都是4K对齐,而MySQL代码还是按照早期磁盘512字节对齐的方式刷日志的,这样会导致磁盘做很多不必要的读操作,不能发挥出SSD盘的性能,我们在这方面也做了优化。
  5. POLARDB的Replica节点,日志目前是一批一批应用的,因此当新的一批日志被应用之前,Replica上的读请求不需要重复创建新的ReadView,可以使用上次缓存下来的。这个优化也能提高Replica上的读性能。
  6. POLARDB对临时表也做了一些优化,例如在临时表上可以把Change Buffer关掉,临时表的修改可以少做一些操作,例如在应用日志的时候可以不对索引加锁等。
  7. POLARDB也继承了AliSQL几乎所有的性能优化点,例如,日志核心函数log_write_up_to的优化, LOCK_grant锁的优化,jemalloc内存分配库的使用,日志Group Commit的优化,Double RedoLog Buffer的优化,Buffer Pool Hazard Pointer的优化,自适应concurrency tickets的优化,只读事务的优化等等,详情可以参考GitHub上的AliSQL首页。
  8. 我们实现了一套按需应用日志的控制算法,即只有在Buffer Pool中的数据页才需要应用日志,同时,不仅仅是后台线程会应用日志,用户线程也会应用日志,这两点相结合,大大减少了应用日志缓慢而造成的延迟。此外,由于使用了自研的PolarFS文件系统,刷脏的延迟和吞吐量与传统的Ext4文件系统有很大的不同,我们对page cleaner和double write buffer(虽然PolarFS支持原子写,但依然可以开启)都进行了优化,例如增加dblwr个数以及调整page cleaner线程的算法和优先级等。

展望未来

POLARDB目前已经公测,但是未来我们还有很多有趣的特性可以做,在性能方面也有很多的优化点,例如:

  1. 目前POLARDB高可用切换和实例可用性检测都需要依赖外部的管控系统,另外一方面,由于Primary和Replica共享数据和日志,如果Replica和Primary之间的通信中断,Replica就感知不到数据文件的状态,在这种情况下,Replica将会不可服务,为了解决这两个问题,POLARDB也会引入类似Raft协议下的自制集群机制,自动检测可用性,自动发起切换。具体实现可以参考RDS金融版三节点实例自制集群的方案。
  2. 可以在Replica节点上引入类似Materialized view的特性,当然这里不是指数据库概念中的视图,而是POLARDB中的ReadView。
  3. 上文提到过,目前的DDL是个强同步点,因此我们需要进一步优化DDL,例如引入文件多版本来解决Replica读的问题。另外,Online DDL很多情况下依然需要拷贝整个数据文件,也许我们可以参考PolarStore COW的思想,把这部分拷贝数据的IO压力分摊到后续的DML中。
  4. 在阿里云,用户有很多上云迁移数据的需求,物理迁移的方式毕竟只能服务MySQL数据库,逻辑的SQL导入才是最通用的解决方法,因此我们需要尽量提高在导入大量数据时的性能。一个简单的优化就是数据按照主键顺序排序,我们记录最后一次插入的BTree页节点位置,后续插入就不需要再从Root节点开始扫描了。我们也可以在导入数据的时候,暂时关闭Redolog日志写入,也能提高性能,类似Oracle Nologging机制。
  5. 在存储引擎的锁机制上,POLARDB也还可以做更多的优化,使用更多的lock free结构,减少锁冲突。我们也考虑使用最新的Futex来取代效率低下的Spinlock,当然这些功能需要经过完善缜密的测试,才能上线。
  6. 由于PolarFS/PolarStore能提供16K数据页维度的原子写支持,所以我们不需要类似double write buffer的机制,并对写数据页的操作进一步优化,例如通过某些机制可以释放数据页在刷盘时hold住的读锁。POLARDB也可以使用PolarFS提供的IO优先级功能,保证日志是以第一优先级刷入磁盘。这些改动能大幅度提升性能,但是与锁机制类似,我们需要做充足的测试。
  7. 此外,我们也会Port官方MySQL 8.0的最新的数据字典结构,因为MySQL支持事务性DDL需要这些必要的改动。

写在最后

总之,POLARDB是阿里云数据库团队为云计算时代定制的数据库,拥有很多优秀的特性,是云计算2.0时代产品进化的关键里程碑之一,也是开源数据库生态的积极推动力。我们将在9月下旬开放公测,期待大家的反馈。

参考文献:

http://www.infoq.com/cn/news/2017/08/ali-polardb https://www.percona.com/live/data-performance-conference-2016/users/zhai-weixiang https://www.percona.com/live/17/users/zhai-weixiang

image.png