14.1 内存优先数据库:执行、布局与恢复
元数据卡
- 前置:第 4 章存储与恢复、第 13 章并发控制
- 关键词:内存优先、CPU Cache、分区执行、日志、快照、恢复
- 案例:H-Store/VoltDB、SAP HANA、DuckDB
“数据在内存里”可能只表示缓存命中,也可能表示系统从数据布局、并发控制到恢复路径都以 DRAM 常驻为前提。两者不能混为一谈。
先分清三类系统
| 类型 | 权威状态放在哪里 | 内存不足时怎样处理 | 典型关注点 |
|---|---|---|---|
| 磁盘优先数据库 | 持久化页/段,内存是缓存 | 驱逐页并从存储重读 | Buffer Pool、随机 I/O、WAL |
| 内存优先数据库 | 主工作集以可直接执行的内存布局存在 | 容量规划、分区、冷数据分层或拒绝写入 | Cache/NUMA、日志吞吐、恢复时间 |
| 可溢写分析引擎 | 内存做算子工作区,数据可在持久化文件或外部对象存储 | 算子 spill、重新扫描或分块 | 向量化、内存预算、临时 I/O |
DuckDB 属于嵌入式分析数据库,支持 buffer manager 和 larger-than-memory 查询,不应称为“用操作系统 mmap 代替 Buffer Pool 的纯内存库”。Redis 更接近内存数据结构服务;HANA、VoltDB 一类系统则从不同方向探索内存优先的数据管理。
去掉磁盘缺页,并不等于没有存储成本
把一次访问固定写成“磁盘 10 ms、内存 100 ns,因此快十万倍”会误导设计。真实延迟还受设备、队列深度、页缓存、网络、访问模式和命中率影响。数据常驻内存后,瓶颈会转向:
- CPU cache miss 与指针追逐;
- NUMA 远端内存访问;
- 内存带宽和分支预测;
- 索引、版本、对象头及碎片带来的容量放大;
- 日志刷盘、复制确认和网络往返;
- 热点键上的串行化与排队。
所以内存数据库仍需要执行计划、紧凑布局和并发控制。一个选择性很差的查询不会因为少了磁盘缺页就自动变好。
内存布局可以围绕 CPU 设计
磁盘页式系统倾向用稳定页号和槽位定位记录。内存优先系统可以采用更紧凑的数组、偏移或指针,但直接指针并非免费:重启后地址失效,压缩和移动对象更困难,复制快照也需要重新定位。
设计时应比较:
- 行布局适合点查和更新,列布局适合投影、压缩与扫描;
- 固定宽度访问简单,变长字段需要额外区域或间接引用;
- 节点式索引更新方便但 cache locality 较差,紧凑数组更适合批处理;
- 每行版本和锁元数据会影响实际可容纳数据量。
“无 Buffer Pool、直接指针、编译执行”不是所有内存系统共同具备的三条定律,而是可选设计空间。
分区串行执行:用数据归属消掉局部协调
H-Store/VoltDB 路线把数据按分区归属到执行站点。单分区事务在所属执行上下文中按确定顺序运行,可避免该分区内部大量锁闩开销。
收益依赖一个关键前提:事务的大部分读写都落在同一分区。若分区键选择不当:
- 热门分区限制总吞吐;
- 跨分区事务需要协调多个执行站点;
- 分区迁移和再平衡变得昂贵;
- 长事务阻塞同分区后续工作。
“单线程”也不是整个集群只有一个线程,而是特定分区的事务执行被串行化;网络、日志、复制和其他分区仍可并行。不要据此宣称系统完全没有并发控制。
持久化必须同时回答 RPO 与 RTO
内存是工作状态,不代表可以省略持久化。常见组合是:
- 事务日志记录恢复所需变化或确定性命令;
- 周期快照缩短日志重放区间;
- 复制降低单机故障风险;
- 备份覆盖误删、逻辑损坏和整个故障域丢失。
需要明确两类目标:
- RPO:故障时允许丢多少已接收数据;
- RTO:从快照、日志和副本恢复服务要多久。
只说“有快照”没有说明最近一次快照后的数据窗口;只说“有副本”也没有说明副本是否同步、是否落盘,以及故障切换会不会丢失已确认写入。
恢复路径应定期演练
内存数据集越大,重启加载越可能成为可用性瓶颈。恢复预算应包含:
发现故障
+ 选主或重建实例
+ 读取基础快照
+ 重放增量日志
+ 重建非持久化索引/缓存
+ 追上复制或消息流
+ 校验后开放流量假设快照 128 GiB,实际恢复读取速率为 1 GiB/s,随后还需重放 800 万条记录,回放速率为每秒 20 万条。在两阶段不重叠的假设下,单是加载与重放就需约 128 + 40 = 168 秒;还没计入选主、校验与开放流量。原始数据能放进 RAM,并不能让重启跳过这 168 秒。
日志写得快不代表恢复也快。应该用接近生产数据量的备份定期测试恢复耗时、校验方法和故障切换脚本。
DuckDB 与 HANA 放在这里怎样理解
DuckDB 是嵌入式 OLAP 引擎,采用向量化执行和自己的 buffer manager,可在内存预算下把部分中间状态溢写到磁盘。部分聚合状态和算子组合仍可能超出预算或遇到 spill 限制,不能据此承诺任意查询都不会 OOM。它说明“为内存效率优化”与“数据必须全部放内存”是两回事。
HANA 等内存优先列式系统仍有日志、保存点和持久化层:重启可从完成的保存点读取数据,再回放此后的 redo。参见 SAP HANA 数据与日志卷。内存中的主工作布局服务低延迟访问,持久化层服务恢复;这也不是“磁盘只做偶尔备份”那么简单。
选型检查表
- 工作集和峰值内存放大是多少,而不只是原始数据量?
- 热点能否按稳定业务键分区?跨分区事务比例多高?
- 提交确认要求到本机日志、同步副本还是多故障域?
- 冷启动与全量恢复是否满足 RTO?
- 超过内存预算时系统 spill、淘汰、拒绝写入还是崩溃?
- 是否真的需要完整 SQL、约束和多行事务?
参考
下一节聚焦 Redis:它的原子命令、MULTI/EXEC、RDB 与 AOF 分别能保证什么。