在“国产数据库硬核技术沙龙 -TDSQL- A 技术揭秘”系列分享中,5 位腾讯云技术大咖别离从整体技术架构、列式存储及相干执行优化、集群数据交互总线、Fragment 执行框架 / 查问分片策略 / 子查问框架以及向量化执行引擎等多个方面对 TDSQL- A 进行了深刻解读。错过直播的小伙伴有福啦,明天带来本次系列分享中腾讯云数据库高级工程师伍鑫老师主题为“TDSQL- A 列存储设计原理及执行优化详解”的文字版。
TDSQL- A 是腾讯首款分布式剖析型数据库,采纳全并行无共享架构,适应于海量 OLAP 关联剖析查问场景,可能反对 2000 台物理服务器以上的集群规模,存储容量能达到单数据库实例百 P 级。其中,TDSQL- A 还具备自研列式存储引擎,能反对行列混合存储,对分析模型下的查问语句性能做到了极致优化。
TDSQL- A 整体架构
在开始之前,咱们先来理解下 TDSQL- A 的整体架构,它分为这几个模块:
第一是 下层的 GTM 事务管理器,它次要是负责全局事务的治理,协调机群的事务,同时治理集群的整体对象。右上角是协调节点,它是业务拜访入口。协调节点模块是程度对等的,也就是说业务连贯到任何一个协调节点上,都可能取得到统一的数据库视图。
第二是 下方的数据节点。数据节点也是理论存储数据的节点,每个数据节点只会存储数据的分片,而数据节点之间加在一起会造成一个残缺的数据视图。
而数据节点和协调节点之间是集群的数据交互总线。集群交互总线的目标是把集群外部节点连贯在一起,从而实现整个查问交互。
最初,左侧形容的是数据库内核的运维管控零碎。通过运维治理、实时监控、实时告警、平安审计和数据治理等性能,能够进行自动化的运维,缩小运维人员和用户应用的老本。
自研列存储带来极致优化性能
咱们明天次要分享两个方面,一个是 TDSQL- A 自研列存储,另外一个是基于自研列存储的优化器相干优化。当初先来看看 TDSQL- A 的自研列存储。
说到列存储,置信大家都比拟相熟。列存储对具体的查问模型或者拜访模型自身是有非凡优化的。传统状况下,数据库更多的是偏差事务型的场景,在每次数据写入的时候,都会把整行写到存储下面,一次磁盘 IO 能够拜访所有列。这种场景在面对互联网这种大数据量剖析的状况下,可能有些列在计算的时候并不需要拜访,这会带来 IO 拜访上的瓶颈,同时在后续优化上,也不不便针对特定的计算场景去进行优化。因而列存储也就应运而生了。
每列独自存储,多个列逻辑组成一行,一次磁盘 IO 只蕴含一列数据,这一列的数据可能是一行中的一列,或者是执行中的一个数据,同时在这种编排格局下,更不便去做数据压缩,因为雷同列的数据类型有更高的一致性和匹配度,所以可能会面向很多极致的压缩的场景,也更适宜 OLAP 的计算场景。
TDSQL- A 同时反对行存储和列存储建表,行表和列表之间能够进行相互操作,行列表之间反对混合查问,依据用户的场景去设定不同的行表和列表的定义形式,同时在混合查问的时候也严格保障事务一致性,从而让客户能够比拟释怀地去定义本人的行表和列表。
2.1 TDSQL- A 自研列存储整体设计
这部分次要是介绍咱们针对列存储所做的优化。TDSQL- A 在设计列存储之前就曾经去充沛调研过客户相干的需要,上面这张图就把咱们的整个能力残缺地出现了进去。
通过比拟好的列存储格局的文件编排,能够达到更好的插入或者读取的性能。下层通过 Buffer 进行高速的减速,以及提早物化以及向量化执行,对整个引擎做到了针对性的极致优化;通过残缺的设计,能够比拟好的去优化查问打算以及执行引擎,从而达到一个比拟好的执行成果。
2.2 TDSQL- A 列存储模型介绍
TDSQL- A 中列存储首先会分为多个 Slice 文件,不同的文件能够调配不同的并发拜访的数据变更申请,每一个 Slice 文件标号为 SID,SID 外面会蕴含多个 Row 的信息,每一个列会有一个独自的本人的文件存储,咱们能够通过 RID 来去寻找对应不同行的列的数据。咱们能够简略了解为 SID 和 RID 是一个标识,通过它们能够去反对索引查问或者前面讲到的提早物化外面通过 ID 扫描到相应的数据来进行一个补缺。
咱们在存储格局上还设计了版本列,即通过 xmin、xmax 的相干记录来去反对事务的判断。针对不同类型的数据,咱们又有一些相应的优化机制,定长类型紧凑排列,变长类型构建字典列,来减速数据查找。
同时针对一些排序相干的查问,咱们在存储下面也进行了优化。比如说由索引列来进行高效的排序,或者是 xmin、xmax 相干的计算减速或优化,从而在存储层面提供反对。
2.3 TDSQL- A 基于自研列存储的三大劣势
针对一些有特定特色的场景,TDSQL- A 能够用轻量级压缩算法来做一个更高效的压缩,对于通用的一些场景,通过通明压缩算法也能够达到一个比拟好的整体压缩成果。在例图里能够看到,依据等值数据、递增数据可能会达到上百倍的压缩成果。
高效计算能力也是 TDSQL- A 的一大个性。实际上咱们设计了比拟丰盛的性能来反对 TDSQL- A 的计算优化,次要分为这几个方向:
第一点是 SeqScan 的向量化反对。在底层扫描数据的时候,理论数据量越大,向量化带来的成果和优化的空间也是越大,向量化对下层的简单计算提供了反对;
第二点是 Shared CTE。简略来说,之前的版本可能须要把数据拉到 CN 做,在 TDSQL- A 中,咱们也做了相应的反对,能够分布式的在 DN 下来计算 CTE。Shared CTE 算子会在计算时把数据进行物化。咱们在 Shared CTE 物化时针对性地去借鉴了列存的数据格式编排,包含下面的提到的向量化的计算,以及其余的一些优化,能够更好地借助列存版本在设计上的劣势,或者计算上的劣势来去进步整个场景的计算性能;
第三点是提早物化进一步缩小计算和网络耗费。咱们会针对列存做更多的极致优化来晋升查问性能,提供更大规模数据上的实时查问成果。
TDSQL- A 的并行能力,实际上分为节点级并行、过程级并行以及指令级并行,过程级并行前面我会介绍一下具体优化性外面怎么去进行一个优化,针对并行、提早物化去进行一个对立的布局。
提前物化 or 提早物化?优化器抉择有办法
上一部分介绍了很多列存储以及相干的查问优化办法,这部分咱们次要去具体开展提早物化相干的介绍。
咱们讲提早物化,但实际上并不是所有的提早物化都快,咱们须要去寻找最优的策略和计划,为客户主动调整到去用提早物化还是提前物化。上面先介绍两种物化策略的区别。
3.1 提前物化 VS 提早物化
简略的场景的时候,采纳提前物化,提前把 C1、C2 或者 D1、D2 列全副都扫描进去,去组装成 tuple 进行外部计算,这个时候它的益处是数据只拜访一次。但如果 join 的选择率比拟低的话,咱们就会节约掉很多晚期的网络交互。如果采纳提早物化,且 join 的选择率非常低,C2 列我能够进行一个提早物化,在 join 完结之后我才去把它的数据进行一个补全,这种状况下我可能缩小了很多不必要的扫描和网络交互。
然而对于这两种场景,join 的选择率以及整个不同列的宽度或者数据列在从新扫描时遇到乱序等场景,都会影响到咱们抉择不同策略的布局,并非简略的肯定会抉择提早物化或者抉择提前物化。
这外面提早物化会缩小物化的 tuple 数量,升高 IO、网络、计算压力;同时在基于列存储的拜访上,能够借助计算下面的加强技术来去升高 CPU 的耗费;提早物化还有一个益处就是在扫描数据绝对较少的状况下,须要 cache 的数据量是更少的,像这时候也会变相地进步整个计算的 cache 命中率,缩小 cpu 的耗费。
提前物化的益处是缩小 LM 中不必要的数据 reaccess(须要频繁的屡次拜访雷同的数据块从而导致更高的拜访)。所以提前物化在选择率比拟高的时候,或者自身 join 两头会有数据乱序的状况下,可能提前物化的成果会更好。提前物化外面还有更粗疏的结构化。有两种形式,一种是先对 A 列进行 prediate 计算,这时候它可能曾经过滤了很多数据,再去进行第二列的 prediate 计算。但这种状况下,只能对列存储来去做相应的优化;相应地就是左边这种在晚期扫的时候不论 Predicate,先去把 A、B 两列扫描进去,而后再去进行 Predicate,这样扫描的数据量还是会比拟多,这个也是一个粗疏的优化,能够通过这样的形式去缩小更多的列存储的数据扫描量。
3.2 不同场景利用不同物化策略
这里咱们介绍下 TDSQL- A 优化器是如何针对不同场景对物化策略进行抉择。
比拟晚期的数据库或者是九十年代以前更多的是抉择一个 Rule-based Optimizer(RBO)。简略讲就是通过动态的规定去对逻辑查问打算进行优化,像提前下推,都是通过强制的规定去进行计算的。这里的益处就是我能够比较简单地实现,或在晚期实践比较简单的状况下,通过这样的形式来达到比拟好的出现成果。然而它在面对更简单的查问、算子、优化等这些场景时,可能两头会有互斥,或者很难去进行最终的优化。这种状况下 Cost Based Optimizer 就应运而生,前面会整体介绍通过整个不同 paths 的代价评估来进行整体的估算,实际上像比拟新的或者当初的数据库整合都会相互去达到比拟好的劣势的借鉴。
咱们在 join Reordering 的时候,到底是怎么基于 cost 来进行推算的呢?举个例子,比如说咱们有三张表,在 join 优化器外面它会抉择动静布局的一个算法,最开始会把同一个 jointree 所有 join 参加的表打散成 base relation,这个时候基于 base relation 去进行不同层级的计算。第一层咱们就会抉择这三张表其中两张表来做一个计算,生成相应的一个 path,这条红线就是生成某一个 level 的查询方法的一个门路,对于每个 path 会有相应的代价叫 cost。第二层基于方才第一层生成表的抉择状况,再去补全第三张表,这种状况下绝对于是一个动静布局,两头的一个转移函数次要是通过不同 path 两头去抉择一个 Cheapest cost,通过这种形式来实现整个查问打算的便当,抉择出代价最低的打算,这外面是一个简略铺垫。
思考到并行的状况下,这个问题就会变的更简单一些,同时须要布局一个并行的 path,或者是一个非并行的 path,在每一个转移函数的时候须要去思考,在有并行 paths 的状况下,是否须要在以后这一层把这个并行 path 加一个 gather 节点,来变成一个串行的 path。实际上串行只能跟串行比,并行的数据还没有整顿结束,这种状况下每一层都会去思考所有的串行的 path 外面再去加上 gather 的并行 path,这样整体的 cost 一比照,而后对立来抉择并行还是串行,这也是并行优化外面比拟重要的一部分。
如果再思考到提早物化的话,这个问题就会变的更加简单,就是说咱们在拆分成 base relation 的时候,通过 query tree 能够晓得 join 的时候哪些列是不须要的。比方这个表有 100 列,两头的 join 可能只用了 10 列,能够只生成这 10 列的缺失状态的 path,实际上生成对应的 path 还是能够满足以后第一层场景的。如果我发现 join 须要第三、第四列须要补全的时候,会在 join 之前把这两列通过提早物化算子把它补全进来,这样保障每一层在计算的时候须要的数据是补全进来的。另外在转移函数的时候,也须要去思考失常的 path 怎么去跟提早物化的 path 进行一个比照,须要去把提早物化的 path 补全,将相应的列补全的状况下能力去跟失常的 paths 进行一个比照。这种状况下是残缺的转移函数,这个时候就会比拟好的体现出抉择一个什么样的 paths,最初生成物理的相应打算。
![file](/img/bVcUCtv)
实际上的利用场景会更加简单,像具体的 SeqScan Cost 的计算,怎么思考到提早物化来做一个调整呢?实际上,宽度是有动静的调整,依据不同的状况去进行一个计算。像 RidScan Cost 在去进行一个列的补全的时候,须要依据这个列之前传给我的 RID,基于具体打算的状况去进行思考。这种状况下其实就须要思考 Rid 是不是 sort 的,去扫描的时候,会不会有很多的 re-access,这种状况下会导致 RidScan Cost 比较复杂。第三点拼装的时候 LM Fetch 须要思考以后是不是通过 Remote Scan,或者本节点的 local scan。不同的 cost 的计算也是不一样的,包含第四点 Remote Subpath Cost 计算会大量的升高网络带宽的状况,这个要思考到少扫了 80% 的列的宽度,实际上对网络层也是有很大影响的,这都是要对立地去计算。
这些都会对整个优化器的决策去进行一个调整,最开始生成 base paths 的时候,实际上我是不晓得我到底需不需要去 remote 或者需不需要去 sort,这个时候都会标成 local 或者 skip sort,另外一点就是在几种状况下可能会导致产生变动,比如说 remoteSubpath,因为我收的程序可能是乱序的,RD 曾经乱了,前面再去进行 RD Stand 的时候,通过代价来去评估它是不是能够这样走,或者如果要这样走,我是不是要在物理算子上进行一个排序,以及 Hashjoin 的时候都会有相应的一些影响。比如说最简略的 ARTIST 表面应该是程序,然而它有 join 的时候,读上来程序也是乱的。像 Inner Path 更是如此,Hash table 实际上会对整个程序进行扭转,这些都会对提早物化有很深层的影响,导致优化器外面会有不同的决策、抉择。
3.3 提早物化推动通信效率晋升
通过这些比拟谨严的思考和优化器的调整,咱们更多的参数回归和调整,咱们也达到了比拟好的成果。比如说,1TB 的数据如果了选择率绝对比拟低,10% 的状况下 P60%,2 表 JOIN 可能会有一个工夫上 81.1% 的晋升,耗费工夫的降落以及网络占用带宽的降落;如果是多表的话,会有更好的一个成果。