第5届天池全球数据库大赛赛道1: 云原生数据库PolarDB业务数据压缩挑战,选择了数据库行业较为常用的页压缩作为赛题原型,分为初赛和复赛两个阶段。
其中初赛不测评性能,排名也不计入决赛排名的权重之中,仅作为选手了解和尝试比赛的窗口,同时也是赛事方迭代赛题细节的窗口。选手通过初赛实现了规定接口下最基础的页压缩功能,再在复赛中持续优化性能表现提高排名,最终叠加决赛现场讲演得分,评出第5届天池全球数据库大赛各项大奖。
赛题解读
赛题背景
数据库产品进入云环境中时,“按量计费”成为了普遍的计费方式,相比本地部署时较为粗放的成本控制,云环境中用户非常关心自己的每一分钱是否发挥了应有的价值。因此数据压缩成为了云上数据库存储引擎的核心竞争力。
数据库存储引擎在实现数据压缩时有多种技术路线,如页压缩、行压缩、列压缩、前缀压缩、字段编码压缩等,同时可以针对redolog、binlog、undolog、主键及二级索引等不同的数据类型,选择不同的压缩方法。
考虑到赛题 “场景真实、抽象简单” 的需要,以及选手上手难易程度的考量,我们选择页压缩为大背景,设计本次PolarDB业务数据压缩赛题。页压缩对数据库存储引擎的逻辑侵入最小,能明显减少持久化数据用量,提高带宽的使用效率。但对页内的具体数据往往作为单纯的字符串处理,缺少更精细的编码语义,压缩率更难做到非常低。
赛题范围
赛题选择了“Buffer Pool以下,文件系统以上”作为赛题中选手实现的范围。
Buffer Pool是数据库存储引擎中对性能影响最大的组件之一,如果这部分交由选手自行实现,将极大提高选手实现的难度和复杂度,最终性能评测成绩也会变成“比谁的Buffer Pool实现性能更好”,这严重背离了我们“数据压缩”的大背景。因此:
- 限制了选手内存使用在50MB以内
- 复赛中对数据持久化提出要求,即数据写完成后Kill进程重启,数据不丢失
- 数据读写的trace采集自真实的PolarDB测试实例,真实的BP读写Pattern中,因LRU的存在,同一个Page的持久化访问间隔通常比较长
都是避免选手试图在数据压缩的实现中再次引入一个内部的Buffer Pool实现。即便这样做了,也很难拿到收益。
部分云上数据库会选择直接基于块存储构建存储引擎,取消文件系统。但是PolarDB选择基于一个分布式文件系统PolarFS来构建存储引擎。这一独特的软件栈为PolarDB带来了部分优势:
- 兼容性更好,能无缝适配本地盘、通用云存储、高性能PolarStore网盘等多种存储方式
- 能更精细得管理数据块,对DB无感得完成数据打散等操作,提高性能
- 能够实现数据的冷热分层、数据Cache等附加功能,降本增效
在赛题设计上,基于文件系统接口,实现上更简单的同时,也更贴合PolarDB的真实场景。如果让选手直接基于块设备接口实现,无疑会大大提高实现难度和复杂度。
使用文件系统时,不可避免的将引入Linux Page Cache的影响。初赛并不对性能作测评,因此放开了Linux Page Cache,导致初赛测评环境表现出了远超ESSD本身的IO性能。复赛中将对Page Cache做出限制,保证使用Direct IO的选手才能表现更好的性能。使用Direct IO也是数据库存储引擎应有的保证。
赛题评分
初赛的初衷是引导选手熟悉赛制的基础上,实现一个最基础的页压缩功能,因此只评测了数据压缩率。
在复赛中,我们力求在相对简单的抽象下,还原最真实的数据库存储引擎数据压缩场景,提供给选手公平纯粹的比赛环境。最终选手从比赛中积累有价值的经验,PolarDB也从选手的智慧中获取有价值的实现。
在真实的数据库存储引擎场景中,数据页通常会被打散至多个Buffer Pool Instance处理,Buffer Pool也会实现完备的并发控制策略,保证不会出现多个线程同时读写一个数据页的情况发生。为模拟这种特性,在复赛的测评中将由多个线程完成对Page读写接口的调用,通常来说线程数等同于CPU核数。测评同样保证对同一个数据页,不会出现多个线程同时读写的情况发生。
同时,存储引擎的性能也是最直接的评分标准。4核CPU,200MB/s左右的Disk IO带宽的环境,对存储引擎通常是一个IO瓶颈的场景。因此存储引擎的性能更多取决于对磁盘IOPS和带宽的使用效率。虽然复赛的评分中不直接对压缩率记分,但最终性能更好的实现少不了高数据压缩率的支撑。除了数据压缩率以外,是否存在读写数据字典造成的IO放大、是否存在多次小IO造成的IOPS瓶颈、是否单次Page读写延迟过高造成性能无法完全释放、是否CPU使用过高从IO瓶颈转为了CPU瓶颈等等因素,都将综合影响最终性能。
马克杯领取攻略
下面我们基于一个非常简单的思路,实现一个简单的提交,帮助选手快速完成从0到1的尝试。
通过对示例trace的分析,我们发现有一些数据页都是“半满”的状态,其中有大量全0的数据内容。因此最直接的想法是如果对数据页中连续的0做文件系统的“punch hole”操作,将连续的0从文件系统到磁盘block的映射中释放:
(将以下内容替换至示例当中)
RetCode DummyEngine::pageWrite(uint32_t page_no, const void *buf) {
ssize_t nwrite = pwrite(fd, buf, 16384, (uint64_t)page_no * 16384);
assert(nwrite == 16384);
// punch hole in file system
for (size_t i=0; i<16384;) {
if (*(uint64_t*)((char*)buf + i) == 0) {
i += 8;
if ((i & 0xfff) == 0) {
uint64_t offset = (uint64_t)page_no * 16384 + i - 0x1000;
int ret = fallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, offset, 0x1000);
assert(ret == 0);
}
} else {
i = ((i + 0x1000) & (~0xfff));
}
}
return kSucc;
}
(将page_engine文件夹打包为一个zip压缩包并提交)
提交测评后发现,确实能够对部分页压缩,获得了一定的压缩比。但大多数数据页中并不存在连续的4KB全0内容,因此压缩效果并不理想。如果还需要进一步提高压缩能力,还需要配合字典压缩等其他方法,对实际的数据内容再次压缩。
但是没关系,我们已经可以去 https://tianchi.aliyun.com/forum/post/576721 领取一支天池定制马克杯了 :)




