引言
数据库系统的设计长期以来在两种架构间进行权衡:纯内存数据库(In-Memory Database Systems)与基于磁盘的数据库(Disk-Based Database Systems)。前者提供极高的性能,但受限于DRAM的成本和容量;后者具备良好的扩展性和成本效益,但在I/O方面存在固有性能瓶颈。
论文《Umbra: A Disk-Based System with In-Memory Performance》提出了一种新的数据库架构——Umbra,旨在结合上述两种架构的优点。Umbra是一个基于SSD的系统,其核心目标是在工作集(Working Set)能够载入内存时,提供与纯内存数据库相当的性能;同时,当数据超出内存容量时,能够高效地利用SSD进行数据交换,实现透明的扩展。
值得一提的是,Umbra已经登顶宽表分析的知名榜单ClickBench,并且在cold read(数据在磁盘上)和hot read(数据已经缓存在内存中)相比第二名均有明显优势,且Umbra在TPC-H上的性能据说相比DuckDB也有显著优势,Umbra的性能强大可见一斑。Umbra源于知名数据库学者Thomas Neumann领衔的实验室,该实验室在过去十多年间对数据库内核研究的影响力可以说是世界上首屈一指。
本文将对Umbra的关键技术进行深入解析,重点讨论其缓冲管理器、内存管理、并发控制及查询执行模型,并分析这些设计如何共同作用以达成其设计目标。
1. 核心问题:传统固定大小页面的局限性
传统磁盘数据库系统的缓冲管理器(Buffer Manager)通常采用固定大小的页面(Fixed-size Pages),例如8KB或16KB。这种设计简化了缓冲池内的内存分配和地址计算,但为上层的数据管理带来了显著的复杂性和性能开销:
大对象存储(Large Object Storage):当一个数据对象(如长字符串、BLOB或用于压缩的字典)的大小超过单个页面时,必须将其分割存储在多个页面中。在访问该对象时,系统需要执行多次I/O操作,并在内存中进行数据的重组(Reassembly),这一过程引入了显著的CPU和I/O开销。
数据布局复杂性:为处理跨页对象,数据库需要在页面元数据中维护复杂的指针或链接结构,这增加了数据布局的复杂性,并可能导致额外的间接寻址开销。
这些由固定大小页面设计所引发的问题,是传统磁盘系统难以达到内存系统性能水平的一个根本原因。Umbra的设计出发点即是解决这一核心问题。
2. Umbra的核心创新:基于可变大小页面的缓冲管理器
Umbra架构的基石是一个新颖的缓冲管理器,它原生支持可变大小的页面(Variable-size Pages)。页面大小按2的幂次增长(例如64KB, 128KB, 256KB...),使得大部分数据对象都可以被连续地存储在单个页面内,从而避免了数据分割和重组的开销。
为实现这一目标,Umbra必须解决可变大小页面管理中的一个经典难题:内存碎片(Memory Fragmentation)。如果直接在物理内存中分配不同大小的块,很快会导致外部碎片,即存在足够的总空闲内存,但没有一块连续的内存能满足较大的分配请求。
Umbra通过利用现代操作系统的虚拟内存(Virtual Memory)机制来规避此问题。其具体实现如下:
尺寸类别(Size Classes):系统根据页面大小将页面组织成不同的尺寸类别。
虚拟地址空间预留:在系统初始化时,Umbra为每个尺寸类别通过 mmap 系统调用预留一个独立的、连续的虚拟地址空间区域。该区域的大小被设定为整个缓冲池的总大小。此操作仅保留地址范围,不分配实际的物理内存,因此开销极低。
虚拟到物理的动态映射:当一个页面需要从SSD加载到内存时,缓冲管理器会在其对应尺寸类别的虚拟地址空间中为其分配一个固定的虚拟地址(称为Buffer Frame)。然后,数据被读入该地址。此时,操作系统内核的页表机制会负责将该虚拟地址映射到一块可用的物理内存页帧上。物理页帧在物理内存中的位置是否连续,对Umbra是完全透明的。
页面换出与物理内存回收:当一个页面被从缓冲池中换出(Evict)时,系统会调用 madvise 并使用 MADV_DONTNEED 标志。这个调用会通知操作系统,与该虚拟地址范围关联的物理内存可以被回收,但虚拟地址本身保持有效。这确保了即便有其他线程乐观地访问该地址,也不会导致段错误(Segmentation Fault),而是会读到零值,从而简化了并发控制逻辑。
下图(论文图1)展示了该缓冲管理器的内存布局:
图中假设缓冲池大小为512KB,最小页面为64KB。可以看到,对于Size Class 0 (64KB)到Size Class 3 (512KB)的每个类别,都预留了512KB的虚拟内存。灰色的“active page”表示已映射物理内存的页面,所有活动页面的物理内存总和受限于缓冲池的大小。
通过这种设计,Umbra在逻辑上获得了为每个尺寸类别服务的、无碎片的连续内存池,而将复杂的物理内存管理完全委托给了操作系统。
3. 支持高性能的配套机制
为充分发挥可变大小页面缓冲管理器的优势,Umbra还集成了一系列配套技术。
3.1. 指针转换(Pointer Swizzling)与地址翻译
为了最小化页面地址的查找开销,Umbra避免了使用全局哈希表等集中式结构。它采用了指针转换技术,通过一个64位的swip(smart pointer)来引用页面。
swip的结构(论文图2)如下:
已转换(Swizzled)状态:当页面位于内存中,swip的最低有效位为0,其余位直接存储页面的虚拟内存地址。对该页面的访问可直接通过指针解引用完成,无额外开销。
未转换(Unswizzled)状态:当页面在磁盘上,swip的最低有效位为1,其余位编码了页面的持久化ID(PID)和尺寸类别。访问时,系统检测到标志位为1,随即调用缓冲管理器加载页面,加载完成后再将swip转换为Swizzled状态。
这种去中心化的地址翻译机制,使得对已缓存数据的访问路径与纯内存数据库完全相同。这项技术,也正是之前我们介绍过的LeanStore的核心技术之一。
3.2. 版本化锁存器(Versioned Latches)与并发控制
高并发下的锁(Latch)竞争是数据库性能的主要瓶颈之一。Umbra采用了基于版本号的乐观并发控制机制来管理对页面的并发访问。
每个缓冲帧(Buffer Frame)的头部包含一个64位的版本化锁存器(论文图3):
结构:该锁存器的高59位用作版本计数器(Version Counter),低5位表示锁的状态(如:未锁定、共享锁定、排他锁定)。
读操作:读线程在访问页面前记录当前的版本号。完成数据读取后,再次检查版本号。如果版本号未变,说明读取期间无写操作发生,读取有效。如果版本号改变,说明发生了写冲突,读操作需要重试。此过程无需获取任何锁。
写操作:写线程必须获取排他锁。在修改数据完成后,它会递增版本号,然后释放锁。
这种机制极大地降低了读操作的同步开销,使得读密集型和混合型工作负载能够获得很高的并发度。这种基于版本号的乐观并发控制机制已经成为高性能数据库的常见技巧之一,在LeanStore、VMCache、Tabby等库中都可以见到。
3.3. 自适应查询编译与执行模型
纯内存数据库HyPer的一个特点是JIT(Just-in-Time)编译,将查询直接编译为机器码。但这对于短查询而言,编译开销可能超过执行收益。Umbra对此进行了改进,采用了一种自适应编译策略:
解释执行启动:查询首先被快速编译成一种内部字节码,并由一个轻量级虚拟机解释执行。这确保了查询的启动延迟非常低。
热点检测与JIT编译:执行引擎会监控字节码的执行频率。当某个代码块(例如一个循环体)的执行次数超过预设阈值时,该代码块被识别为“热点”。此时,系统会触发LLVM编译器,将这个热点代码块编译成高度优化的机器码。
代码替换:编译完成后,执行引擎会将后续对该代码块的调用重定向到新生成的机器码版本。
一个查询被分解为多个流水线(Pipelines)和步骤(Steps)。这种细粒度的分解使得系统可以精确地监控和优化执行计划中的性能关键部分,为自适应编译提供了基础。
这种混合执行模型在短查询的低延迟和长查询的高吞吐量之间取得了有效的平衡。
4. 实验验证与性能分析
论文通过一系列实验来验证Umbra设计的有效性。
缓冲管理器开销:通过与一个禁用页面换出、模拟纯内存行为的Umbra版本对比,实验表明,当数据完全在内存中时,完整功能的缓冲管理器引入的性能开销平均低于6%。这证实了其在缓存命中情况下的高效性。
I/O性能:在冷缓存条件下,Umbra的顺序扫描操作能够达到1.13 GiB/s的吞吐量,接近所用SSD设备的硬件极限。这表明其I/O路径经过了良好优化,没有成为瓶颈。
系统对比:与前身HyPer和另一个列存数据库MonetDB相比,Umbra在标准基准测试(JOB, TPC-H)上表现出显著的性能优势。相较于HyPer,其性能提升(JOB上平均3.0倍,TPC-H上平均1.8倍)主要得益于自适应编译策略,避免了不必要的编译开销。
以下是Umbra在TPC-H上相比于DuckDB的性能,可以看到优势也是非常明显:
结论
Umbra通过引入一个基于虚拟内存和可变大小页面的新型缓冲管理器,成功地解决了传统磁盘数据库在处理大对象和内存管理方面的核心难题。结合指针转换、版本化锁存器和自适应编译等一系列优化技术,Umbra实现了其核心设计目标:在数据缓存于内存时,提供与纯内存数据库无异的性能;在数据超出内存时,高效利用SSD进行扩展,且整个过程对上层应用透明。
Umbra的设计证明,通过充分利用现代硬件和操作系统的能力,可以在性能、成本和扩展性三者之间找到一个新的、更优的平衡点,为未来高性能数据库系统的发展提供了重要的架构参考。

