RocksDB 二级缓存
本文分享自天翼云开发者社区《RocksDB 二级缓存》,作者:b****n
RocksDB 团队正在实现对非易失性介质上的块缓存的支持。可以看作是 RocksDB 当前的易失性块缓存的扩展。非易失性块缓存充当第二层缓存,其中包含从易失性缓存中逐出的块。当这些块由于访问而变得更热时,它们会被提升到易失性缓存中。
此功能适用于数据库位于远程存储或云存储上的情况。非易失性缓存在 RocksDB 中被称为 SecondaryCache。通过维护比 DRAM 大一个数量级的二级缓存,需要从远程存储读取的次数会更少,从而减少读取延迟和网络带宽消耗。
从用户的角度来看,本地闪存缓存将支持以下需求:
1.打开数据库时提供指向二级缓存的指针。
2.能够在同一进程中跨 DB 共享二级缓存。
3.一台主机上有多个二级缓存。
4.通过确保缓存键的可重复性,支持跨进程重启和重启持久化缓存。
设计
在为 SecondaryCache 设计 API 时,我们可以选择使其对 RocksDB 代码可见或将其隐藏在 RocksDB 块缓存后面。将它隐藏在块缓存后面有几个优点:
1.允许灵活地将块插入二级缓存。块可以在从 RAM 层逐出时插入,也可以立即插入。
2.无论是否配置了二级缓存,它通过提供统一的接口来降低 RocksDB 代码的其余部分的复杂性。
3.使并行读取、在缓存中查看预取、故障处理等更容易。
4.如果需要,可以更轻松地扩展为压缩数据,并允许将其他持久性媒体添加为附加层。
我们决定通过将二级缓存隐藏在 block cache 后面,使二级缓存对其余 RocksDB 代码透明。我们需要解决的一个关键问题是缓存项的内存分配和所有权,插入二级缓存可能需要由其分配内存。这意味着需要将缓存对象中可以转移到二级缓存的部分复制出来,并且在查找时需要将二级缓存中存储的数据提供给对象构造函数。对于 RocksDB 缓存对象,如数据块、索引和过滤器块以及压缩字典,解包涉及复制出块的原始未压缩块,打包涉及使用原始未压缩数据构造相应的块/索引/过滤器/字典对象。
我们考虑的另一种选择是现有的 PersistentCache 接口。但是,我们决定不追求它并最终弃用它,原因如下:
1.它直接暴露给表读取器代码,这使得实现不同策略将其扩展到更复杂的准入控制策略变得更加困难。
2.该接口不允许自定义内存分配和对象打包/解包,因此无论如何都必须定义新的 API。
3.当前的 PersistentCache 实现非常简单,没有任何准入控制策略。
应用程序接口
RocksDB 的块缓存和二级缓存之间的接口被设计为允许可插拔实现。对于企业内部使用,我们计划使用带有 wrapper 的 Cachelib 提供插件实现,并使用 folly 等 fbcode 库,RocksDB 无法直接使用,来高效实现缓存操作。下图显示了块的插入和查找流程:
二级缓存中的项目由 SecondaryCacheHandle 引用。句柄可能不会立即准备好或具有有效值。调用者可以调用 IsReady() 以确定它是否准备就绪,并且可以调用 Wait() 以阻塞直到它准备就绪。调用方必须在准备就绪后调用 Value() 以确定项目是否已成功读取。Value() 必须在失败时返回 nullptr。
二级缓存的用户(例如,通过 LRUCache 间接调用的 BlockBasedTableReader)必须实现 CacheItemHelper 中定义的回调,以便于解包/打包对象以保存到二级缓存和从中恢复。必须实现 CreateCallback 以从二级缓存中的原始数据构造可缓存对象。
二级缓存提供者必须提供 SecondaryCache 抽象类的具体实现。
SecondaryCache 由用户通过在 LRUCacheOptions 中提供指向它的指针来配置。
当前进展
最初的 RocksDB 对二级缓存的支持已经合并到主分支中,并将在 6.21 版本中提供。这包括在实例化 RocksDB 的 LRU 缓存(易失性块缓存)时为用户提供一种配置二级缓存的方法,将从 LRU 缓存驱逐的块溢出到闪存缓存,将块从 SecondaryCache 读取到 LRU 缓存,更新工具例如 cache_bench 和 db_bench 来指定闪存缓存。相关的 PR 是 #8271、#8191 和 #8312。
我们使用上述 PR 以及基于 Cachelib 的 SecondaryCache 实现制作了端到端解决方案的原型。我们运行了一个 mixgraph 基准测试来模拟真实的读/写工作负载。结果显示,与没有本地缓存相比,使用本地闪存缓存可提高 15%,网络读取减少约 25-30%,缓存未命中率相应减少。
未来展望
在短期内,我们计划执行以下操作以将 SecondaryCache 与 RocksDB 完全集成:
1.使用 DB session ID 作为缓存键前缀,保证唯一性和可重复性
2.优化 MultiGet 和迭代器工作负载的闪存缓存使用
3.压力测试
4.更多基准测试
从长远来看,我们计划将其部署在 Facebook 的生产环境中。
评论