深度解读 KaiwuDB 的排序操作

news2024/11/19 23:18:39

 一、单节点执行

在单节点环境执行一条简单的 SQL 语句 SELECT * FROM NATION ORDER BY N_NAME。NATION 是一张小表,只有 25 条记录;对第 2 列 N_NAME 进行升序排列。

1. 抽象语法树

上述示例中的 SQL 语句经过分析器解析后得到 AST,如下图所示:

2. 逻辑计划

将 AST 转换成一个树状结构的 Plan,称之为逻辑查询计划。抽象语法树中的每一个语法元素都被转换成一个查询逻辑单元,例如 scanNode, sortNode, joinNode 等。

逻辑计划可以通过一系列规则进行优化,称之为 RBO(Rule Base Optimization)。

举一个简单的例子,SQL 语句 SELECT * FROM t WHERE a + 1 > 4 通过规则改写可以转换为 SELECT * FROM t WHERE a > 3 。从数据库的计划角度,两者有很大差别。

前者只能扫描全表,每次读取一条记录并计算表达式判断是否符合过滤条件;后者可以利用 a 列索引信息减少扫描范围,即使没有索引也不需要每次进行表达式计算。

例子中的逻辑计划很简单,就是扫描节点 Scan 和排序节点 Sort。命令 Explain SELECT * FROM NATION ORDER BY N_NAME 显示如下:

3. 物理计划

(DistSQLPlanner).PlanAndRun 方法把逻辑计划转换为物理计划,其中递归调用 createPlanForNode 方法生成各个物理算子,交给执行器具体执行。

生成物理计划也是一个优化过程,称之为 CBO(Cost Base Optimization)。例如逻辑计划的表连接,在具体实现时可以交换顺序,也可以有不同的实现方法(Nest Loop Join,Sort Merge Join和Hash Join)。

此时就需要通过代价模型(Cost Model)在巨大的搜索空间中寻找一个合理的物理执行计划。这是一个 NP 问题,所以一般只能找到一个相对最优解。

此外,KaiwuDB 根据底层 KV 数据的分布和预估返回数据集的大小,决定是否需要生成分布式执行计划;这个例子是一个本地执行的物理计划。

逻辑计划节点和物理计划节点并不是一一对应关系,但是这个例子中,逻辑计划中的 Scan 和 Sort 分别对应物理计划中的 TableReader 算子和 Sorter 算子。

4. 执行引擎

最后调用(DistSQLPlanner).Run 方法执行物理计划。执行引擎采用火山模型(Volcano),每一层执行算子通过调用下一层的 Next 方法获取一条记录。执行伪代码如下:

5.排序分析

下面具体分析排序算子操作,首先了解一下数据在各个阶段的存储格式。

(1)数据格式

在 KV 存储引擎中,数据的存储格式如下:

                   *注:Column ID Diff 表示跟前一个列 ID 的差值

TableReader 通过 KV 存储接口,读取一条数据后返回一个 EncDatumRow 对象,里面添加了 KaiwuDB 的编码信息。

Sorter 算子的 fill 方法将 EncDatumRow 对象中的数据解码后加入 MemRowContainer 对象的 chunks 里,用于后续的排序。

chunks 是一个二维数组[][]Datum,Datum 是一个接口,代表 SQL 中的值,具体的实现类包括 DBool, DInt, DString 等。KaiwuDB 中尽量使用原生数据类型,比如 DBool/DInt/DString 分别被定义成 bool/int64/string,并实现了 Datum 接口方法;而 DDecimal 就是一个自定义结构体。

func (s *sortAllProcessor) fill() (ok bool, _ error) {
    ctx := s.EvalCtx.Ctx()       
    for {        
        //input是一个RowChannel对象,Next获取下一条数据        
        row, meta := s.input.Next()        
        ......        

        // rows是一个MemRowContainer对象        
        if err := s.rows.AddRow(ctx, row); err != nil {            
            return false, err        
        }    
    }    
    s.rows.Sort(ctx)    
    
    s.i = s.rows.NewFinalIterator(ctx)    
    s.i.Rewind()    
    return true, nil
}

直觉上 []Datum 应该对应表里的一行数据,但是为了减少 golang 里切片扩容带来的影响,KaiwuDB 将 64 行数据打包放在了一个 []Datum 里。Nation 表的数据在 chunks 中打印显示如下:

内存中的 chunks 结构:

UML 类图

(2)排序方法

最常用的排序执行算子叫做 sortAllProcessorSorter,将全部待排序结果读入后(内存或磁盘),进行一次排序输出最终结果。调用时序图如下:

MemRowContainer 的 Sort 方法实现了内存中对 chunks 数组的排序:

  • 数组长度小于等于 12 时,采用插入排序;

  • 快速排序,递归最大深度 2*ceil(lg(n+1));

  • 递归到了最大深度,采用堆排序;

// At accesses a row at a specific index.
func (c *RowContainer) At(i int) tree.Datums {
    // This is a hot-path: do not add additional checks here. 
    chunk, pos := c.getChunkAndPos(i)    
    return c.chunks[chunk][pos : pos+c.numCols : pos+c.numCols]
}

// Sort sorts data.
// It makes one call to data.Len to determine n, and O(n*log(n)) calls to
// data.Less and data.Swap. The sort is not guaranteed to be stable.
func Sort(data sort.Interface, cancelChecker *CancelChecker) {
    n := data.Len()   
    quickSort(data, 0, n, maxDepth(n), cancelChecker)
}

// Sort is part of the SortableRowContainer interface.
func (mc *MemRowContainer) Sort(ctx context.Context) {
    mc.invertSorting = false    
    cancelChecker := sqlbase.NewCancelChecker(ctx)    
    sqlbase.Sort(mc, cancelChecker)}

func quickSort(data sort.Interface, a, b, maxDepth int, cancelChecker 
*CancelChecker) {
    for b-a > 12 { // Use ShellSort for slices <= 12 elements
        if maxDepth == 0 {
            heapSort(data, a, b, cancelChecker)            
            return        
        }        
        maxDepth--        
        // Short-circuit sort if necessary.        
        if cancelChecker.Check() != nil {            
            return        
        }       
        mlo, mhi := doPivot(data, a, b)        
        // Avoiding recursion on the larger subproblem guarantees        
        // a stack depth of at most lg(b-a).        
        if mlo-a < b-mhi {            
            quickSort(data, a, mlo, maxDepth, cancelChecker)            
            a = mhi // i.e., quickSort(data, mhi, b)        
        } else {            
            quickSort(data, mhi, b, maxDepth, cancelChecker)            
            b = mlo // i.e., quickSort(data, a, mlo)        
        }    
    }    
    if b-a > 1 {        
        // Do ShellSort pass with gap 6        
        // It could be written in this simplified form cause b-a <= 12        
        for i := a + 6; i < b; i++ {            
            if data.Less(i, i-6) {                
                data.Swap(i, i-6)            
            }        
        }        
        insertionSort(data, a, b)    
    }
}

如果需要排序的数据超出了阈值(WorkMemBytes,默认 64MB),会调用 spillToDisk 方法将 MemRowContainer 中的数据写入 DiskRowContainer 中,后者将 OrderBy 列的信息作为 Key 值写入 KV 存储引擎。

此外为了提高 KV 写入速度,DiskRowContainer 不会每次只写一条数据,而是有一个 buffer 负责累积一批键值对,然后一起写入。

其他的两种排序执行算子包括:

  • sortTopkProcessor: Limit 下推到 Sorter 时,算子只分配 N 行的排序空间,然后进行堆排序;

  • sortChunksProcessor: 多列排序时,如果前 i 列 (0 < i < N) 已经有序,算子逐行读入输入数据,直到前 i 列出现不同值;重复对读入的批次进行排序。处理完数据集后前 i+1 列已经有序,迭代前面的步骤对后续列进行排序,直到结果集多列排序完成。

二、多节点执行

在三节点环境上执行 SQL 语句 SELECT * FROM LINEITEM ORDER BY L_SHIPDATE。LINEITEM 是一张大表,有约 6000 万条数据;对第 11 列 L_SHIPDATE 进行升序排列。

1. 逻辑计划

逻辑计划和单节点环境的计划相似。

2. 物理计划

KaiwuDB 在多节点环境中将数据分片放在不同的节点中,每个分片数据有多个备份。示意图如下:

分布式执行计划根据 Span 信息分为多个 tableReader 算子在多个节点上执行;AddNoGroupingStage 方法将 sorter 算子加入到所有的 tableReader 算子之后,对各个节点上的分片数据进行排序。

最后 FinalizePlan 方法会增加一个 No-op 算子,归并汇总最终的结果集。生成的分布式物理计划如下:

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

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

相关文章

(文章复现)面向配电网韧性提升的移动储能预布局与动态调度策略(2)-灾后调度matlab代码

参考文献&#xff1a; [1]王月汉,刘文霞,姚齐,万海洋,何剑,熊雪君.面向配电网韧性提升的移动储能预布局与动态调度策略[J].电力系统自动化,2022,46(15):37-45. 1.基本原理 1. 1 目标函数 在灾害发生后&#xff0c;配电网失去主网供电&#xff0c;设故障的持续时间可根据灾害…

基于SpringBoot+Vue的酒店管理系统设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

代码随想录 二叉树 Java(二)

文章目录 &#xff08;*中等&#xff09;222. 完全二叉树的节点个数&#xff08;*简单&#xff09;110. 平衡二叉树&#xff08;*简单&#xff09;257. 二叉树的所有路径&#xff08;简单&#xff09;404. 左叶子之和&#xff08;简单&#xff09;513. 找树左下角的值&#xff…

设计模式的原则(一)

相信自己&#xff0c;无论自己到了什么局面&#xff0c;请一定要继续相信自己。 新的世界开始了&#xff0c;接下来&#xff0c;老蝴蝶带领大家学习一下设计模式。 我们先了解一下 设计原则 一.设计模式 一.一 设计原则 设计模式常用的七大原则: 单一职责原则接口隔离原则…

【项目】接入飞书平台

前言 项目有和飞书打通的需求&#xff0c;因为是第一次打通&#xff0c;摸索过程还是花了些时间的&#xff0c;现在相关笔记分享给大家。 步骤 1、熟悉开发文档 熟悉飞书的开发文档&#xff1a;开发文档 &#xff0c;找到你需要的接口&#xff0c;拿我为例&#xff0c;我需…

长生的秘密:肠道菌群代谢组学

欲遂长生志&#xff0c;但求千金方。长生不老是人类文明历程中苦苦追寻的目标之一&#xff0c;影响人类寿命的因素也复杂多样&#xff0c;包括但不限于遗传因素如性别、线粒体状态、染色体稳定性、端粒长短、疾病、干细胞活性&#xff1b;环境因素如肠道微生物、饮食、运动、空…

如何解决“RuntimeError: CUDA Out of memory”问题

当遇到这个问题时,你可以尝试一下这些建议,按代码更改的顺序递增: 减少“batch_size” 降低精度 按照错误说的做 清除缓存 修改模型/训练 在这些选项中,如果你使用的是预训练模型,则最容易和最有可能解决问题的选项是第一个。 修改batchsize 如果你是在运行现成的代码或…

页面置换算法的模拟与比较

前言 在计算机操作系统中&#xff0c;页面置换算法是虚拟存储管理中的重要环节。通过对页面置换算法的模拟实验&#xff0c;我们可以更深入地理解虚拟存储技术&#xff0c;并比较不同算法在请求页式虚拟存储管理中的优劣。 随着计算机系统和应用程序的日益复杂&#xff0c;内存…

技术管理方法论

今天来跟大家分享一下我对于技术管理的理解。先介绍一下对于管理最普遍的认识&#xff0c;我们每一个人在公司里面都有两种类型的角色&#xff0c;一种是通过个人的能力和产出来实现组织利益的最大化&#xff0c;另外一类人就是通过管理使得一群人产出结果最大化。 也就是我们…

阿里P8传授的80K+星的MySQL笔记助我修行,一周快速进阶

MySQL 是最流行的关系型数据库之一&#xff0c;广泛的应用在各个领域。下面这些问题对于程序员的你来说应该很常见&#xff0c;来看看你面对这些问题是否会胆怯? MySQL数据库作发布系统的存储&#xff0c;一天五万条以上的增量&#xff0c;预计运维三年,怎么优化&#xff1f; …

Linux防火墙学习笔记8

iptables的白名单和黑名单&#xff1a; iptables -t filter -I INPUT -s 192.168.2.20 -p tcp --dport 80 -j DROP 之前内网的机器可以访问到80端口&#xff0c;现在添加了这条规则&#xff0c;那么就192.168.2.10这个用户就不能访问了。 案例&#xff1a;白名单&#xff1a;…

系列二、RuoYi前后端分离(登录密码加密去除公钥)

一、问题描述 系列一虽然实现了登录时密码加密&#xff0c;但是/getPublicKey返回的结果中&#xff0c;把私钥也返回了&#xff0c;这样显然是不合理的&#xff0c;如下&#xff1a; 二、后端代码修改 2.1、RSAUtil package com.tssl.business.utils;import org.apache.comm…

STM32单片机蓝牙APP LORA无线远程火灾报警温度烟雾监控系统

实践制作DIY- GC0145蓝牙APP LORA无线远程火灾报警 基于STM32单片机设计---蓝牙APP LORA无线远程火灾报警 二、功能介绍&#xff1a; 1个主机&#xff1a;STM32F103C系列单片机LCD1602显示器蜂鸣器 LORA无线模块3个按键&#xff08;设置、加、减&#xff09;HC-05蓝牙模块&…

Node包管理工具

包管理工具 package代表了一组特定功能的源码集合。 管理包的应用软件&#xff0c;可以对包进行下载安装&#xff0c;更新&#xff0c;删除&#xff0c;上传等操作 借助包管理工具&#xff0c;可以快速开发项目&#xff0c;提高开发效率 前端常用包管理工具 npm Node Pack…

【算法系列 | 3】深入解析排序算法之——选择排序

序言 你只管努力&#xff0c;其他交给时间&#xff0c;时间会证明一切。 文章标记颜色说明&#xff1a; 黄色&#xff1a;重要标题红色&#xff1a;用来标记结论绿色&#xff1a;用来标记一级论点蓝色&#xff1a;用来标记二级论点 决定开一个算法专栏&#xff0c;希望能帮助大…

基于Hexo和Butterfly创建个人技术博客,(3) 创建博客文章及文章模板配置

Hexo官司网查看 这里 笔者个人站查看 这里 特别说明&#xff1a; hexo博客站点发布的文件全是静态文件&#xff0c;没有任何后台服务。博文的发布过程是&#xff1a;1、在本地用hexo new命令创建.md文件----2、经hexo g命令生成.html文件-----3、再通过hexo d命令发布到远程主机…

知网CN期刊《才智》简介及投稿邮箱

《才智》杂志成立于2001年&#xff0c;隶属吉林省人事厅&#xff0c;是经国家新闻出版总署批准的&#xff0c;吉林省人事系统唯一一本面向全国公开发行的杂志。是一本专业发表各类论文评定职称的省级理论性杂志。以挖掘各行各业拔尖人才、促进科教兴国、振兴人才市场为己任&…

python套接字(二):实现一个服务器和多客户端连接

文章目录 前言一、问题二、实现一个服务器连接多个客户端1、问题分析2、代码实现a、服务器端b、客户端 3、运行 前言 在上一篇博客python套接字(一)&#xff1a;socket的使用简单说明了一下套接字的使用&#xff0c;也实现了使用套接字来传输消息&#xff0c;但是也有一个问题…

深度学习应用篇-推荐系统[12]:经典模型-DeepFM模型、DSSM模型召回排序策略以及和其他模型对比

【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍&#xff1a;【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化…

JavaWeb笔记(五)

JavaWeb后端 经过前面的学习&#xff0c;现在终于可以正式进入到后端的学习当中&#xff0c;不过&#xff0c;我们还是需要再系统地讲解一下HTTP通信基础知识&#xff0c;它是我们学习JavaWeb的基础知识&#xff0c;我们之前已经学习过TCP通信&#xff0c;而HTTP实际上是基于T…