查看原文
其他

实现分区表性能提升超10倍,解密TDSQL PG版开源升级特性

陈再妮 腾讯云数据库 2022-10-13

1月11日,腾讯云TDSQL PG开源版(开源代号TBase)再升级:分布区表关联查询性能(join)提升超10倍,同时提升了产品在分布式场景下的易用性,增加灵活可用的功能组件。


该升级版本在第十一届PostgreSQL中国技术大会上正式公布,同时更新文档已同步在GitHub上。依托社区和内部业务系统的实践检验,TDSQL PG开源版基本保持每月一次小升级、每半年一次重大升级的节奏,助力众多开发者应用前沿数据库技术。


本期将为大家深度解读TDSQL PG开源版升级特性,具体包括:分区表功能增强、异地多活易用性增强、分布式死锁自动检测并解锁功能、2PC残留自动检测并清理功能等硬核干货。



TDSQL PG版

开源发展历程


TDSQL PG版(原名TBase)是腾讯自主研发的新一代分布式国产数据库,其具备业界领先的HTAP能力,属于MPP无共享架构,在企业级安全方面采用三权分立安全体系。        



2020年,腾讯云正式宣布数据库品牌TDSQL的全新战略升级计划。原有的TDSQL、TBase、CynosDB三大产品线统一升级为“腾讯云企业级分布式数据库TDSQL”。全新升级后的腾讯云TDSQL涵盖分布式、分析型、云原生等多引擎融合的完整数据库产品体系。TDSQL PG版则是TDSQL系列产品之一,主要针对HTAP场景。


下图是TDSQL PG版的总体框架。左上角为事务管理器GTM,负责整个集群的事务管理和全局事务的协调。右上角为协调节点CN,它是业务应用访问的入口,每个节点对等,业务连接任意节点,最终返回的数据都相同。右下角为数据节点DN,每个DN只存储部分用户数据,所有DN一起组成完整的数据集。最左边和最下边的部分属于管控系统,负责系统的资源管理和告警监控。  

                

TDSQL PG版最早可追溯到2008年,2012年其发布第一个分布式版本,2015年其发布第二个分布式版本。截至目前,TDSQL PG版已经在众多市场客户中得到应用,包括数字广东、云南公安、微信广告、微信支付等。值得一提的是,TDSQL PG版在2019年中标PICC,成为行业内第一个成功落地保险核心系统的分布式国产数据库。 



TDSQL PG版在2019年11月7日正式宣布开源。开源地址在Github平台上,下图是供广大开发人员使用参考文档的wiki地址。

                

TDSQL PG版开源对用户有重要价值,分别体现在:

  • 版本经过大量业务验证,成熟稳定,开源版本和腾讯自用版本共基线,能够帮助用户快速构建核心业务。
  • 同时支持OLTP/OLAP能力,提供一站式数据库解决方案。
  • 安全可控的企业级分布式数据库能力,腾讯专业研发运营团队持续投入开发和维护,推动社区进步。
  • 团队积极回馈开源社区,即时响应用户需求,努力创造开源社会价值。


TDSQL PG版

开源特性回顾


2021年7月,我们进行了TDSQL PG版 2.2.0版本的升级。2.2.0升级主要包括四个方面:内存管理优化,提供会话内存视图,进行优化管理;优化分布式执行器、优化器;分布式调优性能增强;分布式执行可视化能力增强。
                

2.1 执行器/优化器优化


我们将子查询在内部优化成一个关联查询,提高查询效率。以视图中的表格为例,需要根据表A中的ID找到表B中每一条对应ID的数据,再用表A的值与表B中值的MIN进行比较。在优化前,因为这是一个子查询,假如表A有1000条记录,则A中每一条记录都要在表B进行全量扫描。做完这个查询至少要将表B扫描一千次,这样做的成本非常高。在优化后,因为表B需要取B的MIN最小值,且我们需要利用id做join,所以可以先扫描一遍B表,根据id先进行聚集,把每一个id最小的MIN值取出来,生成聚集表,最后聚集表与表A进行hash join,因此我们只需将B表进行一次扫描即可。
                
我们还对distinct进行性能优化。在优化前,需要将所有数据从DN节点拉到CN节点,由CN节点进行distinct的去重操作。因为分布式系统数据量特别大,如果全部拉到CN,会导致数据量大,网络负载高,CN也会成为单点瓶颈,难以发挥分布式特性。我们针对此类情况进行优化。先在DN本地进行部分的distinct去重,使得每个DN可以并行计算,并行后将结果全部发到CN上,由CN进行最终的去重操作,减少了大量的网络交互,充分发挥出分布式数据库的特性。
                
在对优化器进行优化后,我们取部分TPCDS标准测试集进行测试,发现有些SQL的性能可以提升上千倍。
                

2.2 内存管理优化


我们提供Memory的内存视图详情,能够详细看到每一个进程占用的内存情况。除了对视图进行增强外,我们还对内存使用进行详细优化。
                
下图展示的是假设有一个用户连接,这个连接在访问一万张表后的内存占用情况(访问完该连接还没有退出、仍处于空闲状态时的内存占用情况)。从下表中可以看到,TopMemoryContext上出现数量级增长,实现10倍以上提升,CacheMemoryContext也实现20倍的提升。
               
针对明显空闲的连接却占用大量内存的不合理情况,我们进行了优化我们将内存的使用分为两部分:第一部分是RelCache,即用户数据表的Cache;第二部分是Cat Cache,即元数据的Cache。如果需要对某个Rel进行Cache,我们会将新来的项放在Relation LRU List的最前面,此后每次来新的项我们都往前面加。当LRU项目超过预定意义的参数时,我们会从后往前去遍历LRU,将引用计数为0的Relation的内存项释放掉。Cat LRU元数据缓存也同理,我们将新来的某条元数据缓存,将其放在Cat LRU List的最前面,再从后往前进行遍历,将引用计数为0的项进行删除,从而释放内存。由此可以将部分占用内存大且低频使用的空闲连接,通过LRU将其替换出去,使会话内存减少55.7%。
                
对于分布式系统,除了单点外还要考虑分布式内存管理。当一个应用程序连接到CN后,CN需要跟DN进行数据通信。但它与DN的通信的DN进程需要向本地的Pooler申请连接,Pooler会将本地缓存的部分到DN的空闲连接分配给CN,此后CN才能与DN进行通信。Pooler本地缓存了很多到DN的空闲连接,实时准备为CN提供服务,以至于存在许多空闲连接。对此,我们在Pooler内部上线实时检测空闲连接的功能,将大于配置参数的连接释放掉,使得空闲连接占用减少89.3%。
                
经过优化后,在2CN、2DN的情况下,依旧是一个连接在访问一万张表后的内存占用情况,优化前总内存占51.4M,经优化后只占5.4M。节点内部DN自动释放空闲连接,空闲连接直接减少89%,经过RelCache LRU替换后,CN连接内存减少55%。
                

2.3 分布式执行可视化


以下图为例,表A和表B进行join,A表作为分布键,B表作为非分布键,我们进行join时要将join的过程下推到DN节点执行,但因为表B不是分布键join,所以需要对表B按f2字段进行重分布。在重分布过程中,DN1上有一个backend进程,需要扫描本地的表B,把扫描到的部分数据留给本地的DN节点,部分数据则重分布到DN2数据节点。所以一次重分布中,本地会新创建一个DN1的backend进程进行数据扫描,还要创建另一个backend进程负责发送数据给DN2。
               
利用分布式可视化的能力,可以查到任意一个用户的session id,查到session id后,可以根据session id查到对应的在不同CN、DN上的各个进程目前在做什么。比如这个节点我们需要做hash join,因为需要根据f2字段做数据重分布,现在在等待重分布的数据,另外一个进程是扫描表B,扫描完后会提供给前面的进程,才能进行hash join,CN1则是在执行查询事务。通过分布式执行过程的可视化视图,我们可以详细知道一个SQL下去各个节点在做什么。
                
下图展示的是分布式可视化功能的使用方法,详情可参考Github上的wiki地址:     
         


TDSQL PG版

开源重磅升级


TDSQL PG开源版此次升级V2.3.0版本包括四个方面:分区表能力增强、异地多活易用性增强、分布式死锁检测、2PC残留自动清理。
                

3.1 分区表能力增强


针对分区表能力,本次TDSQL PG版升级,整体响应速度更快,有效应对此前业界仍然面临数据量大时读写性能慢等问题。新版本通过分区表功能增强,包括增加hash分区类型、支持default分区子表创建、分区父表索引操作自动同步子表等,实现分区表便捷管理;同时,新版本实现了分区剪枝性能提升30%,分布区表关联查询性能(join)提升超10倍,完美解决查询效率问题。

具体来说,在原有支持range和list分区表的基础上,TDSQL PG版新增hash分区表功能。用户在创建时可以指定该分区表为hash分区表,再指定分区键,同时需要指定hash模数和余数。

TDSQL PG版还新增了default分区。在上一版本中,在创建分区表且创建子分区时,如果没有创建default默认分区,用户插入数据时,如果插入不属于指定分区的其他数据,会出现报错。在本次升级后,如果插入的数据不属于其他指定子分区,所有数据会落到default分区。比如下图右边的例子,2019年12月和2020年3月的数据,都不属于指定的其他子分区,因此会落入default分区。其优点是不需要报错,同时必要时可以查询不属于指定分区的其他零散值。该功能目前仅支持range和list分区,不支持hash分区。因为在指定hash的模数和余数后,数据就必然会落在指定分区,不存在不属于指定分区的情况。
               
TDSQL PG版也支持分区键更新。在上一版本中,如果要更新创建表时指定的分区键,就会出现报错,不允许更新。新版本对此进行升级。在下图左边的例子中,一个分区子表存储0到30的数据,另一个分区子表存储30到60的数据,用户可以对分区键即id键进行更新,将id从25改为50,数据会自动从原来的分区转移到新的分区。

此外,TDSQL PG版还支持分区父表建索引自动同步子表。在上一版本中,如果要为分区表建索引,只能给某个指定子表建分区,不能给父表建分区。在新版本中,我们对此进行优化,支持在父表上指定建分区。在下图右边的例子中,假设要给tbl表的id字段建索引,在创建后它会自动将索引同步到所有子分区上,比如2020年1月分区、2020年2月分区和default分区。用户只需要在父表上执行一条SQL,它就会自动在所有子分区上全部创建该索引,其优点是方便用户对分区里的索引进行维护。我们也支持创建唯一分区,但需要包含分区键。
                
除了分区能力的增强,TDSQL PG版还引入partition-wise join,对原有性能进行提升。假设有两个分区表,分别为A表和B表,它们的分区数量完全相同,且分区的字段类型一致。如果没有partition-wise join,在进行join时,需要先将A表中所有子分区的数据全部捞取,再将B表中所有子分区的数据全部捞取,再用A表全量数据与B表全量数据进行join,这种操作方式的数据量非常大。在引入partition-wise join后,因为两个分区键为同一类型,且分区数量相同,我们可以将A表的分区1与B表的分区1进行join,A表的分区2与B表的分区2进行join。我们并不需要将A表的全量数据与B表的全量数据进行join,只需要将对等的分区进行join即可,从而使性能得到大幅提升。

下图是该功能实际效果的测试对比。在partition-wise join打开的情况下,与上一版本相比,最新版本的性能有10倍的提升。
                

3.2 异地多活的易用性增强


另一重要升级,是异地多活的易用性增强。原先单活时跨区接入延时大,一旦发生故障服务和数据库都需要切换,流程复杂;而异地多中心接入时延小,业务在一个中心内能完成闭环,秒级即可完成切换。本次升级新增多活插件化功能,方便用户安装配置,提升了产品易用性。

异地多活主要区别于传统两地三中心的单活模式。在单活模式中,假设主库在南方,北方的服务要写入主库,因为地域相隔太远,延时会非常大。如果出现南北网络故障,北方用户就无法访问主库,出现不可用情况。如果采用异地多活模式则不存在上述问题。我们可以在北方多设立一个主库,北方的服务写入北方主库,南方的服务写入南方主库,南北主库之间进行双向复制。其优点在于南北主库各自都有全量数据集,且业务可以就近接入,在一个中心内部实现闭环,即使南北出现网络故障也不会影响服务。
                
异地多活采用双向复制功能,下图是异地多活架构示意图。区域1是深圳,区域2是上海,我们将区域1的数据节点作为发布端,将区域2的CN节点作为订阅端(CN可以订阅作为发布端的数据节点上的数据),在完成数据订阅后,CN需要经过一个路由才能将订阅到的数据写入本地的DN节点上,这样我们支持了异构集群之间的同步,一个CN可以订阅多个数据节点。
                
在新版本中,我们对易用性进行增强。通过tbase_subscription工具,用户可以利用SQL直接创建整个同步的异地多活过程。创建时还可以指定订阅的并行度,从而提高订阅效率。此外,因为一个CN可以订阅多个DN,用户可以将订阅任务下发到不同CN上,使CN进行负载均衡。
                

3.3 分布式死锁自动检测


以下图为例。假设有两个CN,分别为CN1和CN2。有两个用户,用户1连到CN1,产生一个session,用户2连到CN2,也产生一个session。用户1使用显示事务查询A表数据,用户2也采用显示事务查询B表数据。用户1查询完A表数据时,想将B表进行删除。因为是分布式系统,CN2上也存在B表,需要同步进行删除。如果要删除B表则需要B表的排他锁。但这时在CN2上,用户2刚启动显示事务查询B表,获得B表的共享锁,这就产生了锁冲突,反之亦然。

在这种情况下,CN1有A表的共享锁,需要等B表的排他锁,CN2有B表的共享锁,又要等A表的排他锁,两个事务之间就形成了分布式死锁。
                
针对这种情况,我们引入了pg_unlock工具。该工具可通过extension安装,快速便捷。用户安装后可以查到分布式死锁之间的依赖关系。比如在上面的例子中,CN1是一个依赖于CN2的事务,CN2是一个依赖于CN1的事务。用户还可以查到集群中一个死锁的详细信息。比如CN1上的drop b需要B表的排他锁,CN2上的drop a也需要A的排他锁,它们之间都是求而不得存在死锁。我们可以通过执行pg_unlock execute来解开死锁,解开的方法是回滚掉一个事务。在回滚事务时,我们会在内部进行优化,计算出回滚代价最小的事务并进行回滚。
                

3.4 2PC残留自动清理


以下图为例,假设有两个CN,分别为CN1、CN2,有两个DN,分别为DN1、DN2。用户连进来创建A表,再进行prepare transaction,该事务命名为create a。我们通过另一个CN连接进来,可以查询处于prepare状态的事务有哪些,可以看到CN1上有一个prepare事务叫create a,CN2、DN1、DN2也有。

用户继续执行在DN2上将create a事务进行回滚的操作,此后该操作会话直接退出。退出后我们再去查处于prepare状态的事务,发现CN1、CN2、DN1各有一个create a事务,但DN2上没有。因为上述会话在退出前,对DN2上的create a事务进行回滚,相当于该事务不存在prepare状态。由于会话已经退出,该事务永远不会终止,就出现了2PC残留的情况。
                
针对这种情况,我们引入了pg-clean工具。通过pg_clean check transaction,用户可以直接查到目前有哪些2PC残留事务,以及这些事务的名称、参与的节点等。通过pg_clean execute,我们可以对2PC残留事务进行自动清理。这实际上是将残留事务进行abort操作。因为CN1、CN2、DN1上的事务都处于prepare状态,但DN2上执行了abort,它只能将该事务abort,不能将其提交,否则会出现部分提交数据不一致的问题。我们能够根据事务状态自动选择回滚或提交,从而将2PC残留事务进行清理。
                


TDSQL PG版

开源生态未来发展计划

    
在下个版本中,我们计划在以下四个方面对TDSQL PG开源版进行加强:
  • 存储能力加强。支持透明压缩,减少磁盘存储成本;提供Direct IO能力,提高某些情况下的写效率。
  • 索引优化。支持全局索引,提升非分布键查询性能。
  • 优化器能力提升。提高执行效率,提升业务查询及写的性能。
  • 分布式状态可视化。优化全局session可视化、query可视化、分布式锁可视化、分布式事务状态可视化,帮助用户更好地掌控系统。


开源的本质是以技术开放促进技术创新。腾讯云对TDSQL PG版不断开发和投入,版本经过大量业务验证,成熟稳定,开源版本和腾讯自用版本共基线,能够帮助用户快速构建核心业务,将持续给客户带来价值,与广大开发者共同打造可持续的国产数据库开源生态。

同时欢迎各位感兴趣的小伙伴,与我们一起共同助力国产数据库技术创新与开源生态发展!


-- 更多精彩 --

腾讯云在PostgreSQL领域的‘‘再次突破’’


一文详解TDSQL PG版Oracle兼容性实践


↓↓点击阅读原文,了解更多优惠

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存