📣

TiDB Cloud Serverless 现已更名为
Starter
!此页面由 AI 自动翻译,英文原文请见
此处。

RocksDB 概述

RocksDB 是一种 LSM-tree 存储引擎,提供键值存储和读写功能。它由 Facebook 开发,基于 LevelDB。用户写入的键值对首先插入到 Write Ahead Log (WAL),然后写入内存中的 SkipList(一种数据结构,称为 MemTable)。LSM-tree 引擎将随机修改(插入)转换为对 WAL 文件的顺序写入,因此它们比 B-tree 引擎提供更高的写入吞吐量。

当内存中的数据达到一定大小时,RocksDB 会将内容刷新到磁盘上的 Sorted String Table (SST) 文件中。SST 文件以多个层级(默认最多 6 层)组织。当某一层的总大小达到阈值时,RocksDB 会选择部分 SST 文件并将它们合并到下一层。每一层的大小是前一层的 10 倍,因此 90% 的数据存储在最后一层。

RocksDB 允许用户创建多个 Column Families (CFs)。CFs 拥有自己的 SkipList 和 SST 文件,但共享同一个 WAL 文件。这样,不同的 CF 可以根据应用特性设置不同参数,同时不会增加对 WAL 的写入次数。

TiKV 架构

TiKV 的架构如下图所示:

TiKV RocksDB

作为 TiKV 的存储引擎,RocksDB 用于存储 Raft 日志和用户数据。一个 TiKV 节点中的所有数据共享两个 RocksDB 实例:一个用于 Raft 日志(通常称为 raftdb),另一个用于用户数据和 MVCC 元数据(通常称为 kvdb)。kvdb 中有四个 CF:raft、lock、default 和 write:

  • raft CF:存储每个 Region 的元数据。占用空间非常小,用户无需关心。
  • lock CF:存储悲观事务的悲观锁和分布式事务的 Prewrite 锁。事务提交后,相关的 lock CF 数据会迅速删除。因此,lock CF 中的数据通常非常少(少于 1 GB)。如果 lock CF 中的数据大量增加,意味着有大量事务等待提交,系统可能存在 bug 或故障。
  • write CF:存储用户实际写入的数据和 MVCC 元数据(事务的开始时间戳和提交时间戳)。当用户写入一行数据时,如果数据长度不超过 255 字节,则存储在 write CF 中;否则存储在 default CF 中。在 TiDB 中,二级索引只占用 write CF 的空间,因为非唯一索引存储的值为空,唯一索引存储的值为主键索引。
  • default CF:存储长度超过 255 字节的数据。

RocksDB 内存使用

为了提升读取性能并减少对磁盘的读取操作,RocksDB 会根据一定大小(默认 64 KB)将存储在磁盘上的文件划分为块。在读取块时,首先检查数据是否已存在于内存中的 BlockCache。如果存在,可以直接从内存读取,无需访问磁盘。

BlockCache 根据 LRU 算法淘汰最少使用的数据。默认情况下,TiKV 将系统内存的 45% 分配给 BlockCache。用户也可以通过修改 storage.block-cache.capacity 配置自行设置合适的值,但不建议超过系统总内存的 60%。

写入 RocksDB 的数据首先写入 MemTable。当 MemTable 的大小超过 128 MB 时,会切换到新的 MemTable。TiKV 中有两个 RocksDB 实例,总共 4 个 CF。每个 CF 的单个 MemTable 大小限制为 128 MB。最多可以同时存在 5 个 MemTables,否则会阻塞前端写入。此部分占用的最大内存为 2.5 GB(4 x 5 x 128 MB)。不建议修改此限制,以免增加内存消耗。

RocksDB 空间使用

  • 多版本:由于 RocksDB 是基于 LSM-tree 结构的键值存储引擎,MemTable 中的数据会先被刷新到 L0。由于文件按生成顺序排列,L0 中的 SST 范围可能存在重叠。因此,同一键在 L0 中可能存在多个版本。当文件从 L0 合并到 L1 时,会按一定大小(默认 8 MB)切割成多个文件。同一层级内的每个文件的键范围不重叠,因此在 L1 及后续层级中,每个键只有一个版本。
  • 空间放大:每一层的文件总大小是前一层的 x(默认 10)倍,因此 90% 的数据存储在最后一层。这也意味着 RocksDB 的空间放大不超过 1.11(L0 的数据较少,可忽略)。
  • TiKV 的空间放大:TiKV 有自己的 MVCC 策略。当用户写入一个键时,实际写入 RocksDB 的数据为 key + commit_ts,也就是说,更新和删除操作也会写入新键到 RocksDB。TiKV 会定期删除旧版本的数据(通过 RocksDB 的 Delete 接口),因此可以认为用户在 TiKV 上存储的数据实际空间被放大到 1.11 加上最近 10 分钟内写入的数据(假设 TiKV 能及时清理旧数据)。

RocksDB 后台线程和压缩

在 RocksDB 中,将 MemTable 转换为 SST 文件以及在不同层级合并 SST 文件的操作由后台线程池完成。默认后台线程池大小为 8。当机器的 CPU 数少于或等于 8 时,默认大小为 CPU 数减一。

一般来说,用户无需修改此配置。如果用户在一台机器上部署多个 TiKV 实例,或者机器的读负载较高、写负载较低,可以适当调整 rocksdb/max-background-jobs 为 3 或 4。

WriteStall

RocksDB 的 L0 与其他层级不同。L0 的 SST 按生成顺序排列,且 SST 之间的键范围可能重叠。因此,在查询时必须依次查询每个 SST。为了不影响查询性能,当 L0 中文件过多时,会触发 WriteStall 阻塞写入。

遇到写入延迟突然大幅增加时,可以先检查 Grafana RocksDB KV 面板上的 WriteStall Reason 指标。如果是由于 L0 文件过多引起的 WriteStall,可以将以下配置调整为 64。

rocksdb.defaultcf.level0-slowdown-writes-trigger rocksdb.writecf.level0-slowdown-writes-trigger rocksdb.lockcf.level0-slowdown-writes-trigger rocksdb.defaultcf.level0-stop-writes-trigger rocksdb.writecf.level0-stop-writes-trigger rocksdb.lockcf.level0-stop-writes-trigger

文档内容是否有帮助?