TiDB 写入慢流程排查系列(六)— GC 机制

TiKV 没有单独的 GC 机制,GC 的触发依赖于 TiDB gc-worker 运行。

TiKV 收到 TiDB 发送的的 GC 命令(包含 SafePoint)后扫描当前 Region 的所有 key,然后对每个 Key 执行 GC 操作,即 SafePoint 之前只保留最后一次提交的数据。缺点是删除也是写入,需等待 compaction 运行后数据才真正删除。另外 GC 过程要占用系统资源,对正常事务的运行产生影响。

TiKV 在 3.0.6 版本开始支持 GC 流控,可通过配置 gc.max-write-bytes-per-sec 限制 GC worker 每秒数据写入量,降低对正常请求的影响,0 为关闭该功能。该配置可通过 tikv-ctl 动态修改

tikv-ctl --host=ip:port modify-tikv-config -m server -n gc.max_write_bytes_per_sec -v 10MB

第一步:Resolve Locks(清理锁)

Resolve Locks 这一步的任务即对 safe point 之前的锁进行清理。即如果一个锁对应的 Primary 已经提交,那么该锁也应该被提交;反之,则应该回滚。而如果 Primary 仍然是上锁的状态(没有提交也没有回滚),则应当将该事务视为超时失败而回滚。

Resolve Locks 的执行方式是由 GC leader 对所有的 Region 发送请求扫描过期的锁,并对扫到的锁查询 Primary 的状态,再发送请求对其进行提交或回滚。这个过程默认会并行地执行,并发数量默认与 TiKV 节点个数相同。

实际遇到要清理的锁的话,是调用 RocksDB 的 delete 接口,写入一个删除标记。

判断 Resolve Locks 操作的数量可以通过 TiKV-Details → gRPC → gRPC message count 中的 kv_scan_lock 请求数量来判断,kv_scan_lock 请求只会由 GC 导致,resolve_lock 操作可以通过 gRPC message count 中的 kv_resolve_lock 数量判断,但是因为正常查询也会产生 kv_resolve_lock 操作,所以该指标并不完全等同于 GC 产生的 resolve lock 操作。

第二步:Delete Ranges(删除区间)

在执行 DROP TABLE/INDEX 等操作时,会有大量连续的数据被删除。如果对每个 key 都进行删除操作、再对每个 key 进行 GC 的话,那么执行效率和空间回收速度都可能非常的低下。事实上,这种时候 TiDB 并不会对每个 key 进行删除操作,而是将这些待删除的区间及删除操作的时间戳记录下来。Delete Ranges 会将这些时间戳在 safe point 之前的区间进行快速的物理删除。

判断 Delete Ranges 操作的数量可以通过 TiKV-Details → gRPC → gRPC message count 中的 kv_delete_range 和 unsafe_destroy_range 请求来判断。

第三步:Do GC(进行 GC 清理)

这一步即删除所有 key 的过期版本。为了保证 safe point 之后的任何时间戳都具有一致的快照,这一步删除 safe point 之前提交的数据,但是会对每个 key 保留 safe point 前的最后一次写入(除非最后一次写入是删除)。

在进行这一步时,TiDB 只需要将 safe point 发送给 PD,即可结束整轮 GC。TiKV 会自行检测到 safe point 发生了更新,会对当前节点上所有作为 Region leader 进行 GC。与此同时,GC leader 可以继续触发下一轮 GC。

Do GC 阶段,实际的操作是 TiKV 去扫描所有的 region ,如果发现 key 的 tso 信息早于 SafePoint 点了,那就调用 RocksDB 的 delete 接口,打一个删除标记。等待后续 Compaction 的时候回收空间。扫描所有 region 这一步有一点优化是,扫描的时候会根据 sst 上的元信息初步判断是否有较多的历史数据,进而来判断是否是可以跳过的。

GC 相关监控介绍

  • TiKV-Details → GC → MVCC versions

每个 key 的版本个数,如果 GC 保留时间设置的越长,这个数值可能会越大,查询的时候会需要扫描更多的 MVCC 版本数据后获取到实际数据,影响查询速度。

  • TiKV-Details → GC → MVCC delete versions

GC 删除掉的每个 key 的版本数量

  • TiKV-Details → GC → GC tasks

显示 TiKV 的 GC worker 执行的任务。这些任务分为 GC 任务和 UnsafeDestroyRange 任务。以 region 为粒度的。具体的指标有:

  • total-gc: GC 任务总量。
  • skipped-gc: 跳过的 GC 任务数量。TiKV 会根据记录在 RocksDB 中的一些元信息来推断一个 Region 是不是并没有太多需要 GC 的数据,如果是的话则跳过该 Region。
  • failed-gc: GC 任务失败的数量。在 leader 迁移、region 不可用之类的情况下会发生。如果这一指标非常低,则无需在意。
  • total-unsafe-destroy-range: tikv 实际执行的 UnsafeDestroyRange 任务数量。
  • failed-unsafe-destory-range: UnsafeDestroyRange 失败的数量。一般不会出现。
  • gcworker-too-busy: 由于 TiKV 的 GCWorker 队列已满(队列长度为 2),而拒绝掉的请求数量。默认配置下,0 不存在这种情况。但是如果配置成了使用 2.x 版本的旧的 GC 方式,或者对于 2.1.x 版本,当tikv_gc_concurrency 配置较高时,出现这一指标是正常的。
  • TiKV-Details → GC → GC tasks duration

记录的是各种任务耗时的统计。通常在秒级别、偶尔出现十几秒的 task 都是正常的。如果持续耗时几十秒甚至分钟级别就可能有问题。

  • TiKV-Details → GC → GC keys(write CF)

记录的是 GC 在 write cf 中进行的各种具体的读操作的统计。这个与应用操作导致的 get/next/seek 操作是区分开的。各种指标表示不同方式扫描 key ,数量是包含扫描的所有 key(包括新老 key)

  • TiKV-Details → GC → TiDB GC worker actions

TiDB GC Worker 的不同 action 的个数。只包括 Resolve Locks 和 Delete Ranges 两个阶段。具体的指标有:

  • delete_range,delete range 阶段活动次数,调用 unsafe_destroy_range 接口
  • redo_delete_range,删除的区间 24h 后再删除一次的操作,不用太关注
  • resolve_locks,清锁阶段,无论是否产生真实清锁都会有该 metric,scan_lock 是该阶段内部调用的 tikv 接口,所以不会在这显示
  • run_job,会在 GC 第一阶段最开始产生一个尖峰,可以用这个指标确认是否完成整轮 GC
  • TiKV-Details → GC → TiDB GC seconds

TiDB 执行 GC 花费的时间

  • TiKV-Details → GC → GC speed

GC 删除旧版本的执行速度,单位为 keys/s

  • TiKV-Details → GC → TiKV AutoGC Working

不用关注,看 TiKV Auto GC Progress 监控即可。

  • TiKV-Details → GC → ResolveLocks Progress

记录的是 TiDB 侧进行 Resolve Locks 的粗略进度,值为 Region 的数量。因为在 TiKV 面板不方便去 region 的总数,所以难以以百分比方式进行展示。

TiKV-Details → GC → TiKV Auto GC Progress

TiKV 自行进行分布式 GC 的阶段的粗略进度。分 TiKV 节点显示每台 TiKV 的进度,通过将已扫描的 Region 数量与该 TiKV 节点上 Region 总数相除的方式计算粗略的百分比。如果 GC 运行过快(比如在一个新建的几乎为空的集群上),该指标可能显示不出来。

注:如果在一轮 GC 运行过程中(如进行到 30% 时)TiKV 获取到了更新的 safepoint,下一轮 GC 则可能停止在上一轮更新 safepoint 时的进度(即下一次 GC 进度达到 30% 时停止)

  • TiKV-Details → GC → TiKV Auto GC SafePoint

每台 TiKV 当前自行 GC 时所使用的 SafePoint ,该 SafePoint 由每台 TiKV 定期从 PD 取得。

  • TiKV-Details → GC → GC lifetime

tikv_gc_life_time 的设置值

  • TiKV-Details → GC → GC interval

tikv_gc_run_interval 的设置值