RocksDB 是一个性能非常强悍的 Key-Value 存储引擎,很多项目包括我们的 TiKV 都使用它来存储数据。但 RocksDB 也因其复杂的配置著称,要让 RocksDB 在不同的机器上面都能有很好的性能,并不是一件容易的事情。很多时候,千辛万苦在一台机器上面的配的配置,在另一台机器上面性能就是不行。
RocksDB 官方专门写了一篇文章,来讲如何 tuning RocksDB,这里,我会结合我们在 TiKV 里面对于 RocksDB 的调优经验,详细的对其解释一下,当然也不会完全翻译。一方面是跟大家共同学习,另一个方面则是让自己更加熟悉 RocksDB。
要了解如何优化 RocksDB,首先需要知道 RocksDB 的核心是 LSM ,不过这个现在外面已经有太多的文章了,就不做过多说明了。不过如果有可能,还是可能写写 RocksDB 具体是如何处理 LSM 的。
Amplification factors
调优 RocksDB 通常就是在三个 amplification 之间做取舍的。
- Write amplification - 也就是我们最经常碰到的写放大,假设我们每秒写入 10MB 的数据,但观察到硬盘的写入是 30MB,那么写放大就是 3。所以对于写入压力比较大的应用来说,我们要做的就是尽量减小写放大。写放大能通过 RocksDB 自己的统计看到。
- Read amplification - 读放大,对于一个 query 需要读取硬盘的次数。譬如一个 query 我们读取了 5 pages,那么读放大就是 5。读放大没法通过 RocksDB 自己的统计很好的查看,通常会通过 iostat 等命令进行评估。
- Space amplification - 空间放大,这个比较好理解,假设我需要存储 10MB 的数据,但实际硬盘占用了 30MB,那么空间放大就是 3。
RocksDB Statistics
在实际调优 RocksDB 的时候,我们用的最多的就是 RocksDB 自己的统计信息,RocksDB 自己有一个 stats_dump_period_sec
参数,可以定期的将统计刷到 RocksDB 自己的 LOG 文件里面。当然,我们也可以直接通过 db->GetProperty("rocksdb.stats")
以及 options.statistics.ToString()
在程序里面直接得到相关的统计信息。
对于 TiKV 来说,现阶段我们使用了 4 个 Column Family,所以统计如下:
** Compaction Stats [default] **
Level Files Size(MB} Score Read(GB} Rn(GB} Rnp1(GB} Write(GB} Wnew(GB} Moved(GB} W-Amp Rd(MB/s} Wr(MB/s} Comp(sec} Comp(cnt} Avg(sec} KeyIn KeyDrop
----------------------------------------------------------------------------------------------------------------------------------------------------------
L0 0/0 0.00 0.0 0.0 0.0 0.0 98.5 98.5 0.0 0.0 0.0 205.4 491 1089 0.451 0 0
L1 5/0 128.68 1.1 127.7 98.7 29.0 120.0 91.0 0.0 1.2 179.1 168.3 730 269 2.715 875M 105M
L2 98/0 1381.58 1.2 176.2 91.0 85.2 120.8 35.6 0.0 1.3 18.8 12.9 9624 2904 3.314 1588M 6900K
L3 472/0 12650.65 1.0 97.3 8.5 88.7 96.2 7.5 26.9 11.3 12.7 12.6 7841 571 13.732 1109M 17M
L4 3228/0 98210.34 0.8 145.3 9.0 136.4 133.8 -2.6 25.6 14.9 12.9 11.9 11502 687 16.742 1775M 392M
Sum 3803/0 112371.25 0.0 546.5 207.2 339.3 569.4 230.1 52.5 5.8 18.5 19.3 30189 5520 5.469 5350M 523M
Int 0/0 0.00 0.0 3.7 2.1 1.6 3.7 2.1 0.4 4.8 24.3 24.3 157 50 3.134 34M 4871K
Uptime(secs): 70217.9 total, 70217.9 interval
Flush(GB): cumulative 98.538, interval 0.775
AddFile(GB): cumulative 0.000, interval 0.000
AddFile(Total Files): cumulative 0, interval 0
AddFile(L0 Files): cumulative 0, interval 0
AddFile(Keys): cumulative 0, interval 0
Cumulative compaction: 569.35 GB write, 8.30 MB/s write, 546.53 GB read, 7.97 MB/s read, 30189.2 seconds
Interval compaction: 3.73 GB write, 0.05 MB/s write, 3.72 GB read, 0.05 MB/s read, 156.7 seconds
Stalls(count): 0 level0_slowdown, 0 level0_slowdown_with_compaction, 0 level0_numfiles, 0 level0_numfiles_with_compaction, 0 stop for pending_compaction_bytes, 0 slowdown for pending_compaction_bytes, 0 memtable_compaction, 0 memtable_slowdown, interval 0 total count
** Compaction Stats [raft] **
Level Files Size(MB} Score Read(GB} Rn(GB} Rnp1(GB} Write(GB} Wnew(GB} Moved(GB} W-Amp Rd(MB/s} Wr(MB/s} Comp(sec} Comp(cnt} Avg(sec} KeyIn KeyDrop
----------------------------------------------------------------------------------------------------------------------------------------------------------
L0 1/0 102.02 0.2 0.0 0.0 0.0 866.7 866.7 0.0 0.0 0.0 440.3 2016 9404 0.214 0 0
L1 4/0 96.85 19.9 974.5 866.6 107.9 331.6 223.8 0.0 0.4 703.6 239.4 1418 1923 0.738 741M 197M
L2 7/3 24.49 0.5 339.3 223.7 115.5 130.4 14.9 0.0 0.6 30.0 11.5 11563 7513 1.539 694M 37M
L3 185/77 5793.93 0.3 1180.5 13.8 1166.7 1171.3 4.6 1.0 84.7 12.0 11.9 100814 1760 57.280 602M 266M
Sum 197/80 6017.28 0.0 2494.3 1104.2 1390.1 2500.1 1109.9 1.0 2.9 22.1 22.1 115810 20600 5.622 2038M 500M
Int 0/0 0.00 0.0 32.2 9.4 22.8 32.0 9.2 0.0 4.3 16.7 16.5 1979 181 10.931 26M 7633K
Uptime(secs): 70217.9 total, 70217.9 interval
Flush(GB): cumulative 866.720, interval 7.361
AddFile(GB): cumulative 0.000, interval 0.000
AddFile(Total Files): cumulative 0, interval 0
AddFile(L0 Files): cumulative 0, interval 0
AddFile(Keys): cumulative 0, interval 0
Cumulative compaction: 2500.08 GB write, 36.46 MB/s write, 2494.33 GB read, 36.38 MB/s read, 115810.5 seconds
Interval compaction: 31.96 GB write, 0.47 MB/s write, 32.17 GB read, 0.47 MB/s read, 1978.5 seconds
Stalls(count): 0 level0_slowdown, 0 level0_slowdown_with_compaction, 0 level0_numfiles, 0 level0_numfiles_with_compaction, 0 stop for pending_compaction_bytes, 0 slowdown for pending_compaction_bytes, 0 memtable_compaction, 0 memtable_slowdown, interval 0 total count
** DB Stats **
Uptime(secs): 70217.9 total, 600.1 interval
Cumulative writes: 305M writes, 13G keys, 303M commit groups, 1.0 writes per commit group, ingest: 2090.72 GB, 30.49 MB/s
Cumulative WAL: 305M writes, 0 syncs, 305710569.00 writes per sync, written: 2090.72 GB, 30.49 MB/s
Cumulative stall: 00:03:12.944 H:M:S, 0.3 percent
Interval writes: 3971K writes, 124M keys, 3943K commit groups, 1.0 writes per commit group, ingest: 18679.94 MB, 31.13 MB/s
Interval WAL: 3971K writes, 0 syncs, 3971919.00 writes per sync, written: 18.24 MB, 31.13 MB/s
Interval stall: 00:00:0.000 H:M:S, 0.0 percent
上面因为统计量比较大,我们只输出了 default 和 raft 两个 CF compaction 的统计,以及整个 DB 的统计。
Compaction stats
Compaction stats 就是在 level N 和 N + 1 层做 compaction 的统计,在 N + 1 层输出结果。
- Level:也就是 LSM 的 level,Sum 表示的是所有 level 的总和,而 Int 则表示从上一次 stats 到现在的数据。
- Files:有两个值
a/b
,a 用来表示当前 level 有多少文件,而 b 则用来表示当前用多少个线程正在对这层 level 做 compaction。 - Size(MB}:当前 level 总共多大,用 MB 表示。
- Score:除开 level 0,其他几层的 score 都是通过
(current level size) / (max level size)
来计算,通常 0 和 1 是正常的,但如果大于 1 了,表明这层 level 就需要做 compaction 了。对于 level 0,我们通过(current number of files) / (file number compaction trigger)
来计算。 - Read(GB}:对 level N 和 N + 1 做 compaction 的时候总的读取数据量
- Rn(GB}:对 level N 和 N + 1 做 compaction 的时候从 level N 读取的数据量
- Rnp1(GB}:对 level N 和 N + 1 做 compaction 的时候从 level N + 1读取的数据量
- Write(GB}:对 level N 和 N + 1 做 compaction 的时候总的写入数据量
- Wnew(GB}:新写入 level N + 1 的数据量,使用
(total bytes written to N+1) - (bytes read from N+1 during compaction with level N)
- Moved(GB}:直接移动到 level N + 1 的数据量。这里仅仅只会更新 manifest,并没有其他 IO 操作,表明之前在 level X 的文件现在在 level Y 了。
- W-Amp:从 level N 到 N + 1 的写放大,使用
(total bytes written to level N+1) / (total bytes read from level N)
计算。 - Rd(MB/s}:在 level N 到 N + 1 做 compaction 的时候读取速度。使用
Read(GB) * 1024 / duration
计算,duration 就是从 N 到 N + 1 compaction 的时间。 - Wr(MB/s}:在 level N 到 N + 1 做 compaction 的时候写入速度,类似
Rd(MB/s)
。 - Comp(sec}:在 level N 到 N + 1 做 compaction 的总的时间。
- Comp(cnt}:在 level N 到 N + 1 做 compaction 的总的次数。
- Avg(sec}:在 level N 到 N + 1 做 compaction 平均时间。
- KeyIn:在 level N 到 N + 1 做 compaction 的时候比较的 record 的数量
- KeyDrop:在 level N 到 N + 1 做 compaction 的时候直接丢弃的 record 的数量
具体相关的计算可以参考 RocksDB internal_stats.cc
里面的 PrintLevelStats
函数。
紧跟着 Compaction 的各个 Level 的统计,是这个 CF 这次 compaction 的一些汇总统计。Cumulative 是到现在为止累加的统计,Interval 则是从上一次统计到现在的变更数据。这里不做过多说明,重点关注的是
Stalls(count): 0 level0_slowdown, 0 level0_slowdown_with_compaction, 0 level0_numfiles, 0 level0_numfiles_with_compaction, 0 stop for pending_compaction_bytes, 0 slowdown for pending_compaction_bytes, 0 memtable_compaction, 0 memtable_slowdown, interval 0 total count
如果出现了 Stall,就表明外面写入的压力太大,RocksDB compaction 不及时,这样就会开始阻塞写入了。
DB Stats
除了不同 CF 的各个 level 的 compaction stats,我们也会得到一个总的汇总的统计,主要关注:
- Uptime:从启动到现在的时间
- writes:
- writes - 总的写入次数
- keys - 总的写入 key 的数量
- commit groups - 总的 group 提交的数量
- writes per commit group - 每个 group 提交包含的写入次数
- ingest - 直接 ingest 到 DB 的数量
- MB/s - 写入的速度
- WAL:
- writes - 总的写入次数
- syncs - 强制 sync 的次数
- writes - 每次 sync 有多少次写入
- written - 总的写入数据量
- MB/s - 写入的速度
- stall:
- H:M:S - 总的 stall 的时间
- percent - stall 的时间在总时间的比例
More statistics
除了上面的 statistics,RocksDB 还提供了一个更加细粒度的 statistics,我们需要手动 options.statistics = rocksdb::CreateDBStatistics()
打开,但它会影响性能。根据 RocksDB 官方的文档,打开这个 statistics 会损失 5-10% 的性能,但 TiKV 这边实测其实没到,所以我们默认是打开的。
通过 statistics,我们可以观察更多的统计信息,譬如每层 level 的读数据延迟:
** Level 0 read latency histogram (micros):
Count: 3834542 Average: 19.1113 StdDev: 143.36
Min: 0 Median: 13.6954 Max: 58656
Percentiles: P50: 13.70 P75: 16.70 P99: 42.01 P99.9: 838.63 P99.99: 6676.77
上面就是对于 level 0 的读取延迟 histogram 统计,看一看到读取的次数,瓶颈的时间,标准方差等。
然后还有 cache 的一些统计,譬如:
rocksdb.block.cache.miss COUNT : 349550104
rocksdb.block.cache.hit COUNT : 11172137213
上面表示 cache 总共 miss 了多少次,hit 了多少次。
当然还有 stall 的相关统计:
rocksdb.stall.micros COUNT : 192944077
rocksdb.db.write.stall statistics Percentiles :=> 50 : 0.825706 95 : 4683.057851 99 : 29772.380952
上面表示 stall 总的时间,以及 histogram。
在 TiKV 里面,我们通过 statistics 相关的接口,取出相关的数据,将其发送给 Prometheus,这样我们就能通过 Prometheus 来监控 RockSB 了。
小结
通常上面的 statistics,我们其实还不能确定某一个操作慢到底是怎么导致的。譬如我们 seek 某一个 key,但发现时间非常长,这时候我们就可以通过 Perf Context and IO Stats Context对一个操作进行分析,但实际对于一个运行的服务器程序,这种方式并不方便,所以在 TiKV 中我们并没有使用,但不排除以后也会研究,以及通过 tracing 接口显示的对一些操作打开。
在 TiKV 中,我们定时 10 分钟会将 Statistics 输出,同时也支持将 Statistics 及时的输出到 TiKV 自己的 LOG 里面供我们分析性能瓶颈。很多时候,我们不可能一次在不同的机器上面将 RocksDB 的参数配对,所以我们都是首先根据机器性能评估一个可用配置,然后在实际测试,通过 Statistics 以及系统其他的指标来动态调优的。