第五届全球数据库大赛赛道1:云原生数据库PolarDB业务数据压缩挑战--方案分享


前言

去年抱着打酱油的心态参加了第四届数据库比赛,幸运的获取了优胜奖,所以今年一直在等着第五届比赛。谁知比赛开始后却因为时间冲突,初赛几乎没打,复赛也是开始半个月之后才开始打,一路追赶,最终排名第8,比去年进步了一点点。


一、赛题简介

以下为赛题简介,详细赛题内容可以上阿里云官网天池大赛页面(https://tianchi.aliyun.com/competition/entrance/532117/information)了解。
典型的数据库存储引擎中,用户读写请求并不直接读写持久化数据。Buffer Pool承接了对数据页的本地高频读写,从磁盘中读取数据页成为“Clean Page”,将不再被频繁访问的“Dirty Page”写回磁盘。
在这里插入图片描述

本次赛题专注于单个数据表的持久化数据读写,如图中红色部分所示。选手需要在给定的数据页读写接口下实现数据压缩与解压算法,并设计持久化数据的组织方式。比赛分为初赛和复赛两阶段。初赛与复赛使用相同的赛题,但有不同的要求与评分标准。初赛只评测“数据压缩率”,复赛将加入性能评测。

在给定的数据页读写接口下,实现数据压缩与解压算法。接口示例:

class PageEngine {
public:
// Open engine
static RetCode Open(const std::string& path, Engine** eptr);

// Close engine
virtual ~PageEngine();

// Write 16KB page into disk
virtual RetCode pageWrite(uint32_t page_no, const void *buf);

// Read 16KB page from disk
virtual RetCode pageRead(uint32_t page_no, void *buf);
}
注意:

1.简单起见,所有读写仅涉及单一数据表,因此只使用一个page_no索引数据页
2.参数page_no表示数据库一个数据文件中的数据页编号,从0开始依次递增1,每个数据页固定16KB大小
3.参数path为可供读写的持久化数据目录,除此目录外均没有读写权限
4.参数buf为预先分配的16KB内存空间
  a.在pageRead中需读取磁盘并将数据页16KB内容解压后写入buf中
  b.在pageWrite中需将buf所指向的16KB内容压缩后持久化至磁盘

二、解题思路

1.赛题要点

1、单个数据表的持久化数据读写,需要实现数据压缩与解压
2、每页数据大小16KB,总共不超过655360页,存在重复写
3、多线程读写,但是特定一个数据页只会由特定一个线程来读写
4、某次读写完成后可能kill进程并重启测评
5、读写交替进行,对page cache做了严格限制,内存限制50M

2.解题关键

1、找一个执行效率高,压缩率高的压缩算法
2、为了提高IOPS,需要把随机读写改成顺序读写
3、自己实现一个读写缓存机制
4、考虑重启前数据落盘问题
5、考虑重复写,缓存大小有限制

3.解题思路
在这里插入图片描述

有多个线程负责读写,最后我们是用了40个线程,因为40个相对得分较高,然后要选择一个压缩算法,比如zlib、lz4、brotli、zstd等等,data.ibd是持久化数据的文件,由于需要实现顺序存储,所以需要一个索引数组page array来保存原页码与实际写入页码的对应关系。由于有重启的考量,那么这个数组也需要持久化,page.ibd就是它的持久化文件,在重启后初始化时由page.ibd恢复数组。

二、方案介绍

1.压缩算法

在这里插入图片描述

在解题过程中,我试过3种压缩算法:zlib、lz4和brotli。前期摸索阶段主要是用zlib,后面就换成了lz4和brotli。对比一下这三种压缩算法,zlib压缩率高,压缩速度太慢。lz4压缩率稍低,压缩速度快,比较适合,因为本题主要考量IOPS,对压缩率要求没那么高。但是lz4对sysbench和wiki这2个数据集压缩率太低,特别是sysbench,压缩率接近1,几乎没有压缩,所以我就选择了brotli来压缩sysbench和wiki这2个数据集。brotli压缩率高,压缩速度还行,但是相对更耗内存。

2.随机读写

在这里插入图片描述

赛题提供了一个基线版本,主要实现了一个没有压缩的随机存储方法,在前期摸索的时候我首先就做了一个随机读写的版本。首先设置O_DIRECT,绕过系统缓存page cache,直接写磁盘,这个是DEMO提示的。然后使用开源压缩算法,我开始使用的是zlib。写数据时先压缩,记录压缩后数据size,然后按照页码对应的文件位置写入第N页,读数据时按照页码对应的文件位置读出第N页,然后根据size解压由于O_DIRECT,读写数据时需要按照逻辑块大小(4K)的整数倍对齐。由于每页16KB,所以这里不需要额外处理对齐。

3.顺序读写

在这里插入图片描述

随机读写得分并不高,所以自然就考虑把随机读写改成顺序读写。首先设置32页写缓存,写数据时顺序写入写缓存。写缓存满或者重启前写入持久化文件。定义page_array[655360]数组记录实际页码,数组下标代表原页码,数组内容代表实际写入页码由于有重启考量,用page.ibd文件对page_array数组做持久化,page.ibd文件偏移地址表示实际写入页码,文件内容表示原页码,重启后恢复page_array数组。读数据时直接通过page_array数组找到实际页码读数据,然后解压,重复写时直接覆盖实际页码。这里数据还是按页存储,也不需要额外处理对齐。但是写page.ibd文件需要考虑对齐问题,可以按1024页补齐。

4.优化后顺序读写

在这里插入图片描述

经过了前期的摸索和尝试之后,我再重新梳理了一下整个思路,把各个细节都考虑到位。就做了这个优化后顺序读写的方案,其实就是自己实现了一个写缓存方案。首先压缩后数据直接按位置存储,不再按页存储,所以落盘时需要按块补齐4K对齐,page.ibd文件也有一个缓存,落盘时也需要按块补齐4K对齐,可以与data.ibd文件一起落盘。然后设了40个读写线程,由于内存限制,写缓存大小设为256页,写数据时所有线程可以同时写缓存,当某一个线程判断已到256页时,等待其它线程都写完缓存再落盘,未落盘前从写缓存直接读取数据,落盘后直接读文件。page_array数组记录数据在文件中偏移位置、压缩后数据大小、数据在写缓存中偏移位置、压缩方式。重复写时直接写在文件后面,并修改数组中文件偏移位置,这样数据就没有问题。索引也是写在page.ibd后面,初始化时从前往后重建数组,后面的会覆盖前面的,这样索引也没有问题。有些数据会占用多个位置,压缩率会下降一些,大部分数据集重复写的不多,有两个数据集多一点。最终压缩率在0.39,在0.6范围内。

5.读缓存

在这里插入图片描述

这个定型方案最终得分16000多,其实在这个方案实现过程中,也一直在考虑另外一个问题,就是我的方案主要是对写数据的优化处理,对读数据的优化做的比较少。所以这个方案做完之后,主要就在考虑对读数据的优化方法,即怎样实现一个比较好的读缓存。前期也考虑过使用链表、MAP等来做读缓存,但是效率太低,还要加锁。最后想到了直接使用偏移位置,来实现了一个读缓存,也提高了1000分。为了减少读磁盘,读数据时可以一次读入多页数据,保存在读缓存中,由于内存限制,读缓冲区大小设置为512*16KB,如果可读数据小于它,以实际读取大小为准offset(文件中偏移位置)-read_start_offset(读缓存开始位置)<=read_size(读缓存大小),说明读缓冲区存在该页数据如果每次都读多条没有必要,所以每读2500次更新一次读缓冲区数据,更新读缓冲区数据后如果有该页数据重写落盘动作,落盘后该页数据不能从读缓存读,因为读缓存中该页数据是旧的。

6.重启落盘

前期摸索尝试阶段主要用了以下方案。
每个写线程把需要写的数据插入一个队列然后等待,另起一个独立的线程负责从队列中取出数据写入缓存或者落盘,写入数据完成后唤醒写线程(每次留一条数据给下一个循环处理,这样在重启之前需要等这条数据处理完才唤醒该线程),这个独立的线程循环执行,当一定时间(比如5毫秒)队列中无数据时认为即将重启,把剩余的数据落盘,此时最后一个写线程被唤醒,所有写线程才处理完,才会重启。单线程顺序写,不用加锁,但是写线程等待,未充分利用CPU资源。而且全读数据的时候也会导致落盘,影响了IOPS。
后期定型的时候使用了一个新方案
使用一个原子操作变量来计数,每个线程调用写或者读方法时计数加1,执行完pageWrite或者pageRead方法时计数减1,pageWrite或者pageRead方法执行完之前判断这个原子变量是否为1,即当前只有一个写线程或者读线程,此时认为将要执行重启操作,把写缓存数据落盘。这个方案对重启的判断就比较准确

总结

1、在解题过程中学到了不少新知识,收获良多。解题过程中和赛后在钉钉群与各位大佬交流,发现自己的解题方案还有很多缺陷与不足,也学到了不少新的思路。
2、从结果看还是稍有遗憾,在最后阶段稍微松懈了一些,导致一些本可以优化的点没有去做。主要有两点技术遗憾:
首先压缩算法的选择上有点随意,没有深入的研究,应该选择lz4+zstd的组合。
第二点就是在解题过程中也考虑过多个data文件的方法,但是最后没有去做,最后其实还是有时间去做的。事后看应该每个线程单独建立一个写缓存和读缓存。

下面是我的微信公众号,欢迎大家关注
在这里插入图片描述

  • 33
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值