新消息:Helius 收购 Light Protocol
什么是 RocksDB?嵌入式键值存储
博客/工程

什么是 RocksDB?嵌入式键值存储

Developer Experience EngineerX 上的 0xIchigoLinkedIn 上的 0xIchigoGitHub 上的 0xIchigo
阅读需 9 分钟

RocksDB 是应用最广泛的存储系统之一,但几乎没有人会直接配置它。Kafka、通过 MyRocks 使用它的 MySQL、TiDB、YugabyteDB、Ceph,以及绝大多数 Solana 验证者的账本,都以它为底层存储。 

对于这样一个承载关键负载的系统,相关资料却少得出奇。 

官方文档是很好的参考资料,却不适合作为入门读物;其他大多数讲解则假设读者已经掌握相关知识,例如了解什么是 LSM 树。

本文是探索 RocksDB 内部工作原理系列的第一篇,先从最显而易见的问题开始。

什么是 RocksDB?

RocksDB 是一种针对高速存储优化的可嵌入式持久化键值存储。它将键和值作为任意字节数组处理,对其排序,并持久存储到磁盘上。 

首先要理解的最重要一点是:RocksDB 是一个库,而不是服务器。没有需要连接的进程,不用开放端口,也没有需要学习的查询语言。

RocksDB 集成到应用中,在应用进程内运行,并在本地磁盘上读写文件。这正是 RocksDB 速度快的原因——应用代码与所操作的数据之间无需经过网络。它非常精简,因为它有意将复制、分片和查询交由上层系统实现。

简单来说,RocksDB 是一个存储引擎——TiDB 和 YugabyteDB 等数据库都围绕这个核心组件构建。

RocksDB 和 LevelDB 一样吗?

不一样,但它们源自同一个代码库。RocksDB 始于 2012 年,是 LevelDB 的一个分支。LevelDB 是由 Google 的 Jeff Dean 和 Sanjay Ghemawat 编写的轻量级键值存储。 

LevelDB 面向规模较小的环境构建,例如浏览器的 IndexedDB 后端或单个嵌入式设备。它据此做出了一系列设计选择,包括单线程压缩、保守使用内存,以及尽量减少调优需求。

Facebook(现为 Meta)的工程师以此为基础,针对服务器工作负载重新构建了它。目标是在持续写入压力下处理远大于内存的数据集,同时充分利用现代硬件。 

RocksDB 于 2013 年开源,此后与 LevelDB 产生了显著差异,陆续提供多线程压缩、列族、事务、备份、合并运算符、可插拔压缩策略,以及一份出了名的超长调优选项列表。

LevelDB 和 RocksDB 虽然一脉相承,但早在十年前,就已经不能再将两者视为可互换的系统。

RocksDB 如何工作?

RocksDB 构建于**日志结构合并树(LSM 树)**之上。这种数据结构以更复杂的读取过程换取更高的写入吞吐量。 

简单来说,传入的写操作会先写入一个名为 memtable 的内存缓冲区。同时,这些写操作还会追加到磁盘上的预写日志中,以确保数据持久性。 

当 memtable 写满后,它会被冻结,并刷新到磁盘中,形成一个不可变的有序文件,称为有序字符串表(SST)。SST 文件会逐渐累积成分层结构,而一个称为压缩的后台进程会持续合并这些文件,丢弃已被覆盖的值和已删除的键,从而维持整洁、有序且持久的结构。

读取时会先检查 memtable,再逐层查找不同级别的 SST 文件。布隆过滤器让 RocksDB 可以跳过绝不可能包含目标键的文件,块缓存则将热点数据保留在内存中,因此大多数读取最多只需访问一两个文件。

LSM 树与 B 树有什么区别?

大多数传统数据库使用的结构是 B 树。它会原地更新数据,因此随机写入会分散到磁盘各处。LSM 树则采用追加和批处理方式,形成大块连续写入,这正符合 SSD 和高摄取量工作负载的需求。代价是,一个键的当前值可能分布在多个文件中,因此需要通过压缩、布隆过滤器和缓存来控制这种开销。 

LSM 树中的每项设计决策,最终都需要在三种压力之间权衡:

  • 写放大
  • 读放大
  • 空间放大

改善其中任意两项,往往都会使第三项变差。 

这个三角关系是理解 RocksDB 调优最重要的思维模型。 

RocksDB 有哪些用途?

当应用需要在本地磁盘上获得快速、持久、有序的键值存储,又不想承担运行独立数据库服务器的开销时,就会使用 RocksDB。具体示例比类别划分更能清楚说明这种模式。

Kafka Streams 会将每个处理任务的状态——持续更新的聚合、连接和窗口计算——保存在本地 RocksDB 存储中。这样,状态规模可以超出内存容量,并能在重启后恢复,同时不必为每次查询增加一次网络往返。

Meta 的 ZippyDB 在 RocksDB 外封装了复制层、分片管理和配置服务,从而提供完全托管的分布式键值存储。其分工十分清晰:RocksDB 负责存储,所有服务器层面的功能都围绕它构建。

TiDB 的存储层在每个节点上运行 RocksDB,作为分布式 SQL 数据库的底层引擎。它将表结构编码到键前缀中,因此扫描一张表就变成对有序键的一次连续读取。

每个基于 Agave 的 Solana 验证者都会将账本写入 RocksDB,并以时隙作为键,让连续的账本数据在磁盘上相邻存放。

后两个例子尤其值得关注,因为键会按顺序存储——默认按字节排序,也可以使用自定义比较器——从而高效执行范围扫描和前缀查找。

在基于 RocksDB 的真实系统中,相当多的模式设计都归结为如何利用这种排序特性。

谁在使用 RocksDB?

除了上述系统,任何需要嵌入式、久经实战且针对写入优化的存储引擎的系统,都可以将 RocksDB 作为基础。团队选择它,是因为可以直接继承 Meta 十余年的生产环境打磨成果,而不必从头编写一个存储引擎。

值得注意的是:

  • Kafka Streams 将它用作状态存储的默认引擎
  • Apache Flink 提供 RocksDB 状态后端,用于存放因规模过大而无法完全保留在内存中的检查点状态
  • MyRocks 将它嵌入为 MySQL 存储引擎。Meta 开发 MyRocks,旨在减少全球部分最大规模 MySQL 集群的存储占用
  • TiDB 的存储层 TiKV 使用独立的 RocksDB 实例分别存储 Raft 日志和键值数据
  • YugabyteDB 的文档存储 DocDB 使用 RocksDB
  • Ceph 的 BlueStore 使用 RocksDB 存储对象存储元数据
  • ArangoDB 从 3.4 版开始将 RocksDB 作为默认存储引擎,并从 3.7 版开始将其作为唯一的存储引擎

RocksDB 主要用于 SSD 上写入密集、数据规模超出内存容量的工作负载。本文稍后还会回到这一点。

Solana 如何使用 RocksDB?

Solana 是一种高性能、低延迟的权益证明区块链,以注重速度、效率和消费级应用而闻名。Solana 的账本存储在 RocksDB 中。Agave 验证者客户端将账本保存在 Blockstore 中。该组件包含一个 RocksDB 数据库,并将其拆分为彼此独立、可单独调优的键空间。 

不同的列族分别保存分片数据和分片纠删码(即通过网络传入的原始账本数据单元)、交易状态、地址到签名的索引,以及各种元数据。

与大多数数据库工作负载相比,验证者的写入模式极为严苛。验证者必须以几乎单调递增的时隙编号为键,持续按网络线速摄取分片,而且要永不停歇。未经裁剪的完整账本归档远超数百 TB,并且每年至少增长数十 TB。

在分层压缩策略下,分片列族产生了大量后台压缩工作,导致验证者开始遭遇写入停顿。这是 RocksDB 的内置机制:当压缩进度落后时,它会减慢数据摄取速度。 

大约在 2021 年,分片列族改用了 FIFO 压缩。这是一种极简策略,在达到容量上限时直接删除最旧的文件。对于一般工作负载而言,FIFO 通常并不安全。不过,由于分片键基本按近乎单调递增的时隙顺序到达,验证者可以直接删除最旧的文件,因为其中保存的正是最早的时隙。这使压缩策略几乎完美适配了工作负载特征。

后来对分层压缩的优化将其 I/O 放大降低到 FIFO 不再具有任何优势的程度,因此 FIFO 路径于 2024 年 6 月被弃用,并在同年 11 月被移除。

Firedancer 是 Jump Crypto 使用 C 语言编写的 Solana 验证者客户端。它完全弃用 RocksDB,转而采用专门构建的内部存储层。

一个从零构建的存储引擎能否超越经过十余年打磨和调优的 RocksDB,是验证者工程领域一个值得关注的开放问题。

哪些情况下不适合使用 RocksDB?

RocksDB 不适用的场景比它的普及程度所暗示的更多。它的调优范围极其庞大,拥有数百个相互影响并不直观的选项,因此配置不当往往是常态,而非例外。 

更糟的是,RocksDB 的默认配置只是合理,并非最优。开发者需要理解前述放大效应之间的权衡,才能真正提升性能。持续的高强度数据摄取可能超过后台压缩的处理速度,从而触发写入停顿,而这种情况往往发生在系统最繁忙的时候。

此外,RocksDB 是一个库,而不是服务器。典型数据库服务器会提供的一切功能,例如复制、分片、备份、访问控制、查询层和运维工具,都必须自行构建。

工作负载的形态也至关重要。 

RocksDB 是一个面向行、针对点查询和范围扫描设计的引擎。对于需要扫描大范围数据并跨列聚合的分析型工作负载,ClickHouse 之类的列式存储更合适。

这些都不是避开 RocksDB 的理由,而是应当审慎选择它的原因。Helius 在重新设计归档层时直接评估了这些风险,但仍然选择了 RocksDB,因为我们的工作负载正是 RocksDB 所擅长的类型:在大型、以追加写入为主的数据集上执行点查询和紧凑的范围扫描。 

总结

RocksDB 是一种可嵌入、持久化、有序的键值存储,它牺牲运维便利性,换取本地磁盘上的极致性能。它源自 LevelDB,随后由 Meta 针对 SSD 和多核计算机进行生产级强化。如今,从流处理器和分布式 SQL 数据库,到存储集群和 Solana 账本,世界各地的多种系统都在内部使用 RocksDB。

如果大规模构建高性能 RocksDB 系统让你感到兴奋,欢迎加入我们。

Solana 正全速迈向全球金融结算层,而其底层基础设施所运行的,正是本系列介绍的技术体系。使用预算所能买到的顶级硬件,在全球规模下解决极具挑战性的系统问题。

我们的工程团队正在招聘。前往 helius.dev/careers 查看所有空缺职位。

订阅 Helius

及时了解 Solana 开发的最新动态,并在我们发布新内容时收到更新