从读写角度,带你了解数仓的 IO 基本框架
本文分享自华为云社区《GaussDB(DWS)基本IO框架》,作者: Naibaoofficial。
行存 IO 管理框架
存储结构
OID(Object identifiers):对象的唯一标识。
每个表存在对应数据库的文件夹中,用 relfilenode 标识。
例如表row1
,可以直接查询对应的文件
每个表的读取写入以页(文件块)为基本单位,页的大小是一个 BLCKSZ,默认 8KB,其结构如下:
Tuple 保存了当前一行的数据,分为 Header 和 Data 两块,头部保存元组的相关信息(列数,事务信息,是否有 Toast 表等)。
每个 Tuple 最大为 2kb,若 Data 过大无法压缩至 2KB,则采用额外的 Toast 表存储,此时 Tuple 内的 Data 保存 Toast 表的相关信息。
GaussDB 行存框架:
这里面涉及到几个比较大的内核机制:
本地缓存:
这里面的本地缓存介绍了三个比较常用的缓存结构,这里直接引用了官方的英文解释。
temp_buffers: Sets the maximum number of temporary buffers used by each database session. These are session-local buffers used only for access to temporary tables.
work_mem: Specifies the amount of memory to be used by internal sort operations and hash tables before writing to temporary disk files.
maintenance_work_mem: Specifies the maximum amount of memory to be used by maintenance operations, such as VACUUM, CREATE INDEX, and ALTER TABLE ADD FOREIGN KEY.
共享内存:可由整个 Gaussdb 共享
包括shared_buffer
和wal_buffer
, 分别用来存放 Page 和 Clog,Wal Segment。
WalWriter,BgWriter:
主要是将共享内存的内容落盘,WalWriter
一般是在事务提交时就需要落盘,但是有时候可以放弃一定的事务一致性原则,从而让WalWriter
异步落盘加快速度。BgWriter
负责将 shared_buffer 中的内容落盘。
外存管理:
负责上层与外存之间的文件交互。
IO 管理框架:读取
读取的过程相对简单,就是从物理文件先装到 shared_buffer 中,然后从shared_buffers
返回相关的结果。
shared_buffers
中就是以 Page 为单位进行存储的,因为每个 Page 的大小是固定的,所以shared_buffers
能存放的 page 个数也就是确定的。这里面就需要考虑一个问题,因为这个资源是共享的,如果一个线程读取了大量的文件,这样势必会使得其他线程的缓存命中率下降。
GaussDB 在这里引入了 Ringbuffer 的机制,可以限制一个线程所使用的 shared_buffers 的大小,从而解决掉这个问题。
IO 管理框架:写入
写入操作是增加的新的元组,Update 操作相当于先 Delete,再 Insert。
INSERT
UPDATE
将旧元组标记为 Dead,然后插入新的元组,由 Vacuum 负责清理。当然,这里面 Data 变为
DELETE
只是用来描述删除的是此 Tuple,实际上 Data 当中的值是不变的。写入的整体逻辑:
GaussDB 行存在写入时,将元组信息先写入到shared_buffers
,然后用 bgwriter 刷入磁盘,这样在事务提交时就可以避免磁盘的 IO 开销,提升性能,为了保证一致性和恢复,使用 wal 日志和 checkpoints 可以实现日志先落盘(也可以异步)和 redo 等操作。
列存的 IO 管理框架
列存的存储单元
列存的存储单元为 CU(CStore Unit)
CU 的大小为 8k 对齐
适合大批量导入的场景
同一列的 CU 存在一个新文件中,大于 1GB 时,切换到新文件中。
列存用一个 CUDesc 的行存表描述 CU 的相关信息,可以理解成为一个 Toast 表。
CUDesc:行存表,记录 CU 的相关信息, 主要属性如下:
col_id,cu_id: 第 col_id 列,第 cu_id 个 CU
min, max, row_count, size
cu_mode: information mask(RLE,LZ4,Delta 表等)
cu_pointer:指向每一个 CU,记录 delete bitmap
magic:和 CU 头部的 magic 相同,校验使用
CU 结构
列存索引
这里介绍两个索引,C-Btree 和 Psort,这里不做过多介绍。主要涉及的是 IO 相关的内容。
C-Btree
索引结构和行存无差别,同样以行存形式存储
C-Btree 可以提升点查效率
存储 key->ctid(cu_id, offset)
过程:
根据 B-tree 索引找到 ctid 集合
对集合进行批量排序(减少 IO 开销)
在 CUDesc 找到对应的 cu_id,根据 offset 找到数据
举例,等值查询 n=49, 范围查询 23<n<64。
PSort
PSort 是一个聚簇索引,对索引进行排序,然后将排序后的索引和行号存入一个新的表,用单独的列存表存储。简单示意如下,图片来源:https://www.modb.pro/db/108155
IO 管理框架:读取
读取过程:
根据 where 条件,做 MIN/MAX 过滤的谓词条件
加载 CUDesc
MIN/MAX 过滤
读取 CU 到 CU Cache 中
解析并填充
CacheMgr: 用来缓存 CU 到内存中,可以提高重复查询的性能。
CU 的物理文件:
CStore_1.0: 当前基本不怎么实用
CStore_2.0: 重整了 CU 的文件结构,避免列数过多导致文件结构复杂。
IO 管理框架:写入
列存的插入要分两种情况,少量的插入和大量的插入,列存主要是对大批量数据设计的,因此为了弥补小量插入的打包 CU 性能开销,设计了一个 delta 行存表,用来记录插入结果,可以减少膨胀和提升性能,最后定期的整理。
写入框架如下
列存的删除比较简单,如果是 delta 表,先从 delta 表中删除满足谓词条件的记录,然后在 CUDesc 表中更新待删除 CU 的 delete_bitmap。
版权声明: 本文为 InfoQ 作者【华为云开发者社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/21c22551d3e27c94206f29a8c】。文章转载请联系作者。
评论