本文主要介绍 TiKV 线程池性能调优的主要手段,以及 TiKV 内部线程池的主要用途。
在 TiKV 中,线程池主要由 gRPC、Scheduler、UnifyReadPool、Raftstore、StoreWriter、Apply、RocksDB 以及其它一些占用 CPU 不多的定时任务与检测组件组成,这里主要介绍几个占用 CPU 比较多且会对用户读写请求的性能产生影响的线程池。
0
,Raftstore 线程将日志写入到磁盘;如果该值不为 0
,Raftstore 线程将日志发送给 StoreWriter 线程处理。TiKV 的读取请求分为两类:
从 TiKV 5.0 版本起,默认所有的读取请求都通过统一的线程池进行查询。如果是从 TiKV 4.0 升级上来的 TiKV 集群且升级前未打开 readpool.storage
的 use-unified-pool
配置,则升级后所有的读取请求仍然继续使用独立的线程池进行查询,可以将 readpool.storage.use-unified-pool
设置为 true
使所有的读取请求通过统一的线程池进行查询。
gRPC 线程池的大小默认配置 (server.grpc-concurrency
) 是 5。由于 gRPC 线程池几乎不会有多少计算开销,它主要负责网络 IO、反序列化请求,因此该配置通常不需要调整。
server.grpc-concurrency
) 设置为 2。server.grpc-concurrency
以控制该线程池使用率在 80% 以下(即 Grafana 上的指标低于 80% * server.grpc-concurrency
的值)。Scheduler 线程池的大小配置 (storage.scheduler-worker-pool-size
) 在 TiKV 检测到机器 CPU 核数大于等于 16 时默认为 8,小于 16 时默认为 4。它主要用于将复杂的事务请求转化为简单的 key-value 读写。但是 scheduler 线程池本身不进行任何写操作。
如果检测到有事务冲突,那么它会提前返回冲突结果给客户端。
如果未检测到事务冲突,那么它会把需要写入的 key-value 合并成一条 Raft 日志交给 Raftstore 线程进行 Raft 日志复制。
通常来说为了避免过多的线程切换,最好确保 scheduler 线程池的利用率保持在 50%~75% 之间。(如果线程池大小为 8 的话,那么 Grafana 上的 TiKV-Details.Thread CPU.scheduler worker CPU 应当在 400%~600% 之间较为合理)
Raftstore 线程池是 TiKV 中最复杂的一个线程池,默认大小 (由 raftstore.store-pool-size
控制) 为 2。StoreWriter 线程池的默认大小 (由 raftstore.store-io-pool-size
控制) 为 0。
当 StoreWriter 线程池大小为 0 时,所有的写请求都会由 Raftstore 线程以 fsync 的方式写入 RocksDB。此时建议采取如下调优操作:
当 StoreWriter 线程池大小不为 0 时,所有写请求都由 StoreWriter 线程以 fsync 的方式写入 RocksDB。此时建议采取如下调优操作:
仅在整体 CPU 资源比较充裕的情况下启用 StoreWriter 线程池,并将 StoreWriter 线程和 Raftstore 线程的 CPU 使用率控制在 80% 以下。
与写请求在 Raftstore 线程完成的情况相比,理论上 StoreWriter 线程处理写请求能够显著地降低写延迟和读的尾延迟。然而,写入速度变得更快意味着 Raft 日志也变得更多,从而导致 Raftstore 线程、Apply 线程和 gRPC 线程的 CPU 开销增多。在这种情况下,CPU 资源不足可能会抵消优化效果,反而还可能比原来的写速度更慢,因此若是 CPU 资源不充裕则不建议开启 StoreWriter 线程。由于 Raftstore 线程把绝大部分的 I/O 请求交给 StoreWriter,因此 Raftstore 线程的 CPU 使用率控制在 80% 以下即可。
大多数情况下将 StoreWriter 线程池的大小设为 1 或 2 即可。这是因为 StoreWriter 线程池的大小会影响 Raft 日志数量,所以该值不宜过大。如果 CPU 使用率高于 80%,可以考虑再增加其大小。
注意 Raft 日志增多对其他线程池 CPU 开销的影响,必要的时候需要相应地增加 Raftstore 线程、Apply 线程和 gRPC 线程的数量。
UnifyReadPool 负责处理所有的读取请求。默认配置 (readpool.unified.max-thread-count
) 大小为机器 CPU 数的 80% (如机器为 16 核,则默认线程池大小为 12)。
通常建议根据业务负载特性调整其 CPU 使用率在线程池大小的 60%~90% 之间(如果用户 Grafana 上 TiKV-Details.Thread CPU.Unified read pool CPU 的峰值不超过 800%,那么建议用户将 readpool.unified.max-thread-count
设置为 10,过多的线程数会造成更频繁的线程切换,并且抢占其他线程池的资源)。
RocksDB 线程池是 RocksDB 进行 Compact 和 Flush 任务的线程池,通常不需要配置。
如果机器 CPU 核数较少,可将 rocksdb.max-background-jobs
与 raftdb.max-background-jobs
同时设置为 4。
如果遇到了 Write Stall,可查看 Grafana 监控上 RocksDB-kv 中的 Write Stall Reason 有哪些指标不为 0。
如果是由 pending compaction bytes 相关原因引起的,可将 rocksdb.max-sub-compactions
设置为 2 或者 3(该配置表示单次 compaction job 允许使用的子线程数量,TiKV 4.0 版本默认值为 3,3.0 版本默认值为 1)。
如果原因是 memtable count 相关,建议调大所有列的 max-write-buffer-number
(默认为 5)。
如果原因是 level0 file limit 相关,建议调大如下参数为 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