浅析OceanBase数据库的向量化执行引擎

news2024/9/21 14:55:01
本篇博客是偏数据库系统概念性的内容,不会深入到 OceanBase 中各个算子和表达式的在向量化中的详细设计和实现。

背景

为了提升OceanBase社区版用户解决问题的效率,OceanBase官方不久前推出了《OceanBase 从入门到实践》系列课程。在第七期直播课程后,我们收到了不少用户的提问,表示在查阅学习资料或笔记时,对其中提到的诸如“rowset=16”或“rowset=256”这样的信息存在理解上的困惑,不清楚这其具体含义。如下代码中的示例。这里的 rowset 信息和 OceanBase 执行引擎的向量化执行技术相关,这篇博客就正好作为 AP 性能专题的内容之一,在解答这个用户问题的同时,也为大家介绍一下 OceanBase 执行引擎的向量化执行技术。

obclient [test]> create table t1(c1 int, c2 int);
Query OK, 0 rows affected (0.203 sec)

obclient [test]> explain select count(*) from t1 where c1 = 1000;
+------------------------------------------------------------------------------------+
| Query Plan                                                                         |
+------------------------------------------------------------------------------------+
| =================================================                                  |
| |ID|OPERATOR         |NAME|EST.ROWS|EST.TIME(us)|                                  |
| -------------------------------------------------                                  |
| |0 |SCALAR GROUP BY  |    |1       |4           |                                  |
| |1 |└─TABLE FULL SCAN|t1  |1       |4           |                                  |
| =================================================                                  |
| Outputs & filters:                                                                 |
| -------------------------------------                                              |
|   0 - output([T_FUN_COUNT_SUM(T_FUN_COUNT(*))]), filter(nil), rowset=16            |
|       group(nil), agg_func([T_FUN_COUNT_SUM(T_FUN_COUNT(*))])                      |
|   1 - output([T_FUN_COUNT(*)]), filter([t1.c1 = 1000]), rowset=16                  |
|       access([t1.c1]), partitions(p0)                                              |
|       is_index_back=false, is_global_index=false, filter_before_indexback[false],  |
|       range_key([t1.__pk_increment]), range(MIN ; MAX)always true                  |
+------------------------------------------------------------------------------------+
14 rows in set (0.033 sec)

火山模型执行引擎

向量化执行引擎是提升 AP 能力的利器之一,也为 2021 年 OceanBase TPCH 打榜夺冠做出了重要贡献。不过如果要学习向量化引擎,首先需要了解一下传统的数据库执行引擎模型 —— 火山模型。

火山模型(Volcano Model)也被称为迭代模型(Iterator Model),是最著名的查询执行模型,早在 1990 年就在论文《Volcano, an Extensible and Parallel Query Evaluation System》中被提出。大多数关系型数据库都采用了这种模型,例如 Oracle、MySQL、DB2、SQLServer 等等。

在火山模型中,一个查询计划会被分解为多个算子(Operator)。每个算子就是一个迭代器,都要实现一个 next() 接口,通常包括三个步骤:

    • 调用儿子算子的 next() 方法,获取儿子算子返回的计算结果;
    • 对儿子算子返回的计算结果,执行当前算子的计算动作,得到计算结果;
    • 返回一行计算结果给父亲算子。

说明:
   论文里算子的的 next() 接口,在 OceanBase 的代码里叫 ObOperator::get_next_row()。

通过火山模型,查询执行引擎可以优雅地将任意算子组装在一起,而不需要考虑每个算子的具体处理逻辑。查询执行时会由查询树自顶向下嵌套调用 get_next_row() ,数据则自底向上地被拉取处理。所以,这种处理方式也称为拉取执行模型(Pull Based)。为了更好地理解火山模型的拉取执行过程,这里继续看上面这个聚合计算的例子:

select count(*) from t1 where c1 = 1000;

说明:

图中的 Tuple 是一个元组,也就是下层算子向上层算子返回的一个计算结果行。

如上图所示:

    • 步骤 1 - 步骤 3,聚合算子 Aggregate 先通过调用 get_next_row() 方法触发下面这些算子逐层调用孩子算子的 get_next_row() 方法。
    • 步骤 4 - 步骤 6,TableScan 算子获取到存储层的数据,吐行给 Filter 算子,Filter 算子完成过滤条件 c1 = 1000 的计算,吐结果行给负责聚合计算的 Aggregate 算子。
    • 步骤 7,Aggregate 算子通过反复调用 next 方法获取到需要的一批数据,最终完成聚合计算,返回计算结果。

把 OceanBase 的向量化开关关掉,就可以看到类似于上图的执行计划树。

-- 关掉向量化开关,让后面的 SQL 默认走类似于火山模型的单行计算模式
alter system set _rowsets_enabled = false;

-- 大家可以发现,在下面这个计划里,看不到类似于上面那个计划里的 rowset 值了
explain select count(*) from t1 where c1 = 1000;
+------------------------------------------------------------------------------------+
| Query Plan                                                                         |
+------------------------------------------------------------------------------------+
| =================================================                                  |
| |ID|OPERATOR         |NAME|EST.ROWS|EST.TIME(us)|                                  |
| -------------------------------------------------                                  |
| |0 |SCALAR GROUP BY  |    |1       |6           |                                  |
| |1 |└─TABLE FULL SCAN|t1  |1       |6           |                                  |
| =================================================                                  |
| Outputs & filters:                                                                 |
| -------------------------------------                                              |
|   0 - output([T_FUN_COUNT_SUM(T_FUN_COUNT(*))]), filter(nil)                       |
|       group(nil), agg_func([T_FUN_COUNT_SUM(T_FUN_COUNT(*))])                      |
|   1 - output([T_FUN_COUNT(*)]), filter([t1.c1 = 1000])                             |
|       access([t1.c1]), partitions(p0)                                              |
|       is_index_back=false, is_global_index=false, filter_before_indexback[false],  |
|       range_key([t1.__pk_increment]), range(MIN ; MAX)always true                  |
+------------------------------------------------------------------------------------+
14 rows in set (0.010 sec)

OceanBase 的计划只有两个算子,比上面这张图上的看上去更简单些。因为 OceanBase 中的每一个算子都包含了 Filter 的功能,所以不需要独立的 Filter 算子。可以在上面的计划里看到,TABLE SCAN 算子里也有 filter([t1.c1 = 1000]) 的信息。计划里的 SCALAR GROUP BY 算子就是负责不需要进行 group by 分组的聚合计算,对应图中的 Aggregate 算子。

火山模型的优点是处理逻辑清晰,每个算子只要关心自己的处理逻辑即可,耦合性低。但是它的缺点也非常明显,主要是两点:

    • get_next_row() 虚函数在每个算子处理每行数据时都要调用一次,调用次数过多,造成 CPU 资源的浪费。在 OLAP 查询数据量大的场景中,问题尤为明显。
    • 数据以行为单位进行处理,不利于充分发挥现代 CPU 的特性。

向量化执行引擎及其优势

向量化模型最早是在 MonetDB-X100(Vectorwise)系统的论文 《MonetDB/X100: Hyper-Pipelining Query Execution》中提出的,不同于传统的火山模型按行迭代的方式,向量化引擎采用按批迭代方式,可以在算子间一次传递一批数据。向量化引擎被提出后,因为可以有效提高 CPU 利用率并充分发挥现代 CPU 的特性,很快就被广泛应用到了现代数据库引擎设计中。 

如上图所示,与数据库传统的火山模型迭代类似,向量化模型也是通过 PULL 模式从算子树的根节点层层拉取数据。区别于 get_next_row() 调用一次传递一行数据,向量化引擎通过 get_next_batch() 一次传递一批数据,并尽量保证该批数据在内存上紧凑排列。

减少虚函数调用的开销

向量化引擎第一个显而易见的优势,就是大幅减少了框架函数的调用次数。假设一张表有 1 亿行数据,按火山模型的处理方式,每个算子均需要执行 1 亿次 get_next_row() 的迭代才能完成查询。如果使用向量化引擎,假设一个向量的大小为 1024 行数据,那么在同样的查询中,get_next_batch() 的调用次数会小于 10 万次( 1 亿 / 1024 = 97657 ),大大降低了虚函数调用次数,节省了 CPU 的开销。

在本文开头的用户问题里,计划中的 rowset 就是表示一个 batch(或者叫一个向量)中有多少行数据。

-- 打开向量化开关
alter system set _rowsets_enabled = true;

-- 设置每个向量的大小是 16 行
alter system set _rowsets_max_rows = 16;

-- 计划中可以看到执行时的向量值大小(rowset=16)
explain select count(*) from t1 where c1 = 1000;
+------------------------------------------------------------------------------------+
| Query Plan                                                                         |
+------------------------------------------------------------------------------------+
| =================================================                                  |
| |ID|OPERATOR         |NAME|EST.ROWS|EST.TIME(us)|                                  |
| -------------------------------------------------                                  |
| |0 |SCALAR GROUP BY  |    |1       |4           |                                  |
| |1 |└─TABLE FULL SCAN|t1  |1       |4           |                                  |
| =================================================                                  |
| Outputs & filters:                                                                 |
| -------------------------------------                                              |
|   0 - output([T_FUN_COUNT_SUM(T_FUN_COUNT(*))]), filter(nil), rowset=16            |
|       group(nil), agg_func([T_FUN_COUNT_SUM(T_FUN_COUNT(*))])                      |
|   1 - output([T_FUN_COUNT(*)]), filter([t1.c1 = 1000]), rowset=16                  |
|       access([t1.c1]), partitions(p0)                                              |
|       is_index_back=false, is_global_index=false, filter_before_indexback[false],  |
|       range_key([t1.__pk_increment]), range(MIN ; MAX)always true                  |
+------------------------------------------------------------------------------------+
14 rows in set (0.021 sec)

充分发挥现代 CPU 的特性

数据紧密排列,提升 Cache 友好性

OceanBase 在向量化执行过程中,会尽量保证该批数据在内存上紧凑排列,产生的中间数据会以列的形式进行组织。比如我们一个 batch 是 256 行数据, 那我们内存排布中 c1 的 256 行数据就会都连续存放, 然后 c2 的 256 行数据也连续存放, 计算 concat(c1, c2) 表达式还是 256 行一起计算,最后将结果放入到该表达式预分配的结果值的内存空间中。

由于中间数据连续,CPU 就可以通过预取指令快速把即将进行计算的数据提前加载到 L2 cache 中,以此减少 memory stall 的现象,提升 CPU 的利用率。在算子函数内部,函数也不再是一次处理一行数据,而通过批量处理连续数据的方式进行计算,可以提升 CPU DCache 和 ICache 的友好性,减少 Cache Miss。

减少分支预测失败对 CPU 流水线的破坏

论文《DBMSs On A Modern Processor: Where Does Time Go?》中介绍了分支预测失败对数据库性能的影响。由于 CPU 中断了流水执行,重新刷新流水线,因此分支预测失败对数据库处理性能的影响很大。SIGMOD13 的论文《Micro Adaptivity in Vectorwise》也对分支在不同选择率下的执行效率有详细论述,见下图。

由于数据库 SQL 引擎逻辑比较复杂,在火山模型下,条件判断逻辑出现频率普遍较高。

// 单行计算流程的伪代码,处理 256 行数据,要进行 256 次 if 的判断:
for (auto row_no : 256) {
  get_next_row() {
    if (A) {
      eval_func_A();
    } else if (B) {
      eval_func_B();
    }
  }
}

但在向量化执行时,可以在算子和表达式内部,最大限度地避免条件判断。例如避免在 for 循环内部出现 if 判断,从而避免分支预测失败对 CPU 流水线的破坏,大幅提升 CPU 的处理能力。

// 向量化计算流程伪代码,处理 256 行数据,只需要进行 1 次 if 的判断:
get_next_batch() {
  if (A) {
    for (auto row_no : 256) {
      eval_func_A();
    }
  } else if (B) {
      for (auto row_no : 256) {
      eval_func_B();
    }
  }
}

利用 SIMD 指令加速计算

由于向量化引擎处理内存连续数据,因此向量引擎可以很方便的把一批数据装载到向量寄存器中。然后通过 SIMD 指令,替换传统的标量(Scalar)算法,进行向量(Vector)计算。SIMD 指令可以使 CPU 同时对一批数据并行执行相同的计算动作,从而减少这批数据计算所需要的 CPU 的周期数。

上图右侧展示了典型的 SIMD 计算,两组四个连续存储的数据元素被并行计算,CPU 会根据 SIMD 指令,对每对相应的数据元素(A1 和 B1,A2 和 B2,A3 和 B3,A4 和 B4)同时执行相同的运算。四个并行计算的结果也会被连续存储。

假设我们的处理器支持 4 个元素的 SIMD 乘法操作,那就意味着它有特殊的寄存器(通常称为向量寄存器),可以同时存储四个整型数。因为 OceanBase 的向量化执行过程中数据会紧密存储,所以就可以按照如下流程编写 SIMD 代码:

  • 数据加载(_mm_loadu_si128):首先,我们将向量 A1 A2 A3 A4 和 B1 B2 B3 B4 的各个元素加载到两个 SIMD 寄存器中。
  • 单一指令乘法(_mm_mullo_epi32):接着,我们使用单一的 SIMD 乘法指令,同时对两个寄存器中的所有元素执行乘法操作。
  • 数据存储(_mm_storeu_si128):最后,我们将结果从 SIMD 寄存器存储回为结果分配的内存中,得到结果向量。
// SSE(针对x86架构)的 C++ 伪代码示例,使用 SIMD 技术进行整型向量的逐元素相乘
#include <immintrin.h> // 包含 SSE 指令集头文件

// 函数用于计算两个整型向量的逐元素相乘
void simdIntVectorMultiply(const int* vec1, const int* vec2, int* result, size_t length) {
  // 确保向量长度是 4 的倍数,因为 SSE 寄存器一次处理 4 个 32 位整数
  assert(length % 4 == 0);

  // 使用 SSE 指令进行优化的循环
    for (size_t i = 0; i < length; i += 4) {

    // 加载四个整数到 xmm 寄存器(128位)
    __m128i vec1_simd = _mm_loadu_si128(reinterpret_cast<const __m128i*>(vec1 + i));
    __m128i vec2_simd = _mm_loadu_si128(reinterpret_cast<const __m128i*>(vec2 + i));

    // 执行向量乘法
    __m128i product_simd = _mm_mullo_epi32(vec1_simd, vec2_simd);

    // 存储结果回内存
    _mm_storeu_si128(reinterpret_cast<__m128i*>(result + i), product_simd);
  }
}

TPCH 性能测试

利用 TPCH 30T 数据集对 OceanBase 数据库进行 TPCH 测试,向量化执行模式相比单行执行模式,执行性能整体可以提升 2.48 倍。对于计算密集的 Q1 查询, 性能提升可以达到 10 倍以上。

在最新的 4.3 版本中,OceanBase 还对从 3.x 版本就已经支持的向量化执行引擎,进行了一次新的优化和重构,详细介绍和性能测试可以参考:这篇博客。

结语

这篇博客是 OceanBase 向量化执行引擎入门级的介绍,希望无论是 DBA 同学,还是内核研发同学,在阅读之后都能够有所收获。如果大家希望继续和作者讨论有关向量化执行的任何问题,都欢迎在这篇博客的评论区进行留言,我会第一时间对大家的问题进行回复。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2152615.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux笔记---简单指令

1. 使用的环境 博主使用的是华为云服务器xshell终端的方式学习的&#xff0c;因为据说这样的方式比较接近以后的工作环境。 其中云服务器安装的是Ubuntu操作系统(以Linux为内核&#xff0c;适合新手学习Linux的一个版本) 这里的云服务器不一定使用华为的&#xff0c;但是我在…

基于Spring JDBC AbstractRoutingDataSource 实现动态数据源

AbstractRoutingDataSource 实现动态数据源 AbstractRoutingDataSource 即抽象的路由数据源&#xff0c;提供了动态数据源切换的机制。你可以通过实现它的 determineCurrentLookupKey() 方法&#xff0c;根据不同的条件返回对应的数据源 key&#xff0c;基于这点可以根据外部输…

OpenRestry(一个Nginx集成工具)的安装与使用

文章目录 一、OpenRestry介绍1、什么是Nginx呢&#xff1f;2、Nginx的反向代理3、Nginx的作用4、什么是OpenRestry&#xff1f; 二、OpenRestry的安装三、OpenRestry中nginx的使用1、Ngnix可以当做web服务器2、Nginx中可以编写Lua脚本 一、OpenRestry介绍 要想了解什么是OpenR…

ChatGPT搭上langchain的知识库RAG应用,效果超预期

最近利用LangchainChatGPT实现了上传文档实现个人知识库应用的能力&#xff0c;效果比想象得要好。文末大家可以体验一下效果~~ 给大家大致介绍下实现方式&#xff0c;参考了Langchain chatchat。 一、LangchainChatGPT 1、概述 LangChain 是一个强大的框架&#xff0c;可以…

数据技术进化史:从数据仓库到数据中台再到数据飞轮的旅程

随着大数据时代的到来&#xff0c;数据已经成为企业的核心资产之一。在过去几十年间&#xff0c;数据技术也随之不断演进&#xff0c;从早期的数据仓库到近年来热门的数据中台&#xff0c;再到正在快速发展的数据飞轮概念&#xff0c;每一步都是技术革新的体现。 一、数据仓库&…

信息安全工程师(10)网络信息安全法律与政策文件

前言 网络信息安全法律与政策文件是保障网络安全、维护网络空间秩序、保护公民和组织合法权益的重要基石。 一、主要法律文件 《中华人民共和国网络安全法》 发布时间&#xff1a;2016年11月7日&#xff0c;由第十二届全国人民代表大会常务委员会第二十四次会议通过。主要内容&…

9。maven必备小技巧

&#xff08;1&#xff09;配置Maven加速时&#xff0c;除了settings之外&#xff0c;还可如下图所示&#xff0c;配置如下&#xff1a; 若想实现Maven加速&#xff0c;最重要的即User settings file。&#xff08;先修改settings.xml&#xff09; &#xff08;2&#xff09;当…

微服务容错及解决

学前必备知识 学之前我们要理解一些概念&#xff0c;之后我们会遇到QPS&#xff0c;并发量&#xff0c;线程等专有名词。 一文搞懂高并发性能指标&#xff1a;QPS、TPS、RT、并发数、吞吐量 - 知乎 (zhihu.com) 雪崩问题 1 是什么 在微服务远程调用的过程中&#xff0c;还…

【C++】—— stack queue deque

【C】—— stack & queue & deque 1 stack 与 queue 的函数接口2 适配器2.1 发现问题2.2 什么是适配器 3 stack 与 queue的模拟实现3.1 栈的基础框架3.2 栈的模拟实现3.3 队列的模拟实现 4 模板的按需实例化5 deque 的简单介绍5.1 vector 与list对比5.1.1 vector5.1.2 …

Spring Boot 3项目使用Swagger3教程

Spring Boot 3项目使用Swagger3教程 Swagger&#xff1a;自动生成接口文档 添加依赖(pom.xml) <dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>4.1…

音视频入门基础:AAC专题(7)——FFmpeg源码中计算AAC裸流每个packet的size值的实现

音视频入门基础&#xff1a;AAC专题系列文章&#xff1a; 音视频入门基础&#xff1a;AAC专题&#xff08;1&#xff09;——AAC官方文档下载 音视频入门基础&#xff1a;AAC专题&#xff08;2&#xff09;——使用FFmpeg命令生成AAC裸流文件 音视频入门基础&#xff1a;AAC…

零工市场小程序:推动零工市场建设

人力资源和社会保障部在2024年4月发布了标题为《地方推进零工市场建设经验做法》的文章。 零工市场小程序的功能 信息登记与发布 精准匹配、推送 在线沟通 权益保障 零工市场小程序作为一个找零工的渠道&#xff0c;在往后随着技术的发展和政策的支持下&#xff0c;功能必然…

自注意力与多头自注意力的区别

自注意力机制和多头自注意力机制在深度学习&#xff0c;尤其是Transformer模型中是核心组件。它们的主要区别在于如何处理输入信息和增强模型的表达能力。 1. 自注意力机制&#xff08;Self-Attention&#xff09; 自注意力机制的主要作用是让模型在处理每个输入元素时&#…

计算机人工智能前沿进展-大语言模型方向-2024-09-20

计算机人工智能前沿进展-大语言模型方向-2024-09-20 1. Multimodal Fusion with LLMs for Engagement Prediction in Natural Conversation Authors: Cheng Charles Ma, Kevin Hyekang Joo, Alexandria K. Vail, Sunreeta Bhattacharya, Alvaro Fern’andez Garc’ia, Kailan…

操作系统 | 学习笔记 | | 王道 | 5.1 I/O管理概述

5.1 I/O管理概述 5.1.1 I/O设备 注&#xff1a;块设备可以寻址&#xff0c;但是字符设备是不可寻址的 I/O设备是将数据输入到计算机中&#xff0c;或者可以接收计算机输出数据的外部设备&#xff0c;属于计算机中的硬件部件&#xff1b; 设备的分类 按使用特性分类&#xff…

新手爬虫er必刷!如何使用代理IP全攻略!

在爬虫开发中&#xff0c;代理IP&#xff08;也称为代理服务器&#xff09;是一个非常重要的工具。当爬虫访问网站时&#xff0c;可能会遭遇IP封锁或请求频率限制。通过使用代理IP&#xff0c;可以分散请求压力并规避特定对IP的限制&#xff0c;从而提高采集任务的持续性。同时…

Cassandra 5.0 Spring Boot 3.3 CRUD

概览 因AI要使用到向量存储&#xff0c;JanusGraph也使用到Cassandra 卸载先前版本 docker stop cassandra && docker remove cassandra && rm -rf cassandra/运行Cassandra容器 docker run \--name cassandra \--hostname cassandra \-p 9042:9042 \--pri…

SpringCloud Alibaba五大组件之——Sentinel

SpringCloud Alibaba五大组件之——Sentinel&#xff08;文末附有完整项目GitHub链接&#xff09; 前言一、什么是Sentinel二、Sentinel控制台1.下载jar包2.自己打包3.启动控制台4.浏览器访问 三、项目中引入Sentinel1.在api-service模块的pom文件引入依赖&#xff1a;2.applic…

【干货整理】什么软件能监控员工电脑?六大好用的电脑监控软件,抢手推荐!

什么软件能监控员工电脑&#xff1f; 电脑监控软件啦&#xff01; 要是能有一双无形的眼睛&#xff0c;既监督员工的工作状态&#xff0c;又保护着公司的数据安全&#xff0c;这无疑是企业管理者的福音。 今天&#xff0c;我们就来一起探索那些能够精准助力、高效护航的六大电…

张养浩,文坛政坛的双重巨匠

张养浩&#xff0c;字希孟&#xff0c;号云庄&#xff0c;又称齐东野人&#xff0c;生于元世祖至元七年&#xff08;公元1270年&#xff09;&#xff0c;卒于元英宗至治三年&#xff08;公元1329年&#xff09;&#xff0c;享年59岁。他是中国元代著名的文学家、政治家&#xf…