RocksDB性能优化手册

RocksDB 是一个广泛应用于生产环境的、非常复杂的存储引擎。直接阅读源码并非最佳入门方式。

  1. 先学 LevelDB — 理解 LSM-Tree 的基本原理
  2. 找一个实际案例 — 例如 MyRocks,研究它如何使用 RocksDB、选择了哪些组件和参数
  3. 动手跑 Benchmark — 查看 RocksDB 的各种日志输出
  4. 按需看代码 — 根据遇到的组件/参数,选择性地深入源码

你也可以用 RocksDB 实现一个类似 Redis/Cassandra 的服务,学习如何用 KV 拼接出上层数据结构。这样能对 RocksDB 形成直观理解,遇到问题时知道从哪里下手排查。

本文内容:

  1. RocksDB 核心概念的直观理解
  2. 性能观测与问题排查手段
  3. 常见使用场景的组件选择与参数配置

先能看见:观测工具

调优的前提是观测。没有数据就改参数,和赌博没什么区别。

RocksDB 内置统计

Statistics

options.statistics = rocksdb::CreateDBStatistics();
// 随时获取人类可读的统计信息
std::string stats = options.statistics->ToString();

这会给你全局的操作计数、延迟分布等信息。开启有少量性能开销,但排查问题时非常有用。

Compaction and DB Stats

std::string value;
db->GetProperty("rocksdb.stats", &value);
db->GetProperty("rocksdb.cfstats", &value);  // Column Family 级别

这是最重要的调试入口。你能看到:

Perf Context and IO Stats Context

用于追踪单个请求在各阶段的耗时:

rocksdb::SetPerfLevel(rocksdb::PerfLevel::kEnableTimeExceptForMutex);
rocksdb::get_perf_context()->Reset();
// ... 执行操作 ...
std::string perf = rocksdb::get_perf_context()->ToString();

能看到 mutex 等待、block cache 查找、磁盘 IO 等各环节的时间。

系统指标

RocksDB 的性能瓶颈往往体现在系统层面:

指标症状排查方向
Disk Write BandwidthWrite Stall,读延迟抖动检查 compaction 是否跟得上,是否需要限速
Disk Read IOPS读延迟高,P99 抖动检查 block cache 命中率,bloom filter 是否生效
CPU整体吞吐上不去检查压缩算法、bloom filter、compaction 线程数
Space磁盘快满了检查压缩策略、level_compaction_dynamic_level_bytes

iostatdstatperf top 等工具配合观察。

参考链接汇总

资源说明
RocksDB Tuning Guide官方调优指南,必读
Setup Options and Basic Tuning基础配置建议
Write Stalls写停顿的原因和解决方案
CompactionCompaction 机制详解
Block Cache缓存配置
Statistics统计信息详解
Perf Context请求级追踪

RocksDB 直观理解

RocksDB 是个 LSM-Tree 存储引擎。所有调优本质上都是在 读放大写放大空间放大 三者之间做权衡(RUM Conjecture)。你不可能同时优化这三个,最多优化其中两个。

MemTable:写入

数据先写入内存中的 MemTable,写满后变成 Immutable MemTable,然后 flush 到磁盘成为 L0 的 SST 文件。

MemTable 类型选择

类型特点适用场景
SkipList(默认)有序,支持范围查询,并发写入友好通用场景
HashSkipList前缀哈希 + SkipList前缀扫描场景,需配合 prefix_extractor
HashLinkList前缀哈希 + 链表每个前缀下 key 数量少的场景
Vector无序,只支持追加批量导入,写入后立即 flush

大多数情况用默认的 SkipList 就够了。

关键参数

Block Cache:读取的加速器

Block Cache 缓存从 SST 文件读上来的数据块、索引块、filter 块。

Cache 类型

类型特点
LRUCache(默认)标准 LRU,分 shard 减少锁竞争
ClockCacheClock 算法,锁竞争更小,但命中率略低
HyperClockCache更激进的并发优化

关键配置

// 创建 Block Cache
auto cache = NewLRUCache(
    capacity,           // 大小,建议系统内存的 1/3 ~ 1/2
    num_shard_bits,     // 分片数 = 2^num_shard_bits,默认 6
    strict_capacity_limit,  // 是否严格限制容量
    high_pri_pool_ratio     // 高优先级池比例,放 index/filter
);

BlockBasedTableOptions table_options;
table_options.block_cache = cache;
table_options.cache_index_and_filter_blocks = true;  // 重要!
table_options.pin_l0_filter_and_index_blocks_in_cache = true;

必须开启 cache_index_and_filter_blocks=true。否则 index 和 filter 会在堆外分配,内存不可控,容易 OOM。

Partitioned Index/Filter

对于大数据量场景(几百 GB 以上),index 和 filter 本身就很大。可以开启分区:

table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch;
table_options.partition_filters = true;
table_options.metadata_block_size = 4096;

这样只缓存用到的部分,减少内存占用。

Compaction:写放大的根源

LSM-Tree 的核心是把随机写变成顺序写。代价是后台需要不断做 compaction,合并整理数据。

三种 Compaction 策略

策略写放大读放大空间放大适用场景
Level(默认)高(10-30x)低(~10%)读多写少,空间敏感
Universal低(可控)高(可达 2x)写多读少,SSD 寿命敏感
FIFO极低TTL 缓存场景

Level Compaction 的层级结构

L0: [SST] [SST] [SST] [SST]  ← 可能有重叠
L1: [--SST--] [--SST--]      ← 无重叠,有序
L2: [----SST----] [----SST----] [----SST----]
...
Lmax: 最大一层,存放最多数据

Write Stall:被限流了

当 RocksDB 写入速度超过 compaction 处理速度时,会触发写限流或停写。

Stall 触发条件

条件默认值行为
L0 文件数 ≥ level0_slowdown_writes_trigger20限速写入
L0 文件数 ≥ level0_stop_writes_trigger36停止写入
Pending compaction bytes 过多-限速/停写
MemTable 数量过多-停止写入

诊断方法

db->GetProperty("rocksdb.is-write-stopped", &value);
db->GetProperty("rocksdb.actual-delayed-write-rate", &value);

或者看 LOG 文件里的 Stalling 关键字。


重要的参数

内存相关

参数默认值建议值说明
write_buffer_size64MB64-256MB单个 MemTable 大小
max_write_buffer_number23-6MemTable 最大数量
min_write_buffer_number_to_merge11-2flush 前合并几个 MemTable
block_cache (capacity)8MB系统内存 1/3~1/2读缓存大小
cache_index_and_filter_blocksfalsetrue必开,否则 OOM
pin_l0_filter_and_index_blocks_in_cachefalsetrueL0 的 filter 常驻缓存

Compaction 相关

参数默认值建议值说明
compaction_styleLevel-Level/Universal/FIFO
max_background_jobs2CPU 核数的一半后台线程数
max_subcompactions12-4单个 compaction 的并行度
level0_file_num_compaction_trigger44-8L0 触发 compaction 的文件数
level0_slowdown_writes_trigger2020-40L0 触发限速的文件数
level0_stop_writes_trigger3636-64L0 触发停写的文件数
target_file_size_base64MB64-256MBL1 单个 SST 文件大小
max_bytes_for_level_base256MB256MB-1GBL1 层总大小
max_bytes_for_level_multiplier1010层间大小比例
level_compaction_dynamic_level_bytesfalsetrue动态调整层级大小,省空间

压缩相关

参数默认值建议值说明
compressionSnappy-全局压缩算法
compression_per_level-见下文分层压缩
bottommost_compression-ZSTD最底层压缩
block_size4KB4-16KB数据块大小,影响压缩率和点查

推荐的分层压缩配置

options.compression_per_level = {
    kNoCompression,      // L0
    kNoCompression,      // L1
    kLZ4Compression,     // L2
    kLZ4Compression,     // L3
    kZSTD,               // L4
    kZSTD,               // L5
    kZSTD                // L6
};

上层热数据不压缩省 CPU,底层冷数据强压缩省空间。

Bloom Filter

参数默认值建议值说明
filter_policynullptrNewBloomFilterPolicy(10)必开,10 bits per key
whole_key_filteringtruetrue对完整 key 建 filter
optimize_filters_for_hitsfalsetrue (读重)最底层不建 filter,省空间

Bloom Filter 能极大减少点查时的磁盘 IO。10 bits per key 的误判率约 1%。

其他重要参数

参数默认值建议值说明
bytes_per_sync01MB写入多少字节后 sync,平滑 IO
wal_bytes_per_sync01MBWAL sync 频率
enable_pipelined_writefalsetrue提升并发写性能
allow_concurrent_memtable_writetruetrue并发写 MemTable
max_total_wal_size0-WAL 总大小限制,触发 flush
delete_obsolete_files_period_micros6h-清理过期文件的周期

##典型工作负载配置

KV 存储(Kvrocks / Pika)

这类场景通常是 Redis 协议兼容,读写混合,需要平衡延迟和吞吐。

// 内存
options.write_buffer_size = 128 * 1024 * 1024;  // 128MB
options.max_write_buffer_number = 4;
options.min_write_buffer_number_to_merge = 2;

// Compaction
options.compaction_style = kCompactionStyleLevel;
options.level0_file_num_compaction_trigger = 4;
options.level0_slowdown_writes_trigger = 20;
options.level0_stop_writes_trigger = 36;
options.max_background_jobs = 8;
options.level_compaction_dynamic_level_bytes = true;

// 压缩
options.compression_per_level = {kNoCompression, kNoCompression, 
    kLZ4Compression, kLZ4Compression, kZSTD, kZSTD, kZSTD};

// Cache(假设 32GB 内存的机器)
table_options.block_cache = NewLRUCache(10 * 1024 * 1024 * 1024LL);  // 10GB
table_options.cache_index_and_filter_blocks = true;
table_options.filter_policy.reset(NewBloomFilterPolicy(10));

Kvrocks 特有优化

SQL 存储(MyRocks / TiKV)

SQL 场景特点:大量点查、范围扫描、事务支持。

MyRocks 典型配置

rocksdb_max_open_files=-1
rocksdb_max_background_jobs=8
rocksdb_max_subcompactions=4
rocksdb_block_size=16384
rocksdb_block_cache_size=8G

# 写缓冲
rocksdb_write_buffer_size=256M
rocksdb_max_write_buffer_number=4

# Compaction
rocksdb_default_cf_options=
  level0_file_num_compaction_trigger=4;
  level0_slowdown_writes_trigger=20;
  level0_stop_writes_trigger=36;
  target_file_size_base=128M;
  max_bytes_for_level_base=512M;
  level_compaction_dynamic_level_bytes=true;
  compression_per_level=kNoCompression:kNoCompression:kLZ4:kLZ4:kZSTD:kZSTD

# Bloom Filter 对点查很重要
rocksdb_whole_key_filtering=ON

TiKV 典型配置

TiKV 使用两个 RocksDB 实例:raftdb(存 Raft 日志)和 kvdb(存数据)。

[rocksdb]
max-background-jobs = 8
max-sub-compactions = 3

[rocksdb.defaultcf]
block-size = "64KB"
block-cache-size = "8GB"
write-buffer-size = "128MB"
max-write-buffer-number = 5
compression-per-level = ["no", "no", "lz4", "lz4", "lz4", "zstd", "zstd"]
level0-file-num-compaction-trigger = 4
level0-slowdown-writes-trigger = 20
level0-stop-writes-trigger = 36

流处理状态存储(Flink)

Flink 使用 RocksDB 作为状态后端,特点是写多读少、checkpoint 时需要快速遍历。

Flink RocksDB 配置

RocksDBStateBackend backend = new RocksDBStateBackend("hdfs://...");

// 通过 RocksDBOptionsFactory 配置
public class CustomOptionsFactory implements ConfigurableRocksDBOptionsFactory {
    @Override
    public DBOptions createDBOptions(DBOptions currentOptions, 
                                      Collection<AutoCloseable> handlesToClose) {
        return currentOptions
            .setMaxBackgroundJobs(4)
            .setMaxOpenFiles(-1);
    }

    @Override
    public ColumnFamilyOptions createColumnOptions(
            ColumnFamilyOptions currentOptions,
            Collection<AutoCloseable> handlesToClose) {
        return currentOptions
            .setCompactionStyle(CompactionStyle.LEVEL)
            .setLevelCompactionDynamicLevelBytes(true)
            .setWriteBufferSize(128 * 1024 * 1024)
            .setMaxWriteBufferNumber(4)
            .setMinWriteBufferNumberToMerge(2)
            .setTargetFileSizeBase(64 * 1024 * 1024)
            .setCompressionType(CompressionType.LZ4_COMPRESSION);
    }
}

Flink 特殊考虑

Flink 配置文件方式

state.backend.rocksdb.memory.managed: true
state.backend.rocksdb.memory.fixed-per-slot: 256mb
state.backend.rocksdb.block.cache-size: 64mb
state.backend.rocksdb.writebuffer.size: 64mb
state.backend.rocksdb.writebuffer.count: 4
state.backend.rocksdb.compaction.level.target-file-size-base: 64mb

快速排查清单

遇到性能问题,按这个顺序检查:

写入慢 / Write Stall

  1. rocksdb.stats 里的 Stalls 统计
  2. 检查 L0 文件数是否频繁触发阈值
  3. 检查 max_background_jobs 是否太小
  4. 检查磁盘写入带宽是否打满
  5. 考虑增大 level0_slowdown_writes_triggerlevel0_stop_writes_trigger
  6. 写极重场景考虑换 Universal Compaction

读取慢

  1. 检查 Block Cache 命中率
  2. 检查 Bloom Filter 是否开启且生效
  3. 检查是否有大量 L0 文件(读需要检查所有 L0)
  4. 用 Perf Context 看时间花在哪里
  5. 检查磁盘 IOPS 是否打满

空间增长过快

  1. 检查 level_compaction_dynamic_level_bytes 是否开启
  2. 检查压缩算法配置,底层应该用 ZSTD
  3. 检查是否有 compaction 积压(pending bytes)
  4. 检查是否有删除/覆盖大量数据但还没 compaction 掉

内存 OOM

  1. 首先检查 cache_index_and_filter_blocks 是否为 true
  2. 检查 Block Cache 大小是否合理
  3. 检查 MemTable 数量和大小
  4. 大数据量场景考虑 Partitioned Index/Filter
  5. 检查 max_open_files,-1 表示不限制

RocksDB 调优核心就两点:

  1. 先观测。用 statistics、rocksdb.stats、perf context 搞清楚瓶颈在哪
  2. 再权衡。读放大、写放大、空间放大,根据你的场景选择优化方向

默认配置对大多数场景已经够用。如果要调,一次只改一个参数,用 db_bench 验证效果。

最后,这些项目的调优实践值得参考:

他们踩过的坑,你不用再踩一遍。

RocksDB BlobDB 深入分析
RocksDB 可视化分析