本文介绍云原生数据仓库AnalyticDB MySQL版的实时存储引擎。

背景

云原生数据仓库AnalyticDB MySQL版作为一款实时数仓产品,为了支持低延迟的写入、更新场景,在传统数仓能力基础上,设计了实时存储引擎。数据写入和更新会以Append_only的方式写入实时存储引擎,经过Compact之后构建索引以支持复杂的计算场景。

实时存储引擎会有以下瓶颈:
  • 在一些大宽表场景下,单行的更新带来了严重的写放大问题。
  • 实时存储引擎内存高频换入换出,Cache Miss高的同时,大量的压缩、解压缩带来CPU瓶颈。
针对以上两类问题,AnalyticDB MySQL版设计了新一代的实时存储引擎。
  • 存储格式上,在确定的I/O单位上设计行列混存格式,使得单行的I/O大小可控。
  • 内存控制上,实现了基于Anti-Caching的缓冲池(Buffer Pool)。

存储格式

最初的AnalyticDB MySQL版实时存储引擎设计是一个列存实现,在宽表更新场景(游戏业务中留存率计算、零售业务中订单统计等)下,I/O放大导致的延迟问题尤为明显。

老版本的AnalyticDB MySQL版实时存储引擎采用RowGroup行列混存的方式,在列存的基础上以行数对齐的方式使得一个组内逻辑行号对齐。这样的设计在宽表场景下存在弊端:当访问一行数据时,磁盘的I/O单位变得不可控。行列混存
当前版本的AnalyticDB MySQL版实时存储引擎为PAX的行列混存实现。在固定大小的Page上以PAX Layout组织数据格式,Page头部维护列数、当前记录数以及空闲大小,其他位记录每个列起始偏移和行粒度的Bitmap信息。存储指针

定长字段的存储会在Minipage中维护,同时每个F-minipage的分配上保证Cache Line对齐。基于PAX Layout的设计,能够确保每个Page的刷盘落到磁盘上都是确定的I/O单位。同时,同一个列的数据仍然保持在一个Minipage上连续布局,在顺序扫描的场景中仍然能够充分利用Cache的能力。

变长字段的存储,AnalyticDB MySQL版用16 Byte存储和表示。存储格式如下图所示:变长字段存储
  • 超过12 Byte的字段:前4 Byte存储字符串长度;中间4 Byte存储前缀;后8 Byte存储指向V-minipage中对应记录的起始地址。
  • 未超过12 Byte的字段:前4 Byte存储字符串长度;后12 Byte存储字符串。

内存控制

在内存控制的演进上,AnalyticDB MySQL版实时存储引擎从最初的LRU-based Cache逐步走向以内存为中心的架构。

Anti-Caching

与传统数据库的Caching机制不同,AnalyticDB MySQL版基于Anti-Caching的设计,将内存作为主存,仅将冷数据淘汰到磁盘,磁盘扮演着备份角色。

Anti-Caching的实现上可以分为以下三类:
  • User-Space:使用应用层的语义优化Ad-hoc的性能。但是,在维护LRU方面会产生一定开销;如果避免维护LRU的开销,实时性会受到影响,不能完全精准地控制内存。
  • Kernel-Space:针对I/O进行调度,同时充分利用硬件特性,但是缺乏应用层面的语义。
  • Hybrid of User-Space and Kernel-Space:将User-Space和Kernel-Space结合在一起的实现方式。
AnalyticDB MySQL版在Anti-Caching的实现上采用了Hybrid of User-Space and Kernel-Space方式,既能够使用应用层的语义优化Ad-hoc的性能,又充分利用硬件特性。
AnalyticDB MySQL版的Buffer Manager在文件系统之上,通过内存映射文件的方式(mmap)维护缓冲池,不同大小的Page都可以加载到Buffer Manager中。当一个Page被淘汰出Buffer Manager时,首先保证该Page成功写回磁盘,随后通过Madvise中的MADV_DONTNEED标记来通知内核立刻重用相关的物理内存。内存控制

Swizzling Pointer

当Page被序列化到磁盘后,系统需要通过逻辑ID (PID)再次访问对应的Page。老版本的AnalyticDB MySQL版采用全局的HashTable来维护逻辑ID的映射关系。这种方法在数据规模较大的场景,存在明显的性能瓶颈,产生较高的Hash开销。

为了避免Hash的开销,AnalyticDB MySQL版采用了Swizzling Pointer的实现方案,以64 bit存储Page的唯一标识。Swizzling Pointer
  • 当Page在内存中时:头部第一个bit标记为0;其余bit用来表征Page的物理地址。
  • 当Page在磁盘中时:头部第一个bit标记为1;第2~7个bit记录Page的SizeClass,SizeClass用来计算Page大小;剩余的57个bit记录Page的逻辑ID。

Swizzling Pointer技术带有一定的去中心化属性,避免了全局Hash的开销;同时在新版本的实时存储引擎中,Page写盘没有采用传统的LZ4、Zstd等压缩算法,使得在CPU密集的场景下,性能有大幅的提升。