TiDB 源码阅读系列文章(十)Chunk 和执行框架简介

2024-04-08 03:18

本文主要是介绍TiDB 源码阅读系列文章(十)Chunk 和执行框架简介,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

什么是 Chunk

TiDB 2.0 中,我们引入了一个叫 Chunk 的数据结构用来在内存中存储内部数据,用于减小内存分配开销、降低内存占用以及实现内存使用量统计/控制,其特点如下:

  • 只读

  • 不支持随机写

  • 只支持追加写

  • 列存,同一列的数据连续的在内存中存放

Chunk 本质上是 Column 的集合,它负责连续的在内存中存储同一列的数据,接下来我们看看 Column 的实现。

1. Column

Column 的实现参考了 Apache Arrow,Column 的代码在 这里。根据所存储的数据类型,我们有两种 Column:

  • 定长 Column:存储定长类型的数据,比如 DoubleBigintDecimal

  • 变长 Column:存储变长类型的数据,比如 CharVarchar

哪些数据类型用定长 Column,哪些数据类型用变长 Column 可以看函数 addColumnByFieldType 。

Column 里面的字段非常多,这里先简单介绍一下:

  • length

    用来表示这个 Column 有多少行数据。

  • nullCount

    用来表示这个 Column 中有多少 NULL 数据。

  • nullBitmap

    用来存储这个 Column 中每个元素是否是 NULL,需要特殊注意的是我们使用 0 表示 NULL,1 表示非 NULL,和 Apache Arrow 一样。

  • data

    存储具体的数据,不管定长还是变长的 Column,所有的数据都存储在这个 byte slice 中。

  • offsets

    给变长的 Column 使用,存储每个数据在 data 这个 slice 中的偏移量。

  • elemBuf

    给定长的 Column 使用,当需要读或者写一个数据的时候,使用它来辅助 encode 和 decode。

1.1  追加一个定长的非 NULL 值

追加一个元素需要根据具体的数据类型调用具体的 append 方法,比如: appendInt64、appendString 等。

一个定长类型的 Column 可以用如下图表示:

我们以 appendInt64 为例来看看如何追加一个定长类型的数据:

  • 使用 unsafe.Pointer 把要 append 的数据先复制到 elemBuf 中;

  • 将 elemBuf 中的数据 append 到 data 中;

  • 往 nullBitmap 中 append 一个 1。

上面第 1 步在 appendInt64 这个函数中完成,第 2、3 步在 finishAppendFixed 这个函数中完成。其他定长类型元素的追加操作非常相似,感兴趣的同学可以接着看看 appendFloat32、appendTime 等函数。

1.2  追加一个变长的非 NULL 值

而一个变长的 Column 可以用下图表示:

我们以 appendString 为例来看看如何追加一个变长类型的数据:

  • 把数据先 append 到 data 中;

  • 往 nullBitmap 中 append 一个 1;

  • 往 offsets 中 append 当前 data 的 size 作为下一个元素在 data 中的起始点。

上面第 1 步在 appendString 这个函数中完成,第 2、3 步在 finishAppendVar 这个函数中完成。其他边长类型元素的追加操作也是非常相似,感兴趣的同学可以接着看看 appendBytes、appendJSON 等函数。

1.3  追加一个 NULL 值

我们使用 appendNull 函数来向一个 Column 中追加一个 NULL 值:

  • 往 nullBitmap 中 append 一个 0;

  • 如果是定长 Column,需要往 data 中 append 一个 elemBuf 长度的数据,用来占位;

  • 如果是变长 Column,不用往 data中 append 数据,而是往 offsets 中 append 当前 data 的 size 作为下一个元素在 data 中的起始点。

2. Row

如上图所示:Chunk 中的 Row 是一个逻辑上的概念:Row 中的数据存储在 Chunk 的各个 Column 中,同一个 Row 中的数据在内存中没有连续存储在一起,我们在获取一个 Row 对象的时候也不需要进行数据拷贝。提供 Row 的概念是因为算子运行过程中,大多数情况都是以 Row 为单位访问和操作数据,比如聚合,排序等。 

Row 提供了获取 Chunk 中数据的方法,比如 GetInt64、GetString、GetMyDecimal 等,前面介绍了往 Column 中 append 数据的方法,获取数据的方法可以由 append 数据的方法反推,代码也比较简单,这里就不再详细介绍了。

3. 使用

目前 Chunk 这个包只对外暴露了 Chunk, Row 等接口,而没有暴露 Column,所以,写数据调用的是在 Chunk 上实现的对 Column 具体函数的 warpper,比如 AppendInt64;读数据调用的是在 Row 上实现的 Getxxx 函数,比如 GetInt64。

执行框架简介

1. 老执行框架简介

在重构前,TiDB 1.0 中使用的执行框架会不断调用 Child 的 Next 函数获取一个由 Datum 组成的 Row(和刚才介绍的 Chunk Row 是两个数据结构),这种执行方式的特点是:每次函数调用只返回一行数据,且不管是什么类型的数据都用 Datum 这个结构体来封装。

这种方法的优点是:简单、易用。缺点是:

  • 如果处理的数据量多,那么框架上的函数调用开销将会非常大;

  • Datum 占用的无效内存太大,内存浪费比较多(存一个 8 字节的整数需要 56 字节);

  • Datum 没有重用,golang 的 gc 压力大;

  • 每个 Operator 一次只输出一行数据,要进行更加缓存友好的计算、更充分的利用 CPU 的 pipeline 非常困难;

  • Datum 中的 interface 类型的数据,统计它的内存使用量比较困难。

2. 新执行框架简介

在重构后,TiDB 2.0 中使用的执行框架会不断调用 Child 的 NextChunk 函数,获取一个 Chunk 的数据。

这种执行方式的特点是:

  • 每次函数调用返回一批数据,数据量由一个叫 tidb_max_chunk_size 的 session 变量来控制,默认是 1024 行。因为 TiDB 是一个混合 TP 和 AP 的数据库,对于 AP 类型的查询来说,因为计算的数据量大,1024 没啥问题,但是对于 TP 请求来说,计算的数据量可能比较少,直接在一开始就分配 1024 行的内存并不是最佳的实践( 这里 有个 github issue 讨论这个问题,欢迎感兴趣的同学来讨论和解决)。

  • Child 把它产出的数据写入到 Parent 传下来的 Chunk 中。

这种执行方式的好处是:

  • 减少了框架上的函数调用开销。比如同样输出 1024 行结果,现在的函数调用次数将会是以前的 1/1024。

  • 内存使用更加高效。Chunk 中的数据组织非常紧凑,存一个 8 字节的整数几乎就只需要 8 字节,没有其他额外的内存开销了。

  • 减轻了 golang 的 gc 压力。Chunk 占用的内存可以不断地重复利用,不用频繁的申请新内存,从而减轻了 golang 的 gc 压力。

  • 查询的执行过程更加缓存友好。如我们之前所说,Chunk 按列来组织数据,在计算的过程中我们也尽量按列来计算,这样既能让一列的数据尽量长时间的待在 Cache 中,减轻 Cache Miss 率,也能充分利用起 CPU 的 pipeline。这一块在后续的源码分析文章中会有详细介绍,这里就不再展开了。

  • 内存监控和控制更加方便。Chunk 中没有使用任何 interface,我们能很方便的直接获取一个 Chunk 当前所占用的内存的大小,具体可以看这个函数:MemoryUsage。关于 TiDB 内存控制,我们也会在后续文章中详细介绍,这里也不再展开了。

3.  新旧执行框架性能对比

采用了新的执行框架后,OLAP 类型语句的执行速度、内存使用效率都有极大提升,从 TPC-H 对比结果 看,性能有数量级的提升。

作者:张建

这篇关于TiDB 源码阅读系列文章(十)Chunk 和执行框架简介的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/884419

相关文章

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步

如何使用 Bash 脚本中的time命令来统计命令执行时间(中英双语)

《如何使用Bash脚本中的time命令来统计命令执行时间(中英双语)》本文介绍了如何在Bash脚本中使用`time`命令来测量命令执行时间,包括`real`、`user`和`sys`三个时间指标,... 使用 Bash 脚本中的 time 命令来统计命令执行时间在日常的开发和运维过程中,性能监控和优化是不

C#如何优雅地取消进程的执行之Cancellation详解

《C#如何优雅地取消进程的执行之Cancellation详解》本文介绍了.NET框架中的取消协作模型,包括CancellationToken的使用、取消请求的发送和接收、以及如何处理取消事件... 目录概述与取消线程相关的类型代码举例操作取消vs对象取消监听并响应取消请求轮询监听通过回调注册进行监听使用Wa

MyBatis框架实现一个简单的数据查询操作

《MyBatis框架实现一个简单的数据查询操作》本文介绍了MyBatis框架下进行数据查询操作的详细步骤,括创建实体类、编写SQL标签、配置Mapper、开启驼峰命名映射以及执行SQL语句等,感兴趣的... 基于在前面几章我们已经学习了对MyBATis进行环境配置,并利用SqlSessionFactory核

PHP执行php.exe -v命令报错的解决方案

《PHP执行php.exe-v命令报错的解决方案》:本文主要介绍PHP执行php.exe-v命令报错的解决方案,文中通过图文讲解的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下... 目录执行phpandroid.exe -v命令报错解决方案执行php.exe -v命令报错-PHP War

Oracle数据库执行计划的查看与分析技巧

《Oracle数据库执行计划的查看与分析技巧》在Oracle数据库中,执行计划能够帮助我们深入了解SQL语句在数据库内部的执行细节,进而优化查询性能、提升系统效率,执行计划是Oracle数据库优化器为... 目录一、什么是执行计划二、查看执行计划的方法(一)使用 EXPLAIN PLAN 命令(二)通过 S

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟 开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚 第一站:海量资源,应有尽有 走进“智听

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言