技术干货丨TDSQL 列存引擎 LibraDB 计算模型的设计与思考

数据库执行器核心关注的问题是性能,那么围绕性能的大前提下,如何把系统资源充分的利用起来,则是执行器首要考虑的问题。

01、背景

虽然乍一看以为只是一本21天教你学画 UML 图的工具书,实际看下来,却是扎扎实实地在教你如何分析你的业务,找到组织的竞争优势。

TDSQL(Tencent Distributed SQL)是腾讯打造的一款企业级数据库产品,具备强一致、高可用、全球部署架构、高 SQL 兼容度、分布式水平扩展等特性,为客户提供完整的分布式数据库解决方案。TDSQL 被人们熟知主要是因为其高性能的在线事务处理能力,并且登顶 TPC-C 基准测试榜单,支持了每分钟 8.14 亿笔交易。

当然除了利用数据库服务高 QPS 的在线事务,大量的客户需要挖掘数据的价值,利用数据库进行数据分析,帮助企业进行更好的进行业务决策,推动的业务的迭代创新,快速适应市场环境的变化。

但是传统数据库包括 TDSQL 为了支持高性能的在线事务处理能力,并且保证业务查询的稳定性,在存储结构上往往选择了行式存储,在执行模型上选择了火山模型,这种计算模型使用的内存比较少,在 TP 这种并发比较高的场景下系统也能提供比较稳定的服务。但是这种计算模型导致了其无法高效的服务分析类查询。

当然部分客户为了能够支持事务以及分析的混合负载(HTAP, Hybrid Transactional/Analytical Processing) , 选择了传统数据库 + 数据仓库的解决方案,但是这种方案为客户带来了高昂的维护成本,需要自己构建数据库到数据仓库的 ETL (Extract-抽取 Transform-转换 Load-加载) 工具,并且数据的实时性以及一致性也得不到很好的满足。

LibraDB 是 TDSQL MYSQL 的列存副本。通过 LibraDB 列式存储能力、向量化并行执行引擎以及为列存存储分布式并行执行而扩展的优化器,让客户能够无需任何的数据迁移就可以在 TDSQL 原地体验到高效地分析能力,另外 TDSQL 的列存存储引擎为高 QPS 的变更、事务的 ACID 进行了针对性的优化,保证了查询数据的实时性以及一致性。

本文从物理执行器的角度来介绍下计算模型的演进过程和优化手段。

02、基础概念

2.1 TDSQL 计算引擎组件

负责 SQL 的语法解析、语义解析、列存、行存逻辑计划的优化、列存、行存物理计划的生成。

2.2 计划片段(Fragment)

Fragment 是物理执行计划的一部分。只有当执行计划被 TDSQL 计算引擎拆分成若干个 Fragment 后,才能多机并行执行。Fragment 是由物理算子构成,另外还包含 Sender 、Receiver 算子。上游的 Fragment 通过 Sender 向下游 Fragment 的 Receiver 算子发送数据。

2.3 计划片段实例(MPP Task)

MPP Task 是 Fragment 的一个执行实例,TDSQL 表在列存副本上面是按照 Tablet 进行存储的,Tablet 内部通过列式存储,每一个 Tablet 包含对应 TDSQL 表的全部数据。优化器将 Fragment 的实例化成多个 MPP Task 处理分布在不同机器上的 Tablet,从而实现数据并行计算。TDSQL 计算引擎确定 MPP Task 的数量和执行 MPP Task 的目标 LibraDB 节点,然后 TDSQL 计算引擎向 LibraDB 节点投递 MPP Task。

参考《Efficiently Compiling Efficient Query Plans for Modern Hardware》和《MonetDB/X100: Hyper-Pipelining Query Execution》这两篇论文,计算节点上的执行器会把 MPP Task 进一步拆分成若干 Pipeline 。每个 Pipeline 会根据 Pipeline 并行度参数而被实例化成一组 Pipe , Pipe是 Pipeline 实例,也是 Pipeline 执行引擎所能调度的基本任务。

2.4 Pipeline

Pipeline 是一组算子构成的链,开始算子为 Source ,末尾算子为 Sink 。Pipeline 中间的算子有一个或者多个输入端和一个或者Source 作为 Pipeline 的起始算子,为 Pipeline 后续算子产生数据,Source 获取数据的途经有:

  1. 读本地文件或者外部数据源,比如 LocalTableScan/RemoteTableScan。
  2. 获得上游 MPP Task 的输出数据,比如 ExchangeReceiverSource。
  3. 获得上游 Pipeline 的 Sink 的计算结果,比如 LocalExchangeSource。

Sink 作为 Pipeline 的末尾算子,吸收 Pipeline 的计算结果, 并输出数据,输出途经有:

  1. 把计算结果输出到外部组件,比如 ExchangeRemoteDataSink。
  2. 把结果发给下游MPP Task,比如 ExchangeDataSink。
  3. 把结果发给下游 Pipeline 的 Source,比如 PartialAGGSink等。

Pipeline 的中间算子,既可获得前驱算子的输入,又可以输出数据给后继算子。

Pipeline 计算时,从前向后,先从 Source 获得 chunk, 输出给下一个算子,该算子处理 chunk,产生输出 chunk,然后输出给再下一个算子,这样不断地向前处理,最终结果会输出到 Sink。对于每对相邻的算子, Pipeline 执行线程调用前一个算子 pull 函数获得 chunk,调用后一个算子的 push 函数将 chunk 推给它。Pipeline 的 Sink 可能需要全量物化,而其他算子,则采用 chunk-at-a-time 的方式工作。

2.5 Pipe

每个 Pipeline 会根据 Pipeline 并行度(dop)参数而被实例化成一组 Pipe, Pipe 是 Pipeline 实例,也是 Pipeline 执行引擎所能调度的基本任务。

03、LibraDB 执行器架构演进

3.1 v1.0 Scatter-Gather模型

如上图所示,执行框架是一个简单的两阶段执行的框架(Scatther Gather 模型),Scatther 任务可以由多个节点(LibraDB)来进行计算,Gather 任务只有一个节点(TDSQL 计算引擎)来进行计算。这里使用聚合计算举个例子如下所示:

代码语言:javascript
复制
select l_orderkey from lineitem group by l_orderkey;

LibraDB 执行器计算流程:

  • Scatter 阶段 :入口节点的 TDSQL 计算引擎 向各个其余的 LibraDB 发送查询请求,要求其返回执行的结果,该阶段是每一个 LibraDB 节点先进行预聚合计算。计算后的结果返回给入口节点的TDSQL 计算引擎。
  • Gather 阶段 :入口节点的 TDSQL 计算引擎继续执行聚合逻辑的最终阶段,将所有 LibraDB 节点上面返回来的预聚合数据在进行 Merge。合并过程只能在单点完成。

当前的计算框架优势在于实现比较简单,缺点也非常明显。当处理复杂 Join 场景的查询或者高基数聚合的场景下,执行性能比较差和内存使用率比较高。

3.2 v2.0 MPP并行计算模型

针对于 Scatther/Gather 模型无法应对复杂 SQL 场景下的性能问题,所以引入了`Sender算子`,`Receiver算子`,整个执行器架构调整成 MPP 的计算模型。TDSQL 计算引擎将用户 SQL 根据 RBO/CBO 拆分成若干个 MPP Task, 采用一次性(all-at-once)投递给 LibraDB , LibraDB 执行 MPP Task 然后返回执行结果。

这里使用 Join 计算举个例子如下所示

代码语言:javascript
复制
select * from lineitem join orders on l_orderkey = o_orderkey;

这个查询语句被规划为 3 个阶段, 扫描数据, 关联计算,返回结果;每个阶段又会被拆分为多个子任务,例如这个查询就被拆分为 5 个任务。

  • 阶段一:2 个节点同时执行 Scan 任务,每个任务执行完之后,把数据根据 o_orderkey 字段 Hash 分桶,分别发送给 2个不同的 Join 任务;
  • 阶段二:2个 Join 任务根据收到的数据,按照 o_orderkey 来做 Join 计算,把结果发送给 TDSQL 计算引擎 Root Task 任务;
  • 阶段三:TDSQL 计算引擎 Root Task 任务收到的数据已经计算好的数据,所以可以直接对数据进行简单的 Merge,然后返回给客户端。

与 Scatther/Gather 模型相比,上述关联计算被分配到多个节点上并行执行了,不仅仅可以加快速度,还可以降低内存的使用,避免内存不足。

3.3 v3.0 SMP PipeLine计算模型

多机并行场景的并行已经通过 MPP 的方案来优化处理了。但是单机场景如何把系统资源利用率提高,也是我们要思考的一个问题。在执行器 v2.0 版本执行器采用向量化火山模型的架构来实现的,如下图所示。

采用这种架构的优势在于:

  1. 架构比较简单。
  2. 因为数据是批量处理的,所以向量化计算应用的场景比较多。

但是这种架构的劣势也比较明显:

  1. 调度不灵活,依赖系统调用,在数据倾斜场景不好处理。
  2. 并行度不好控制。
  3. 有虚函数调用的开销, context switch 比较高,cpu cache 命中率低。
  4. 如果算子内部需要并行,只能自己开线程处理,对于执行器来说线程资源不可控。

针对于向量化的火山模型的痛点问题,执行器架构升级成 Pipeline 并行计算模型进行资源利用率的提升。从而整体上面提升性能。

04、PipeLine 并行计算模型的设计

4.1 基于物理计划构建 Pipeline

我们以 TPC-H 语句中的 Q1 为例:

代码语言:javascript
复制
select
  l_returnflag,
  l_linestatus,
  sum(l_quantity)as sum_qty,
  sum(l_extendedprice)as sum_base_price,
  sum(l_extendedprice *(1- l_discount))as sum_disc_price,
  sum(l_extendedprice *(1- l_discount)*(1+ l_tax))as sum_charge,
  avg(l_quantity)as avg_qty,
  avg(l_extendedprice)as avg_price,
  avg(l_discount)as avg_disc,
  count(*)as count_order
from
  lineitem
where
  l_shipdate <= date_sub('1998-12-01',interval108day)
groupby
  l_returnflag,
  l_linestatus

这里假定节点的物理核心为4,LibraDB 物理执行器默认采用 CPU 物理核心数当做并行度(dop)来进行拆分 Pipeline。

核心流程为:

  • TDSQL 计算引擎接收到用户的 SQL 后,生成的物理执行计划。派发给各个的 LibraDB 节点。
  • LibraDB 组件的 MPP Task Handle 接收 TDSQL 计算引擎生成的物理计划 (MPP task),生成实际的物理算子。
  • LibraDB 组件的 Pipeline Executor 根据物理算子的关联关系,拆分 Pipelines。拆分出来2个 Pipeline。P1 为预聚合计算,P2 为 Final 聚合计算。P2 依赖于 P1 执行完毕后启动。
  • 根据 Pipeline 并行度(dop)生成多个 pipe,让后续的调度组件进行调度。

4.2 基于 Pipeline 构建执行图

核心流程为:

  • LibraDB 组件的 Executing Graph 接收到 Pipeline Executor 产生出来的 Pipelines,把 pipeline 中每个 operator 转化为 node。
  • 把相邻 node 之间创建边(前向边和后向边)进行关联,构建有向无环图。创建算子的 Buffer 放到相邻的 node 之间,为了让后续 pipeline 计算暂存一个数据包的数据。

4.3 基于 Pipeline 调度执行

核心流程为:

  • LibraDB 组件的 Pipeline Scheduler 接收到前面生成的执行图,会从 Work Thread Pool 里面创建出来并行度(dop)个 Work Thread。
  • 每一个 Work 线程会检查算子 Node 的状态信息,当发现 Node 的状态信息为 Ready 的场景下,就调度当前 Node 进行计算,计算后的结果放到算子的缓存 Buffer 中。如果当前 Pipeline 的 Node 都没有 Ready 的情况下,则调度其他的 Pipeline 进行状态信息的判断和执行。

05、阻塞操作异步化

实现 Pipeline 执行引擎还有一个核心的功能就是阻塞操作异步化的优化,举个例子:

  1. 列存针对于分析场景通常要读取大量的磁盘数据,这时会因为磁盘的 I/O 导致上层的算子性能受影响,导致 CPU 利用率低。
  2. 或者通过 Sender 把数据发送给上游的 MPP Task 的场景下,如果上游的任务负载比较高,会反压下层的任务 CPU 利用率低。

针对于上述的场景,我们设计 CPU 任务和 I/O 任务分离的架构。

  1. 针对于 TableScan 磁盘 I/O 的优化,设计一个 Block Cache 队列,底层扫描任务异步的把要读取的数据放入 Block Cache 队列中并且会预读数据。上层的计算线程会从 Block Cache 队列里面进行数据的读取。
  2. 针对于网络 I/O 的优化,会设计网络数据的 Buffer 队列,当前任务计算完,会把数据放入到网络的 Buffer 队列异步的发送给对端。当数据放到网络的 Buffer 队列后,就立刻进行后续的计算。这样 CPU 的利用率能够充分的利用起来。

06、数据倾斜场景的调度优化

6.1 执行线程的窃取能力(work-stealing)

在 Pipeline 执行的时候,每个 pipe 中的任务执行上可能出现各种情形的 skew,虽然在 pipeline 开始水平切分的时候尽可能将数据进行了平均切分,但是通过每个算子之后,剩余的数据可能不同,例如通过一个 filter 算子之后,pipeline 中的三个 pipe 之间的数据出现了极度不均衡,例如下面这种情况,第一个 pipe 的 filter 之后只有 1% 的数据,那么很快这个pipe 就计算完毕了。

代码语言:javascript
复制
pipe1: source1 -> filter -- selectity: 1% ---> op2 ->

pipe2: source2 -> filter -- selectity: 99% ---> op2 ->

pipe3: source3 -> filter -- selectity: 50% ---> op2 ->

在这种情况下,pipe1 就可以尝试 steal pipe3 中的 task queue 中的任务来执行,不让每个 CPU 闲置,提高 CPU 的利用率。

6.2 Local Exchange 数据打散能力

当涉及计算的表是分区表,当分区表的分区的个数小于 CPU 的物理核心数的场景下,并且支持 partition wise 优化的场景下,那么 Pipeline 的 dop 则使用的是分区表分区的个数作为并行度。

这时无法把节点的资源使用满。这时我们通过Local Exchange 算子把 partition wise 计算后的结果进行分发。生成 CPU 核心数的后续算子进行计算。

07、算子性能优化

7.1 AGG 聚合算子的优化策略

我们还用 TPC-H 语句中的 Q1 来看下 LibraDB 关于聚合算子(AGG)层面的优化策略

代码语言:javascript
复制
select
  l_returnflag,
  l_linestatus,
        sum(l_quantity)as sum_qty,
        sum(l_extendedprice)as sum_base_price,
        sum(l_extendedprice *(1- l_discount))as sum_disc_price,
        sum(l_extendedprice *(1- l_discount)*(1+ l_tax))as sum_charge,
        avg(l_quantity)as avg_qty,
        avg(l_extendedprice)as avg_price,
        avg(l_discount)as avg_disc,
        count(*)as count_order
from
  lineitem
where
  l_shipdate <= date_sub('1998-12-01',interval108day)
groupby
  l_returnflag,
  l_linestatus
orderby
        l_returnflag,
        l_linestatus;

7.1.1 并行计算优化策略

7.1.1.1 线程级别并行优化

当 TDSQL 计算引擎产生分布式查询计划 (MPP Task)。物理执行器会使用 `MPP Task` 创建两阶段的 AGG 并行计算流程。

7.1.1.1.1 线程级别并行优化

在聚合算法的预聚合阶段,会创建 CPU 核心数个 HashMap, 每一个 Work 线程持有各自的 HashMap。HashMap 包含256个 bucket,每一个 bucket 是一个子的哈希表。外部数据通过哈希的方式落到不同的 bucket 中进行聚合的预计算处理。这样每一个线程的 CPU 资源使用率都比较高。

7.1.1.1.2 Merge 阶段并行优化策略

在聚合算法的 Merge 阶段,会启动 CPU 核心数个 Merge 线程,因为数据计算的哈希算法相同,并且 HashMap 的 Bucket 的个数也相同。所以每一个线程会把多个 HashMap 中相同 Bucket 的数据进行 Merge 操作,这样多个 HashMap 的 Bucket 也可以并行计算。这样系统资源的利用率会比较高。

7.1.1.2 数据级别并行优化

7.1.1.2.1 SIMD 向量化优化

在 AGG 算子计算 GroupBy Key Hash 值的场景下,针对于底层传递上来的批量数据会进行 SIMD 哈希值的计算。然后通过 SIMD 向量化的求模运算计算 Bucket Num。最终写入到哈希表中进行聚合计算。

7.1.1.3 指令级别并行优化

7.1.1.3.1 Codegen JIT 优化

针对于聚合函数`Sum`、`Max`、`Min`、`AVG`等聚合函数场景,采用 Llvm JIT 的技术来提升执行性能。

7.1.2 指令级别并行优化

7.1.2.1 Codegen JIT 优化

根据 Group By Key 的数据类型、Null 值情况、单 GroupBy Key 场景或者复合 Group By Key 的场景设计出了60多种特定的 Hash 表。充分的利用CPU L1, L2, L3级别的 Cache 能力。从而提升算子的执行的性能。

7.1.2.2 哈希表 Resize 优化

通常场景下,随着往哈希表中写入的数据量越来越多,会触发哈希表的 Resize 操作,导致哈希表中的数据重分布,导致产生 Memory Copy 的动作,从而影响性能。传统业界有两种优化方式:

  1. DuckDB 等采用一个由两部分组成的聚合哈希表。哈希表数据存放的元数据信息和真实数据存储的信息,当产生 Resize 操作的时候,只需要修改哈希表数据存放的元数据信息即可,从而避免了大量的 Memory Copy。
  2. StarRocks 处理 Join 算子等会先缓存哈希表的数据,等全量收集完成后生成哈希表的统计信息,再根据统计信息生成多大的哈希表,再把数据写入到哈希表中。从而避免了大量的 Memory Copy。

LibraDB 采用的方式略有不同,基于计划的 FeedBack 的能力,计划会把算子预估要处理的行数传递给物理执行器,物理执行器接收到后,根据预估的行数来生成多大的哈希表,从而避免了大量的 Memory Copy。

7.1.2.3 单层哈希表转化成二层哈希表

传统哈希表随着数据越来越多,会导致数据查找时冲突的概率越来越大。并且既有写入场景也有查找场景的时候,锁的力度会比较大,锁通常的力度为 Bucket 级别。当某一个 Bucket 下面的数据比较多的情况下,性能影响比较严重。

与传统的哈希表不同,LibraDB 的哈希表当随着数据量增加到某一个阈值的时候,会由一层哈表表切换成二层哈希表,使用二层的哈希表更加有利于降低锁的力度、Resize 场景下数据拷贝的代价降低了、更加有利于并发场景的处理。

7.1.2.4 Prefetch 优化

针对于哈希表中元素比较多的场景下,我们利用 Prefetch 优化 Cache Miss 的的场景,因为 Prefetch 的时机和距离比较难把握目前我们通过配置参数的方式来进行调整。每次读取哈希表数据的时候,通过预读一部分数据放到 CPU Cache 里面,如果后面的数据命中了 CPU Cache 的场景大大的优化了 CPU Cache Miss 的场景。

7.2 Join 关联算子的优化策略

我们还用 TPC-H 语句中的 Q9 来看下 Libra 关于关联算子(Join)层面的优化策略。

代码语言:javascript
复制
SELECT
    nation,
    o_year,
    sum(amount)AS sum_profit
FROM
(
    SELECT
        n_name AS nation,
        toYear(o_orderdate)AS o_year,
        (l_extendedprice *(1- l_discount))-(ps_supplycost * l_quantity)AS amount
    FROM part, supplier, lineitem, partsupp, orders, nation
    WHERE(s_suppkey = l_suppkey)AND(ps_suppkey = l_suppkey)AND(ps_partkey = l_partkey)AND(p_partkey = l_partkey)AND(o_orderkey = l_orderkey)AND(s_nationkey = n_nationkey)AND(p_name LIKE'%dim%')
)AS profit
GROUP BY
    nation,
    o_year
ORDER BY
    nation ASC,
    o_year DESC

TPC-H Q9 中,包含关联运算、聚合运算、排序运算。但是针对于整个 SQL 而言耗时比较重的点还是在多表关联的运算上面, 所以聚合运算和排序运算, 不是我们主要考虑的优化。下面针对于关联运算的优化策略进行描述。

7.2.1 Join 顺序优化

TDSQL 计算引擎接收到用户的 SQL 语句后,会生成 Join Graph。Join 的执行顺序对性能影响是非常严重的。

例如如果使用 lineitem 作为 Hash Join Build 端的情况下,会使用lineitem全量数据构建哈希表,会造成哈希表的膨胀,频繁的进行 Resize 等操作、极端场景下内存中都承载不住 lineitem 的全量数据造成数据的落盘。Join顺序的选择是非常重要的。TDSQL 计算引擎基于统计信息采用 CBO 和 FeedBack 的方式来选择最优的 Join 执行顺序。

7.2.2 并行计算优化策略

7.2.2.1 线程级别并行优化

7.2.2.1.1 Fragment 内部多个 Join 算子哈表表并行构建

传统的数据库当处理多个 Join 的场景下,哈希表都是串行构建的如上图所示,例如 Clickhouse 等。这种计算模型会导致系统资源利用不充足。例如当 nation 表构建哈希表的时候,part、partsupp、orders 表都是没有计算的状态,但是这时这些表如果能够提前进行数据的扫描,这时系统的CPU资源和I/O资源都能充分的利用起来,从而整体的提升性能。

这面做还有个好处,例如如果 part, partsupp、orders 是 Receiver 算子的情况下,也能把下层计划的资源充分的利用起来。

物理执行器采用二阶段执行的方式,第一阶段提前使用 Work 线程并行构建哈希表的数据,让下层的计划物理资源利用率充分的利用起来。第二阶段再进行多个 Join probe 操作。

7.2.2.1.2 Join 算子并行计算优化

针对于单个 Join 算子,物理执行器这里也是采用并行的方案来进行计算的。举个例子。

代码语言:javascript
复制
select*from lineitem join orders on l_orderkey = o_orderkey;

执行器会把数据拆成并行度(dop)个数据源,并且创建并行度(dop)个 Work 线程,执行器会把 Join 的 Build 端进行提前调度,每个 Work 线程读取一个数据源的数据写入到哈希表中。

但是他们共享一个全局算子级别的哈希表(每个算子一个哈希表,所有 Work 线程共享)。这里我们通过每个线程按照 hash 值模桶数把数据拆分 N 份,针对于哈希表进行批量插入,降低锁的力度。

7.2.2.2 数据级别并行优化

7.2.2.2.1 Join 算子向量化优化

和聚合算子类似,也是在批量计算数据的 Hash 值和桶数的时候采用了 SIMD 技术来进行优化处理。

7.2.3 哈希表优化策略

7.2.3.1 哈希表选择优化

根据 Join Key 的数据类型、Null 值情况、单 Join Key 场景或者复合 Join Key 的场景设计出了60多种特定的 Hash 表。充分的利用 CPU L1, L2, L3级别的 Cache 能力。从而提升算子的执行的性能。

7.2.3.2 哈希表 Resize 优化

和聚合算子的哈希表采用相同的技术。

7.2.4 Runtime Filter 优化

减少算法输入规模也能很大程度提升性能。如果能够用很小的代价降输入规模,比如规模减少为十分之一,对于线性复杂度算法,大约能提升十倍。Runtime Filter 正是这个思想的体现。

前面说过,Join 的内表比较小,构建 Hash Map,外表比较大,计算哈希值、匹配过程都比较耗时。如果能对大表先进行过滤,则有可能获得加速。这里的做法是使用内表数据构建一个布隆过滤器(Bloom Filter)、Range区间、In列表等,然后作用于外表,就能起到减少输入规模的作用。

简而言之,关联谓词在 Hash Join 构建阶段转化成一个过滤条件应用于外表。

下面以 Q17 为例,查询如下

代码语言:javascript
复制
SELECTsum(l_extendedprice)/7.AS avg_yearly
FROM lineitem, part
WHERE(p_partkey = l_partkey)AND(p_brand ='Brand#44')AND(p_container ='WRAP PKG')AND(l_quantity <(
    SELECT0.2*avg(l_quantity)
    FROM lineitem
    WHERE l_partkey = p_partkey
))

TPC-H SF 10中 p_partkey 的基数是 2,000,000,而加上两个过滤条件 p_brand = 'Brand#44' and p_container='WRAP PKG',基数变为 2,003,减少了三个数量级。通过 Runtime Filter,等价于过滤条件也作用在子查询的 Agg 算子和外层 Join 算子下的扫描节点,使得扫描节点向上层算子返回的结果规模大大减少,以提升性能。

布隆过滤器的特点并不完全等价于数据库中的谓词过滤,但是也能去除相当比例的无用数据。以 Q17 为例,输出结果集是原数据集的 1/80,整个查询性能提升了 8 倍。

08、结论

LibraDB 的执行器在执行性能方面做了很多的努力。执行器这里要考虑充分的利用集群的资源,实现多机场景下的并行计算,也要考虑在单机场景下,设计高性能的执行框架,例如使用异步化、灵活调度、SIMD、Runtime Filter、延迟物化、Encoding 等等。充分利用单机多核的 CPU、内存、网络、I/O,让资源利用率最大化。

09、未来分享

  1. 资源管理相关,针对于高并发场景、CPU 资源控制、内存资源管控、线程资源控制、Query 的优先级队列、算子外存能力等功能。
  2. 算子性能相关,例如有序数据的算子(Stream AGG 算子,Stream Join)等。Adaptive Window Function、Adaptive AGG 算子等。
  3. Mysql 生态的兼容。
  4. 平台相关优化,针对于 ARM , 海光等国产化平台进行优化。
  5. 列存事务的支持。