读性能慢-TiKV Server 读流程详解

TiDB 通过 gRPC 发送读取请求

读相关的请求主要包含 kv_get / kv_batch_get / coprocessor 三个接口,可以通过以下监控查看请求的数量、失败数量、延迟等情况:

  • TiKV-Detail -> gRPC -> gRPC message count:每种 gRPC 消息的个数
  • TiKV-Detail -> gRPC -> gRPC message failed:失败的 gRPC 消息的个数
  • TiKV-Detail -> gRPC -> gRPC message duration:gRPC 消息的执行时间
  • TiKV-Detail -> thread CPU -> gRPC poll CPU:gRPC 线程的 CPU 使用率,gRPC 线程是一个纯 CPU 的线程,不涉及 IO 操作,如果使用率过高,可以考虑调整 gRPC 线程数量,不然客户端请求会因为处理不及时导致延迟加大,此时 gRPC 会成为瓶颈

以上监控能对整个过程进行一个概览性的分析,如果其中某一个监控指标出现了异常,就需要进一步分析,kv_get / kv_batch_get / coprocessor 以上这三个接口的流量,他们分别在两个线程池执行,分别为:

  • Storage read threads pool (kv_get / kv_batch_get 接口)
  • Coprocessor threads pool (coprocessor 接口)。

Snapshot 模块

无论是走 Storage read pool 还是 Coprocessor pool 两个线程池,在处理请求之前都需要先构建一个 Snapshot,我们后续的读都是基于这个 Snapshot 的,这也是为什么 TiDB 的隔离级别叫 Snapshot 隔离级别的一个原因。

通常获取 Snapshot 很快,但会有一些场景影响 Snapshot 获取时间:

  • 当 TiKV 写压力非常大时,这是由于目前没有单独获取 snapshot 的线程,可能会导致获取 Snapshot 时间延长(此处可以参考 TIKV 写流程)。

  • 当读请求走的是 index read 时,因网络不好也会影响获取 snapshot 的时间(这是因为 TIDB 的分布式存储方式,数据与索引可能并没有存储在一个 TiKV 实例中)

  • 没有使用到 Local Read

    • 扩展知识点 Local Read 。简单讲为了保证数据的读一致性,我们的读都是从 Region Leader 上进行读取的,如果读请求发送到了 TiKV 节点正好是此 Region 的 Leader 节点,那么我们就是用 local read,减少额外的开销
  • Snapshot 的监控

    • TiKV-Detail -> Storage -> storage async snapshot duration 查看,如果该值较高,需要判断 raft store 是否繁忙,并查看是不是很多查询,没有走 local read,并且查看当前的网路状况是否良好。
    • local read 的监控可以在 TiKV-Detail -> Local read 查看,里面记录各种 local read 的数量。

RockDB-KV 模块

我们所有的数据都是以 KV 形势存储在 RockDB 中的,我们把这个 RockDB 称为 RockDB-KV。

可能有用户发现了 还有一个模块叫 RockDB-Raft ,ta 是用户同步 Raft 日志的,为了保证数据的一致性,所以也会将 RaftLog 进行持久化

MEM/Block Cache 命中率

查找的数据需要尽可能在内存中查找,所以查看对应监控信息,来分析读取 KV 键值对的过程中,不同类型的操作所占比例是否合理(下面的监控指标在 TiKV-Detail -> RocksDB-kv 下):

  1. Block Cache/MEM hit,这一部分依赖 Memory,对应的监控信息有:

    • Block cache size:block cache 的大小。如果将 shared-block-cache 禁用,即为每个 CF 的 block cache 的大小
    • Block cache hit:block cache 的命中率
    • Block cache flow:不同 block cache 操作的流量
    • Block cache operations 不同 block cache 操作的个数
  2. 读取 SST Files,这部分主要依赖 IO,对应的监控信息为:

  3. 二分查找过程中会比较 Key 的大小,这部分依赖 CPU,对应的监控有:

以上任何一个环节出现瓶颈,都可能导致读取 KV 键值对的延迟比较高,比如 Block cache size 非常小,命中率很低,那么每次都需要通过 SST File 读取数据必然延迟会加高,如果更糟糕的情况发生,比如 IO 打满或 CPU 打满,延迟会进一步加大。

  • 如果 Block cache 命中率小,可以通过 Block cache flow 和 Block cache operations 进一步定位 Block cache 上面发生的事件。
  • SST 文件由 Index Block(数据索引)、Filter Block(一般是 bloom filter 的数据) 和 Data Block 组成,因此在 Block Cache 相关监控中会出现 index, filter, data 相关的多个曲线。在 RocksDB 中,Block Cache 也会被分为多个 shard(由 num_shard_bits 参数控制,默认值为 6,也就是 64 个 shard,在 3.0 中已支持修改)。
  • 在 Block Cache 的大小已经比较大的情况下,如果还是观察到 Block Cache 命中率低,evict 数据量大(见 Block cache flow)
    • 首先可以观察是否有一个很大的 SST 文件,由于其 index block 和 filter block 很大导致其他 SST 的 cache 失效,这个 SST 文件通常是由于 Region 调度时生成 Snapshot 产生的,如果有则可以考虑手动触发 compaction 将其分散成小文件。
    • 如果没有大 SST,则考虑是由于请求的 Block Cache 都落在同一个 shard 里,在 3.0 中可以通过调小 num_shard_bits,或者通过进一步调高 Block Cache 大小,来解决 Cache 命中率低的问题。

storage read pool

storage read 主要是解决我们 查询中的 点查来单独设置的 线程池。及使用 主键与唯一索引进行等值查询,才会走 storage read pool 这个快速通道。通常情况下storage read pool 这里不应该看到很慢的场景

Storage read threads pool 可以通过以下监控来排查(下面的监控指标在 TiKV-Detail -> Storage 下):

  1. Storage command duration:执行 get 命令所需花费的时间(按照之前的说明,这部分时间就是调度之后,RocksDB->GetSnapshot() + Snapshot->Get(key) 的时间总和,所以不包含调度本身花费的时间)
  2. Storage command total:可以查看 get 命令的数量
  3. TiKV-Detail -> Thread CPU ->Storage ReadPool CPU:Readpool 线程的 CPU 使用率


比较套路的排查思路是先看一下 CPU 是不是被打满了,如果被打满了看是不是命令的数量比较多,如果 CPU 没有打满,但是 Storage command duration 延迟比较高,就按照获取 Snapshot 和从 Snapshot 中获取 KV 比较慢进行排查(上面已讲,这里不再复述)

coprocessor pool

Coprocessor pool 可以通过以下监控来排查(下面的监控指标在 TiKV-Detail -> Coprocessor Overview/detail 下):

  1. Wait duration:请求被调度 + 获取 Snapshot + 构建 Handler 的时间总和(监控中包含 max / .99 / .95 对应的延迟)
    a. 请求被调度这个延迟不涉及 IO 和网络,仅仅和 CPU 的负载有关,如果 CPU 的负载不高,调度的时间应该可以忽略不计
    b. 构建 Handler 的时间在 CPU 负载不高的情况也可以忽略不计
    c. Snapshot 获取时间如果过长,可以按照上面讲述的内容进行排查
    d. 综上:首先应该查看 CPU 的负载情况,如果 CPU 的负载高则说明瓶颈在 CPU,否则进一步排查 Snapshot 获取慢的原因

  2. Handle duration: 使用 Handler 处理这个请求的时间,Handle 过程包含:
    a. 算子的执行依赖 CPU,可以通过 CPU 的负载来定位瓶颈是否在 CPU
    b. 最底层的算子 TableScan / IndexScan 需要使用 Snapshot 的 Range-lookup,如果 CPU 负载不高,但是 Handle duration 延迟高,则需要查看上面提到的 Block Cache/MEM hit 的情况,另外,由于 Range-lookup 是范围扫描,如果 tombstone/MVCC 太多,也会影响查询时间,该指标可通过下面提到的 Perf Statistics 监控查看

  3. TiKV-Detail -> Thread CPU -> Coprocessor CPU:coprocessor 线程的 CPU 使用率

除了以上比较重要的监控指标,也可以通过以下监控辅助排查:

  • TiKV-Detail -> Coprocessor Overview -> Request errors:下推的请求发生错误的个数,正常情况下,短时间内不应该有大量的错误
  • TiKV-Detail -> Coprocessor Detail -> DAG executors:DAG executor 的个数,重点看一下 TableScan 算子的数量,排查是否有大查询扫表将 CPU 资源占用
  • Scan keys:每个请求 scan key 的个数,如果 Scan keys 的数量特别大,会占用大量的系统资源,导致请求的延迟变大
  • Scan details:scan 每个 CF 的详细情况
  • Table Scan - Details by CF:table scan 针对每个 CF 的详细情况
  • Index Scan - Details by CF:index scan 针对每个 CF 的详细情况
  • Table Scan - Perf Statistics:执行 table sacn 的时候,根据 perf 统计的 RocksDB 内部 operation 的个数
  • Index Scan - Perf Statistics:执行 index sacn 的时候,根据 perf 统计的 RocksDB 内部 operation 的个数

其他问题:

一、读请求过程中出现错误

  • 可能出现的错误

    1. 请求是根据 tidb-server 中的 region cache 里的 region 信息进行发送的,而在实际中,部分 region 信息可能因为如下原因导致过期,主要包括:
      a. Region 发生了分裂
      b. Region 发生了合并
      c. Region 发生了调度
    2. 一个请求中的部分 Key 上可能有锁
    3. 其他错误
  • 错误是如何处理的?

    1. 如果是 Region 相关的错误,会先进行 Backoff,然后进行重试,比如:
      a. Region 信息过期会使用新的 Region 信息重试任务
      b. Region 的 leader 切换,会把请求发送给新的 Leader
      c. 如果部分 Key 上有锁,会进行 Resolve lock
    2. 如果有其他错误,则立即向上层返回错误,中断请求
  • 错误相关的监控有哪些?

    1. TiDB -> KV Errors
    2. KV Retry Duration:KV 重试请求的时间
    3. TiClient Region Error OPS:TiKV 返回 Region 相关错误信息的数量
    4. KV Backoff OPS:TiKV 返回错误信息的数量(事务冲突等)
    5. KV Backoff OPS:TiKV 返回错误信息的数量(事务冲突等)
    6. Lock Resolve OPS:事务冲突相关的数量
    7. Other Errors OPS:其他类型的错误数量,包括清锁和更新 SafePoint
    8. TiKV -> Errors
    9. Server is busy
    10. Coprocessor error
    11. gRPC message error
    12. leader drop
    13. leader missing
    14. TiKV -> gRPC -> gRPC message failed

二、MVCC/TombStone 数量太多

  • 导致版本过多的应用场景

    1. 业务使用了计数器
    2. gc时间比较长,且修改频繁
  • 版本过多导致的问题

    1. 需要更多的存储空间
    2. 同一个key的所有版本在底层存储上是相邻的,当scan一个范围的时候需要跳过很多的历史版本,同一个key的所有版本在底层存储上是相邻的,这些历史版本也会因为key的最近的版本被访问而从磁盘读取出来并缓存在block-cache中
  • 相关的监控有哪些

    1. TiKV-Detail -> Coprocessor Overview -> Total RocksDB Perf Statistics
    2. TiKV-Detail -> Coprocessor Detail -> Total Ops Details(Table Scan)
    3. TiKV-Detail -> Coprocessor Detail -> Total Ops Details(Index Scan)
    4. TiKV-Detail -> Coprocessor Detail -> Total Ops Details by CF (Table Scan)
    5. TiKV-Detail -> Coprocessor Detail -> Total Ops Details by CF (Index Scan)
  • 处理方式

    1. 调整 GC 的频率及时间
    2. 对 tikv 进行 compact 操作

三、读热点问题

  • 判断读热点依据

    1. 方式一:在监控面板 TiKV-Details -> Thread CPU 中查看
      a. 若开启了 Unified read pool,查看是否有某个 TiKV 的 Unified read pool CPU 明显高于其他 TiKV 节点
      b. 若未开启 Unified read pool,查看是否有某个 TiKV 的 Coprocessor CPU 明显高于其他 TiKV 节点
    2. 方式二:在监控面板 TiKV-Throuble-Shooting -> Hot Read 中查看
      a. 查看是否存在某个 TiKV 的指标明显高于其他 TiKV 节点
  • 若确定存在读热点,需定位到具体的热点表或索引

    1. 方式一:通过 tidb dashboard 热力图定位(推荐方式)
      a. 对于读热点,在热力图中一般表现为一条明亮的横线,通常是有大量访问的小表
      b. 将鼠标移动到亮色块上可以看到具体什么表或索引上有大流量
    2. 方式二:通过 TiDB API
      • 查询读流量最大的 Region ID
        • pd-ctl -u http://{pd}:2379 -d region topread [limit]
        • 根据 Region ID 定位到表或者索引 curl http://{TiDBIP}:10080/regions/{RegionId}
  • 读热点解决方法

    1. 根据监控面板 TiKV-Details -> RocksDB KV -> Block Cache hit 命中率来判断
      • 若 BlockCache 的命中率下降或者抖动,可能是存在全表扫描的 SQL、执行计划选择不正确的 SQL、存在大量 count(*) 操作的 SQL 等等,解决方法:
        • 优化具体的 SQL
    2. 若 BlockCache 命中率比较稳定,则可能是 Region 并发读取过高导致,在业务查询频繁的命中个别 Region 最终时会导致单个 TiKV 节点性能达到极限,几种解决方法供参考:
      a. 将小表改造成 hash 分区表,打散 Region 分布
      b. 若同时也存在写热点,可以使用 shard_row_id_bits 处理热点表,可以有效缓解读写热点问题
      c. 开启 Coprocessor Cache 功能,开启该功能后,将在 TiDB 实例侧缓存下推给 TiKV 计算的结果,对于小表读热点能起到比较好的效果
      d. 使用 Follower Read 功能,在一定程度上可以缓解读热点问题

四、Pending Task

TiKV 后台会不定时执行一些操作,比如 compaction、gc、split region 等,如果这些 任务执行时间很长,也会增加读请求的延迟

另外,尽管可能性很低,但如果执行读请求相关的任务发生了阻塞,读请求相应的时间会变长,所以我们也建议查看是否发生了 pending task,具体相关的监控,可以查看

  • tikv-detail -> task -> pending task

五、get、seek 慢

  • Get接口提供用户一种从DB中查询key对应value的方法
  • Seek()函数查找大于或等于指定key值的第一个键值对的位置

通常 get 是比 seek 操作更轻,如果 get/seek的耗时较长,可能是集群负载较高、磁盘性能出现问题,对应的耗时情况,可以通过下面指标查看:

  1. RocksDB - kv / raft
  2. Get operations:get 操作的个数
  3. Get duration:get 操作的耗时
  4. Seek operations:seek 操作的个数
  5. Seek duration:seek 操作的耗时
  6. Write operatioxins:write 操作的个数
  7. Write duration:write 操作的耗时
  8. WAL sync operations:sync WAL 操作的个数
  9. WAL sync duration:sync WAL 操作的耗时
  10. Compaction operations:compaction 和 flush 操作的个数
  11. Compaction duration:compaction 和 flush 操作的耗时
  12. SST read duration:读取 SST 所需的时间

六、grpc 超过网卡处理能力

如果 tidb-server的网卡是千兆的,可能会由于执行计划不对,扫描的数据量太多,导致 tikv 把 tidb-server 的网卡打满,影响查询速度。

  • 相关监控:
    • Overview -> System Info -> Network traffic

七、服务器资源

如果服务器资源使用显示不高,但又发现集群处于堵塞状态,需要查看是否开启了资源使用相关控制,另外 tidb 在使用中,不建议开启 swap,如果开启了,需要进行如下操作:

  1. 集群是否开启了 resource control,可在集群参数文件中查看
  2. 系统上是否采用了 numactl 对内存或 CPU 进行了资源控制