共计 6381 个字符,预计需要花费 16 分钟才能阅读完成。
6 月 5 日在“国产数据库硬核技术沙龙 -TDSQL- A 技术揭秘”系列分享中,5 位腾讯云技术大咖别离从整体技术架构、列式存储及相干执行优化、集群数据交互总线、Fragment 执行框架 / 查问分片策略 / 子查问框架以及向量化执行引擎等多个方面对 TDSQL- A 进行了深刻解读。
其中腾讯云数据库高级工程师 - 陈再妮,对于“TDSQL- A 海量数据交互之道及企业级数据库能力”,进行议题分享。没有观看直播的小伙伴,可不要错过本次分享内容的文字实录。
以下内容为现场分享实录:
TDSQL-A 是腾讯基于开源数据库 PG 自主研发的分布式剖析型数据库系统,最早可追溯到 08 年,到当初曾经经验 10 余年打磨,团队领有数十篇外围研发专利。TDSQL-A 全面兼容 PostgreSQL,高度兼容 Oracle 语法,采纳无共享架构,反对行列混合存储,在具备业界当先的数据分析能力的同时还具备残缺反对分布式事务 ACID 的能力。
上面是 TDSQL- A 的总体架构,两头是数据交互总线,它将整个分布式集群的各个节点有机地联结起来,负责整个集群中所有节点数据的交互。这个数据交互总线,咱们称它为 FN(forward node),FN 通过业界当先的集群内通信技术,能够反对上千台节点的超大规模集群,十分实用于 PB 级的海量 OLAP 场景。
1
FN 设计与部署形式
1.1 原分布式执行框架
对分布式系统来说,数据重散布是无奈防止的,就如下图中所示的,当某个表的 hash join 的字段不是散布键时,咱们就须要进行数据重散布。在数据重散布的过程中,咱们须要创立相应的连贯和过程。
假设咱们零碎当初有 N 个 DN,DN 即数据节点,在数据重散布过程中每个 DN 都要跟其余 DN 之间建设连贯,那么这个零碎中的连接数就是 N N。如果有 M 层 Join,那就是 M N N 个连贯,再假定咱们零碎的并发数是 X,在这个根底上再乘以 X,那就是 X MN N 个连贯。
这样的话,当整个集群规模达到千百台、并发为上百个时,整个集群的连接数将会有上亿个。这个连接数是十分宏大的,此外,如果连贯太多创立回收 socket 也会成为瓶颈。为了解决这个问题,咱们引入了数据交互总线节点 FN(forward node)。
1.2 优化后的分布式执行框
咱们在每台服务器上都部署了一个 FN,用于跟其余节点之间的数据通信,图中用红色标记的节点就是 FN。FN 通过 FID,src_node_id, dest_node_id 来进行网络数据路由。而 FID 则标识了查问的 RemotSubplan。整个业务逻辑归纳起来就是,我是谁,我从哪里来,要到哪里去。
通过这种路由形式,在零碎中有 N 个 DN 数据节点的状况下,不论整个零碎的查问有多简单,并发量有多大,整个集群之间最多有服务器个数 N *(N-1)个 socket 连贯,无效升高了整个集群内的连贯数量。此外,本机节点通过共享内存进行数据交互能够不走网络,这样就能够反对超大规模的集群部署。
1.3 部署构造
为了将服务器资源充分利用起来,个别状况下咱们会在服务器上部署多个 CN 或者 DN。比方下图中这个服务器上就部署了两个 DN,而 FN 咱们只须要在每台服务上部署一个即可,也就是说,一个 FN 能够服务于这个机器上的多个 CN/DN 节点的通信。
FN 实际上也是一个 postgres 过程,与 CN、DN 或者 GTM 属于平级的关系。FN 过程在启动时,能够采纳参数 -Z forwardnode 来标识该过程为 FN。把 FN 设计为 postgres 过程的长处在于能够复用 PG 的元数据管理、参数配置等。退出 FN 的次要目标,在于缩小 DN 之间、CN 和 DN 之间数据交换时创立的连贯,从而保障大规模集群下网络连接不成为瓶颈。
1.4 通信音讯
在分布式系统中,节点之间的音讯个别分为两种类型; 一种是管制音讯,走的是控制流;一种是数据音讯,走的是数据流。
在 TDSQL- A 中,管制音讯个别用于元数据的散发治理和命令的传递。传递形式跟 PG 是统一的,采纳的是 Libpq 协定。数据的传输通过 FN 来进行,采纳的是 TCP/IP 协定,FN 之间建设 Socket 连贯,来进行数据的传送。
管制音讯的利用场景能够归为以下三类:
CN 向 DN 下发 plan 或者 sql,commit/rollback 等执行命令
CN/DN 向本机 FN 发动启动时节点的注册 / 退出时节点登记申请
不同机器上的 FN 之间互相注册 / 登记申请(FN 跟 FN 之间,也是通过注册、登记进行交互的)
对于数据音讯,它须要通过 FN 来进行转发。这里咱们又能够把它细分为不同服务器之间、同一服务器外部这两种利用场景。
在不同服务器之间,CN、DN 只须要与本机的 FN 进行交互,不再须要与其余 CN/DN 之间进行连贯。FN 依据目标节点的 ID,去和目标节点的 FN 进行连贯,这是服务器之间的转发。
在同一服务器外部,CN/DN 与 FN 通过共享内存来进行通信。比如说,DN 通过共享内存将数据传递给 FN,FN 再通过共享内存将数据传输到本机器上的其余 DN,这样就不须要进行网络通信。
不论是在服务器之间,还是同一个服务器外部,都是由 FN 进行解决,这样的话,能够做到数据通信的对立治理。咱们也不必放心 FN 会成为瓶颈,因为在 FN 外部也能够配置为多线程进行运行。
1.5 集群初始化
因为引入了 FN,咱们须要对整个集群的初始化进行调整,调整后的流程如下图所示。
初始化过程先是 GTM,再到 FN,最初是 CN、DN。须要留神的是,咱们要保障 FN 启动后,本机上的 CN、DN 再启动,这是因为本机上的 CN、DN 启动后须要向本机的 FN 进行注册,只有注册胜利才可能对外提供服务。敞开程序恰好与启动程序相同,也就是说,敞开的时候先敞开 DN、CN,再敞开 FN,最初是 GTM。
2
FN 数据发送与接管过程
2.1 执行打算
咱们举一个例子。有两张表,一个是 A 表,一个是 B 表,它们都有两列,f1 列作为散布列,f2 不是散布列,咱们要进行一个 join 的查问:B 表用的是 f2,它不是一个散布列,这样的话就须要进行数据重散布,就产生了数据的交互。
后面提到 FN 是通过 FID、原节点 ID、目标节点 ID 进行数据路由,原节点 ID、目标节点 ID 很分明不赘述,先重点理解 FID。
FID 在生成执行打算的时候曾经确定,整体执行打算如下:如右下角的方框所示,先对 B 表进行数据扫描,扫描完之后把数据重散布发送到其余机器节点上,其余机器节点收到这部分数据后进行一个 join,join 完结之后再进行投影。
扫描之后再发送进来这一部分,对应的 FID 是 FID2;join 后果发送数据这一部分,对应的是 FID1;最初收到数据做投影的是 FID0——只有牵扯到数据交互,就会有 FID 来进行标识。这个 FID 是 GTM 来进行统一分配治理,能够保障任意时刻都是全局惟一,这也就能标识”我是谁”这个逻辑。
2.2 数据传输
针对下面的执行打算,咱们来具体看一下数据的传输过程。在执行 FID2 的时候,DN1 上的数据须要传输到 DN2 上,DN2 上的数据也须要传输到 DN1 上。咱们先看 DN1 怎么到 DN2 这个链路。首先是 FN1 通过共享内存,拿到 DN1 上的数据后,发现要去的是 DN2,而且当初执行的是 fid2 这个执行打算,它就把这部分数据通过 fid2 发送了进来。服务器 3 上的 FN,就会在本人的 fid2 的接管队列中收到这部分数据,收到这部分数据后,再通过共享内存传递给这个服务器上的 DN2,这样数据就传送过去了。同样的,DN2 上的数据也是这样传送过来 DN1 的。在 DN1 和 DN2 实现数据传输后,须要把 Join 后果数据传送到 CN 下来做投影,此局部对应的是 fid1,CN 上的 FN 作为一个目标节点,它会从 fid1 的接管队列中收到来自于 FN1、FN2 的数据,而后通过共享内存传递到 CN,CN 再返回到客户端,这样就实现了整个数据传输的路由过程。
2.3 FN 外部架构
下图是 FN 外部的具体架构。FN 外部大略可分为四类:
元数据管理,治理与其余节点通信的所需的信息,比方节点 ID、注册过程 ID、FID 应用状况等;
针对每个 DN 节点要进行数据传送的共享内存,个别共享内存中存储的是一些 DN 节点的统计信息、拥塞管制信息;
FN 作为发送者,这里会有一些发送的共享内存、发送队列、发送线程;
FN 作为接收者,会有一些接管的共享内存、接管线程和接管队列。
2.4 数据收发流程
模块是怎么相互合作来进行发送、接收数据的。
首先咱们来看一下数据发送过程。后面提到,CN/DN 启动的时候须要向本机的 FN 进行注册,注册时 FN 就会为注册的节点调配好内存空间,放在 buffer pool 外面,留待当前备用。这样的话,当前注册节点的这些 DProcess 过程 (就是真正去解决用户申请的过程) 它调用接口向 FN 申请共享内存,如果要发送,就把本人那局部的数据放到申请的内存外面去,放完之后,将这部分数据放到发送端 FN 的 Fragment 的音讯队列里去,它就返回了。FN 外部会有调度线程,会依据数据的 FID 从 Fragment 队列中读出来,看这个音讯要去的目标节点是哪里,把它调度到对应的目标节点的发送音讯队列外面去。FN 外部有一个发送线程,发送线程就会真正地进行发送,发送完之后将那局部内存开释回 buffer pool,这是整个发送过程。
接收端这边,收到数据也会向对应的 buffer pool 申请一块内存来寄存这部分数据。而后把这部分寄存接收数据的内存挂载到接管队列外面,接收端的 FN 也有本人的调度线程,会查看接管队列外面的音讯的 FID,依据 FID 再把它调度到对应的 Fragment 队列里去。最初 DN2 端这边的 DProcess 生产过程(用于解决用户申请),它会从 Fragment 生产队列中把对应的数据读出来,读出来后把这块内存再开释回去,这样就实现了整个 FN 外部数据的收发流程。
3
企业级 Oracle 兼容能力解读
3.1 分区表能力
首先是最罕用的分区表能力。TDSQL- A 反对 range、list、hash、高性能等距离分区,并且能够反对多级分区级联,在分区表的拜访办法上,也全面兼容了 oracle 的语法,除了能够间接拜访子表外,还能够关联父表名字来进行拜访。还能够反对 Update 分区字段的值。
就像下图中的例子所示:0-30 是一个子表,30-60 是一个子表,咱们能够把这个表外面的 id,即分区键,把它改成不属于它以前这个分区范畴内的数据,改了之后 TDSQL- A 外部会主动批改这条数据,将它由以前的分区挪到新的分区外面。以前的分区就查不到这条数据了。
除此之外,咱们还反对分区子表的合并拆分能力、新加分区时 default 分区主动挪动的能力。
咱们先来看分区子表的拆分与合并。随着工夫的推移,在应用过程中,零碎的分区会越来越多,为了方便管理,很多用户就会想将晚期的分区进行合并,TDSQL- A 也像 oracle 一样提供了这样的能力。像图中右边所示,它能够通过 merge partitions 将 1 月份和 2 月份的分区进行合并,造成一个稍大的分区,这样就能够无效地去缩小分区的数量。
分区的拆分刚好与合并相同,对于用户常常拜访的热点数据,如果这些热点数据所处的分区内数据太多的话,每次就会扫到很多不必要的数据,咱们能够通过拆分,将热点的分区拆分掉,拆分之后,在后续进行热点拜访的时候,比如说我要拜访“50”这个数据,我就只扫描 30-60 的子分区,0-30 的分区就不须要扫描,这就能无效缩小数据扫描,进步查问效率。
分区表个别会有默认的 default 分区,用来存储不属于其它分区的数据。在下图这个例子中,比如说 2019 年 12 月份的数据,还有 2020 年 3 月份的数据,它都不属于后面已创立的这两个子分区,但如果用户在之后创立了 2020 年 3 月份这个新分区的话,咱们数据库就会主动把这部分属于这个分区的数据从 default 这个默认分区,挪动到对应的分区里。之后 default 分区中就不蕴含这部分数据了,只有剩下的其余数据。这相似于 oracle 创立了分区之后 default 分区会主动挪动的性能。
3.2 数据治理能力
TDSQL- A 反对不同子分区存储到不同的节点组,不同的节点组关联着不同机器,通过这样的计划能够将热数据存储在配置好的机器上,冷数据存储在略微差点的机器上,以此来实现冷热数据的分级存储,升高用户数据的存储老本。
3.3 存储过程能力
另一个重要的 Oracle 兼容能力就是存储过程,TDSQL- A 中也是反对的。比如说,存储过程中能够指定,在 i 是偶数的时候,对这个事物进行提交,它是奇数的时候,对它进行回滚。这样的话,最终执行实现之后,这个数据表中就只有偶数的数据。
3.4 函数扩大语法能力
此外,为了全面兼容 oracle,TDSQL- A 的函数在创立调用语法上也进行了适配。比如说创立的时候,能够不像 PG 那样指定用 $$ 进行突围,能够做到像 oracle 一样以斜杠来进行结尾。如果有空参数的话,也能够不须要括号。
咱们还能够在任意 statement 前或者是 block 前去增加 label,而后通过 goto 去跳转到指定 label 里去。如果是原生 PG 的话,个别只有循环后面能力增加 label。这些都是属于 TDSQL- A 本人开发的新性能。
在函数下面,咱们还增加了对 WITH FUNCTION 的语法反对。比如说这个例子中 SELECT 调用的这个 add_fnc 函数,就只对这个 SELECT 有用,对于其余任何查问都是没有用的。而且如果这个 WITH FUNCTION 函数的函数名跟零碎中其余函数名重名的话,这个 SELECT 中的函数优先级是高于其余函数的。
3.5 PACKAGE 反对
和存储函数相干的 package,咱们也是反对的。通过创立这样的一些包,用户能够将一些罕用的函数分装到一个包里去,之后能够指定一个包来进行相应的调用。这里须要留神的是,创立的话,应该是先创立包再去创立一个包体,删除的话刚好是相同的。
3.6 ROWID & ROWNUM 反对
很多用户常常应用的 oracle 中典型的 ROWID 和 ROWNUM,TDSQL- A 也是反对的。如果用户在建表的时候,指定了 WITH ROWID 这样的参数,这样建进去的表在前面进行查问时,就能够指定要查的这个 ROWID,就能够看到它惟一标识了这一行的数据,并且在进行数据变更之后,依然能够保障这个 ROWID 不变。跟 ROWID 相近的还有 ROWNUM,但实际上 ROWNUM 跟它有很大区别,它不是真正存储的,它只是用户在进行查问之后,对返回的记录进行编号。
3.7 MERGE INTO 语法反对
TDSQL- A 还反对 MERGE INTO 语法。咱们增加了对 MergeStmt 子句的解析,也减少了 MERGE 命令,能够做到将两个表进行 MERGE 合并。像这个例子中所示,将 MERGE INTO 到 test1 里,应用参考 test2,如果匹配上的话,就对 test1 的数据进行更新,如果不匹配,就能够通过这个语法,将 test2 外面的数据全副增加到 test1 里去,达到合并的目标。、
3.8 Start with connect by 反对
Oracle 里的 start with connect by 档次查问,TDSQL- A 也是反对的。它实际上是进行了树的中序遍历。咱们是通过一个递归 CTE 来进行实现的,最终实现档次查问的后果。
3.9 PIVOT & UNPIVOT 反对
TDSQL- A 还反对 PIVOT 和 UNPIVOT 函数。PIVOT 是一个把行数据转成列属性的函数,UNPIVOT 刚好相同,是将列属性转成行数据。大略的实现办法是:对于 PIVOT,针对不在这个 in 中的这些列,把它转换为一个 grop by 外面的字段,来进行实现。对于 UNPIVO,把这些数据转换成一个 lateral join 来进行实现。这些比拟有特点的 Oracle 罕用函数,TDSQ- A 都是反对的。
3.10 其余兼容能力
此外咱们还反对 Oracle 中 List AGG、SQL hints、同义词、Dual 表、各种日期、工夫、字符串、表达式等罕用函数,能够做到 Oracle 罕用语法的 90% 以上兼容。