打造次世代分析型数据库(七):向量化计算层缓存
作者介绍
azurezhao(赵阳),腾讯云数据库高级工程师,具备多年存储经验,包括文件存储、kv 存储、数据库存储等。目前在腾讯专注于 CDW PG 数据库内核相关的研发。
1. 整体架构和设计目标
向量化计算层缓存(VectorTableSlot Cache, 下面简称 VTS-Cache)。和传统的 OLTP 数据按行聚簇方式不同,在 OLAP 场景下,查询大多数是对某些列进行的,数据存储按照列式存储,查询运算时的数据也是按照列式存储,如下图所示。当前每次执行都需要去存储层读取数据,会有开销,所以考虑引入一层缓存层。但是在 OLAP 场景下,因为数据很大,如果使用一个类似 OLTP 下的 BufferPool 的磁盘-内存映射缓存,会因为频繁的换入换出导致缓存几乎不生效。所以考虑引入一层执行层的缓存,缓存的粒度是如下的 ColumnVector,因为缓存的数据会带 Qualification,所以能比较大的过滤一些数据,从而减少数据 cache 的量,并且支持 DML,不影响数据的一致性,支持缓存的 LRU 淘汰机制,目前只对 Estore 支持,后续会考虑支持 Heap 表,从而为 HTAP 提供统一的缓存层。
2. 竞品对比
2.1 查询结果缓存(MySQL)
缓存语句,通过配置项和规则(内存大小、语句条件是否含有变量等等)将满足要求的语句和结果缓存在 query_cache 中,并且使用 LRU 规则做内存替换。
优点:对于前端透明,如果缓存命中,能有很大的收益。
缺点:必须语句一模一样,发生 DDL、DML 后数据全部失效,导致命中率不高。
在高版本 MySQL 中已经去除了这个特性。
2.2 物理文件映射(PG)
通过内存块和物理文件按照 block 大小做映射,数据访问如果没有在内存中,则在磁盘中读取到内存中,再返回给上层。
优点:粒度很细,不同 query 可以复用。在 OLTP 系统下,数据量不多,并且大多数是点查点写的 SQL,TP 数据遵循 LRU 的假设,所以 cache 的命中率很高。
缺点:对于磁盘文件远大于内存、并且数据访问很大的 OLAP 场景,cache 命中率很低。
3. VTS-Cache 生成
本章将介绍 VTS-Cache 生成原理和细节。如下图所示
只对 EstoreseqScan 生效。
是否是 Estore 并且 vector_buffer_size 大于 0。
是否缓存没命中。
是否表的大小小于 vector_buffer_size/3,分区表看整个表的大小。
做完 Qualification 之后,将数据 merge 进新开辟暂存区。
暂存区如果满了,则去 freelist 找到 CVDItem,将暂存区存储下来,清空暂存区。
如果有 tiny buffer,需要存下 tinybuffer_freelist 找到可用的 tiny buffer,做内存拷贝,并且记录下每个 tiny buffer 原始的内存地址 baseaddress。
3.1 VTS-Cache 使用
本章将介绍 VTS-Cache 使用原理和细节。如下图所示
判断是否满足使用 VTS-Cache。
在 htab_vts_cache_for_rel 寻找是否有对应 relation 的缓存。
如果 htab_vts_cache_for_rel 有对应的 cache,则遍历这个列表。
遍历每一个 CVDItem 里面的 query_id,在 htab_vts_cache_for_condition 里面寻找,判断 scankey 是否满足并且 query 里面的 attnums 是否是 cache 里面的子集。
如果不满足,则将本次 query_id 插入临时 hashtab,如果下次再遇到,则跳过。
找到满足条件的 query_id,然后找到这个 query_id 所对应的全部 CVDItem。
判断每个 attnum 对应的 CVDItem 数量是否一致,如果不一致,则说明发生过内存淘汰,需要重新选取满足的 query_id。
将满足要求的 CVDItem 的 refcount+1,使用完之后或者事务 abort 的时候 将 refcount-1.
在拷贝 tinybuffer 的时候,需要同步将 cv_vals 里面的项跟 baseaddress 做相应偏移
3.2 VTS-Cache 的使用举例
图解如下:
3.2.1 原始数据
3.2.2 命中缓存场景 1,列包含
3.2.3 命中缓存场景 2,qualification 包含
3.3 VS-Cache 的 DML
发生 update 或者 delete。
将发生变更的 relation 的 oid 和 siloid 记录到链表 invalid_list。
等待表结构自身变更完成。
遍历 invalid_list,对比 VTS-Cache 里面的内容,如果有匹配的,则将 CVDItem 的 xmax 置位为当前的 xid。
将事务提交。
查询时候需要对比 CVDItem 里面的 xmax 和当前的事务 id,和普通的 MVVC 事务处理机制相同。
3.4 VTS-Cache 的内存回收逻辑
VTS-Cache 的内存回收按照朴素的 LRU 算法进行回收,需要注意的是,缓存块需要按需被 pin 住,直到使用结束,并且考虑各种异常场景需要释放引用计数,否则会有缓存泄露。
此外,VTS-Cache 和普通的缓存不同,它是由一条 query 产生多个有关联的 cache,所以回收内存也需要按照 query 级别来回收关联所有 cache 块。
3.5 VTS-Cache 在 HTAP 系统中的运用
对于一个典型的 HTAP 应用,我们会将普通 heap 表里面按行存储的数据存储到按列聚簇的内存数据结构 VectorTableSlot 中,然后按照向量化的方式做运算,加速运算过程,也就是所谓的“行转列”。
对于运算层来说,拿到的是 VectorTableSlot 这个数据结构,如果我们对于行存的查询也构建一套 VTS-Cache,就能为 OLTP 和 OLAP 提供统一的运算层缓存,进而加速 HTAP 场景运算。
4. 总结
VTS-Cache 是一个 OLAP 场景下向量化执行缓存,考虑 OLAP 场景下处理的数据量比较大,直接用传统的内存-文件映射的缓存难以解决缓存的低效命中。所以考虑使用执行层的缓存,能够过滤大多数的数据,并且比类似 MySQL 的语句缓存更加灵活,能够方便支持 Heap 表,为 OLAP 和 OLTP 提供统一的 HTAP 执行层缓存。
评论