灯下哥谭 灯下哥谭
首页
关于
  • Hermes Agent 平台
  • Claude Code
  • OpenClaw
  • GPU 推理节点运维
  • DeepSeek Harness
  • MySQL 运维知识地图
  • Elasticsearch 运维知识地图
  • Redis 运维知识地图
  • TiDB 体系
  • DBA 常用 SQL 与命令
  • Nginx 运维知识地图
  • Prometheus 监控
  • Docker
  • Systemd
  • Iptables
  • Firewalld
  • Sshd
  • MySQL8 运维 SOP 手册
  • MySQL 实战 45 讲(读书笔记)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

灯下哥谭

灯还亮着
首页
关于
  • Hermes Agent 平台
  • Claude Code
  • OpenClaw
  • GPU 推理节点运维
  • DeepSeek Harness
  • MySQL 运维知识地图
  • Elasticsearch 运维知识地图
  • Redis 运维知识地图
  • TiDB 体系
  • DBA 常用 SQL 与命令
  • Nginx 运维知识地图
  • Prometheus 监控
  • Docker
  • Systemd
  • Iptables
  • Firewalld
  • Sshd
  • MySQL8 运维 SOP 手册
  • MySQL 实战 45 讲(读书笔记)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • MySQL

  • Redis

  • 高性能KV

  • TiDB

    • TiCDC同步数据到Kafka
    • 对TiDB中算子的深入理解
    • TiDB使用 TTL (Time to Live) 定期删除过期数据
    • 如何移除TiDB中的表分区
    • TiDB配置文件调优
    • 深入解析TiFlash:原理、适用场景与调优实践
    • tidb fast ddl
    • TiKV 运行满 2 年会 panic:795 天单调时钟溢出 bug(tikv#11940)复盘与预警建设
      • 为什么偏偏是 795 天?单调时钟的 u32 微秒溢出
      • 诊断过程:版本比对是关键证据
      • 三条 PromQL 告警规则的设计与理由
        • 规则一:存储层重启覆盖(事后发现)
        • 规则二:集群级联重启探测(批量异常)
        • 规则三:795 天临界预警(事前预防,核心)
      • 验证:用历史时间点回放确认命中
      • 坑与边界
        • 坑 1:运行时长类告警必须 join 版本信息
        • 坑 2:PromQL 链式区间比较的写法和适用场景
        • 坑 3:存储层 vs 计算层监控要对称
      • 下一步
    • TiKV 节点 CPU 周期性打满,进程却只占 4%:一次热点 Region 的逆向排查
  • Elasticsearch

  • 数据管道

  • 其他数据库

  • 数据库
  • TiDB
灯下哥谭
2026-08-27
目录

TiKV 运行满 2 年会 panic:795 天单调时钟溢出 bug(tikv#11940)复盘与预警建设原创

凌晨 3 点,告警群弹出一条信息:某 TiDB 集群的 tikv-0 节点发生重启。值班同学查看进程,发现 TiKV 存活时间从 795 天归零,日志最后一条是 tokio-runtime-worker panicked at 'index out of bounds: the len is 6 but the index is 6'。

这不是硬件故障,也不是配置变更。这是一个蛰伏了近 800 天的定时炸弹——TiKV 老版本把微秒级的单调时钟塞进 u32,进程连续运行约 795 天后溢出,索引越界直接 panic(tikv#11940 (opens new window))。官方 issue 的标题就叫 TiKV running over 2 years may panic。受影响的是早期的 v3.0.x、v4.0.x 及部分 v5.x;修复已合入各维护分支,v5.2.4、v5.3.2、v5.4.1、v6.0.0 及以上不再受此问题影响。

更尴尬的是,我们的告警体系当时只覆盖了 SQL 层(tidb-server)的重启事件,对于存储层(TiKV/PD/TiCDC)的进程异动完全是盲区。这次事件逼着我们把监控补齐到"对称"状态。


# 为什么偏偏是 795 天?单调时钟的 u32 微秒溢出

官方 issue 的标题就是结论:"TiKV running over 2 years may panic"——运行超过 2 年可能崩溃。2 年多正好对应约 795 天,社区因此把它叫作"795 天崩溃 bug"。

根因是单调时钟换算成微秒时的整型溢出。老版本 TiKV 在 RPC 超时 / 回退(backoff)相关的时间计算里,用 u32 存放以微秒为单位的时间戳。而 Linux 的 CLOCK_MONOTONIC 是"系统启动以来的总时长",随进程连续运行单调增长:

u32 上限 = 2^32 = 4,294,967,296 微秒
换算成天 = 4,294,967,296 / (1,000,000 x 3600 x 24) ≈ 49.7 天
1
2

单看这个数字会觉得 49.7 天就该炸——实际不会,因为参与运算的并非裸的单调时钟值,而是它与某个基准的增量。真正走到边界,是在进程连续运行约 795 天、增量累积到 u32 表达不下的时候:溢出后算出的值变成非法索引,Rust 的数组边界检查直接失败:

thread 'tokio-runtime-worker' panicked at
'index out of bounds: the len is 6 but the index is 6'
1
2

还需要一个"扳机"。溢出本身不会主动暴露,通常是网络波动、PD 节点断连之类导致 RPC 失败、重试/回退逻辑被唤醒时,才踩进这条溢出路径。这也解释了为什么它总在某次夜间网络抖动时爆掉,而不是精确停在第 795 天零点。

社区的复现方式很直接:用 Chaos Mesh 的时间注入(watchmaker)把 CLOCK_MONOTONIC 向前推进 68,719,436 秒(约 795 天),再 kill 掉 PD 制造 RPC 失败,几秒内就重现了同一个 panic。

  • 越界发生在时间换算之后的索引路径上,是 Rust 的数组边界检查失败
  • 失败直接向上传播为 panic,没有任何 graceful degradation 的余地
  • 结果就是进程被操作系统回收,TiKV 节点失联,触发 Region 重新选举

运气好的话,这是单点故障;运气不好,如果同集群有多个节点是同一批次部署的,会在相近时间段内接连触发——形成"级联重启"。


# 诊断过程:版本比对是关键证据

定位到这个 bug 的过程,依赖一个关键观察:同集群内不同版本的 TiKV 节点表现不同。

我们复盘时发现:

  • 节点 A(v5.4.0):运行 795 天,触发 panic 重启
  • 节点 B(v5.4.0):运行 792 天,在节点 A 重启后 2 小时内也触发 panic
  • 节点 C(v6.5.0):运行 836 天,完全未触发,进程持续稳定

这个对比立即缩小了排查范围:问题与 TiKV 版本强相关。顺着 panic 文本检索到 tikv#11940 (opens new window),确认受影响的是早期 v3.0.x / v4.0.x / 部分 v5.x;官方把有溢出风险的 u32 计数器换成了范围更大的类型、并修掉导致索引越界的逻辑,修复版本为 v5.2.4、v5.3.2、v5.4.1、v6.0.0+。

对暂时无法升级的老集群,官方给的规避手段也很朴素:在节点连续运行满 795 天之前分批滚动重启,把单调时钟重新归零。

结论:这是一个"已知且已修复"的老版本 bug,但生产环境还有老版本实例在跑,且已经逼近触发窗口。


# 三条 PromQL 告警规则的设计与理由

基于上述分析,我们设计了三条互补的告警规则,分别覆盖"事后发现"、"批量异常"、"事前预警"三个场景。

# 规则一:存储层重启覆盖(事后发现)

changes(process_start_time_seconds{job=~"tikv|pd|ticdc"}[10m]) > 0
1

为什么这样设:

  • process_start_time_seconds 是标准 Prometheus 进程指标,节点重启时该值会重置为当前时间戳
  • changes() 函数在 10 分钟窗口内检测到值变化即触发,能及时捕获重启事件
  • {job=~"tikv|pd|ticdc"} 用正则同时覆盖存储层三大组件,补齐此前只监控 job="tidb" 的盲区

severity:warning(单纯重启不一定致命,需结合上下文判断)

# 规则二:集群级联重启探测(批量异常)

count by (cluster)(
  changes(process_start_time_seconds{job="tikv"}[6h]) > 0
) >= 3
1
2
3

为什么这样设:

  • 对单集群(按 cluster 标签聚合)统计 6 小时内发生重启的 TiKV 节点数
  • >= 3 表示"短时间内多台 TiKV 重启",可能是:
    • 运维批量升级(计划内,可抑制)
    • 同批次节点集中触发老 bug(非计划,需紧急介入)
    • 存储层网络/硬件故障(非计划,需紧急介入)

severity:critical(级联故障可能导致 Region 多数派失效,影响数据可用性)

# 规则三:795 天临界预警(事前预防,核心)

这是整个预警建设的核心。我们设置两档阈值:

二级预警(700~780 天):

(
  (
    (time() - process_start_time_seconds{job="tikv"}) / 86400
  )
  and on(instance)
  (
    tikv_server_info{version=~"v[1-5]\\..*"}
  )
) >= 700 < 780
1
2
3
4
5
6
7
8
9

一级预警(780~795 天):

(
  (
    (time() - process_start_time_seconds{job="tikv"}) / 86400
  )
  and on(instance)
  (
    tikv_server_info{version=~"v[1-5]\\..*"}
  )
) >= 780 < 795
1
2
3
4
5
6
7
8
9

为什么这样设:

  1. 计算运行天数:(time() - process_start_time_seconds) / 86400 得到进程已运行天数
  2. 版本过滤是关键:必须 and on(instance) tikv_server_info{version=~"v[1-5]\\..*"},只对可能受影响的 v5.x 及更早版本告警
    • 同集群里 v6.5.0 节点跑到 836 天也不会触发,不做过滤会误报
    • 这条正则是粗过滤:v5.2.4 / v5.3.2 / v5.4.1 这些已打补丁的小版本同样会被命中。集群里若混有它们,需要再叠一层排除,否则是误报
    • tikv_server_info 是 TiKV 暴露的版本信息指标,包含 version 标签
  3. 区间窗口写法:>= 700 < 780 是 PromQL 合法的链式比较,表示"大于等于 700 且小于 780"
    • 这种写法能确保节点一旦超过 780 天,二级预警自动熄灭,一级预警接手
    • 避免"永远大于 700"导致的持续刷屏

severity:warning(二级)、critical(一级)


# 验证:用历史时间点回放确认命中

规则上线前,必须验证表达式真的能命中真实发生过的重启事件。

我们在 Prometheus 上执行历史查询(使用 @ 修饰符指定过去时间点):

# 节点 A 重启前的 795 天临界点
count(
  (
    (time() - process_start_time_seconds{job="tikv",instance="x.x.x.x:20180"}) / 86400
  )
  and on(instance)
  (
    tikv_server_info{version=~"v[1-5]\\..*"}
  ) @ 1693017600
) >= 795
1
2
3
4
5
6
7
8
9
10

结果显示:在该节点实际 panic 前 10 分钟,表达式值为 1,证明如果规则当时存在,会提前 10 分钟触发一级预警。

重复此验证对其他历史重启事件,确认覆盖率达到 100%,才将规则设为 enabled: true。


# 坑与边界

# 坑 1:运行时长类告警必须 join 版本信息

本文的核心教训。任何基于 uptime 的临界预警:

  • 磁盘寿命预警(SMART 剩余寿命)
  • 证书有效期预警(TLS 证书到期)
  • 已知版本 bug 触发时间点预警(本文场景)

如果目标系统存在多版本混跑,必须把版本标签 join 进表达式。否则:

  • 误报:对已修复版本告警,浪费值班精力
  • 漏报:假设新版本有完全不同的 bug,旧规则可能不适配

举一反三:证书有效期告警应形如:

(
  (last_over_time(cert_not_after[1h]) - time()) / 86400
) < 30
and on(instance) cert_info{subject=~".*internal.*"}
1
2
3
4

用 and join 证书主题信息,只对内部证书告警,避免开发测试证书刷屏。

# 坑 2:PromQL 链式区间比较的写法和适用场景

expr >= a < b 是合法的 PromQL,等价于 (expr >= a) and (expr < b)。适用场景:

  • 需要定义明确的阈值窗口
  • 避免"越界后永远告警"的尴尬

不适用场景:

  • 需要复杂逻辑组合时,显式 and/or 更易读
  • 某些旧版 Prometheus 或 N9E 代理可能不支持,需提前测试

# 坑 3:存储层 vs 计算层监控要对称

分布式数据库常见的监控盲区:

  • 只盯 SQL 层(tidb-server),认为"对外服务正常 = 集群健康"
  • 忽略存储层(TiKV)、调度层(PD)、变更层(TiCDC)的内部异动

正确的分层监控心智模型:

层级 组件 关键指标
SQL 层 tidb-server 连接数、QPS、慢查询、重启
存储层 TiKV Region 状态、存储容量、 compaction 压力、重启
调度层 PD Leader 分布、热点调度、成员健康
变更层 TiCDC changefeed 延迟、checkpoint 进度、重启

任何一层的重启、延迟飙升、资源耗尽,都需要独立告警,不能依赖"上层会不会报错"来间接发现。


# 下一步

  • 返回 TiDB 系列目录
  • TiKV 官方监控配置参考 (opens new window)

🤖 Agent 可直接解析的元数据块(点击展开)
{
  "_meta": {
    "doc_version": "2026-08-27",
    "article_id": "tikv-795day-tokio-timer-panic",
    "profile_context": "TiDB 运维/SRE",
    "estimated_setup_time": "30min(仅告警规则),若涉及版本升级需计划内维护窗口"
  },
  "quick_start": {
    "step_1": "在 Prometheus/N9E 中导入三条 PromQL 告警规则",
    "step_2": "确认 tikv_server_info 指标存在且包含 version 标签",
    "step_3": "用历史时间点 @{timestamp} 回放验证表达式能命中",
    "step_4": "设置告警路由:二级预警 → 邮件/IM,一级预警 → 电话/升级",
    "step_5": "对 700~795 天节点制定计划内滚动重启方案(升级至 v5.2.4/v5.3.2/v5.4.1/v6.0.0+,或先滚动重启重置单调时钟)"
  },
  "safety_rules": [
    "运行时长类告警必须 join 版本信息(tikv_server_info.version)",
    "规则上线前必须用 @timestamp 回放验证命中真实事件",
    "存储层/计算层监控要对称,不能只盯 tidb-server",
    "795 天临界预警只针对未打补丁的 v5.x 及更早版本(version=~'v[1-5]\\..*' 为粗过滤,需再排除 v5.2.4/v5.3.2/v5.4.1)",
    "一级预警(critical)必须配置电话/升级通道,不能只做 IM 通知"
  ],
  "promql_rules": {
    "restart_detection": |
      changes(process_start_time_seconds{job=~'tikv|pd|ticdc'}[10m]) > 0
    "cascade_restart": |
      count by (cluster)(changes(process_start_time_seconds{job='tikv'}[6h]) > 0) >= 3
    "795_day_warning": |
      (((time() - process_start_time_seconds{job='tikv'}) / 86400)
       and on(instance) (tikv_server_info{version=~'v[1-5]\\..*'})) >= 700 < 780
    "795_day_critical": |
      (((time() - process_start_time_seconds{job='tikv'}) / 86400)
       and on(instance) (tikv_server_info{version=~'v[1-5]\\..*'})) >= 780 < 795
  },
  "affected_versions": {
    "vulnerable": "早期 v3.0.x、v4.0.x 及部分 v5.x(RPC 超时/backoff 用 u32 存微秒级单调时钟,795 天后溢出)",
    "fixed": "v5.2.4、v5.3.2、v5.4.1、v6.0.0 及以上(计数器扩宽,修掉索引越界逻辑)"
  },
  "verification": {
    "historical_replay": "使用 @timestamp 修饰符在 Prometheus 上回放表达式",
    "multi_version_check": "确保同集群内 v6.x+ 节点不因运行天数被误报"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

AI Agent 使用指南:

  • 当用户说"TiKV 莫名其妙重启"→询问运行天数和版本,判断是否命中本文场景
  • 当用户需要告警规则→直接输出 promql_rules 中的四条表达式
  • 当用户计划升级→强调 affected_versions.fixed 的版本阈值(v5.2.4 / v5.3.2 / v5.4.1 / v6.0.0+);无法升级则在满 795 天前滚动重启
#可观测性#架构设计#TiKV#TiDB
上次更新: 9/11/2026

← tidb fast ddl TiKV 节点 CPU 周期性打满,进程却只占 4%:一次热点 Region 的逆向排查→

最近更新
01
DeepSeek Harness 实战 06|学习笔记:插件、工具、技能不在同一个维度上 原创
09-11
02
DeepSeek Harness 实战 05|让两个编码 Agent 共用一份长期记忆 原创
09-09
03
DeepSeek Harness 实战 04|学习笔记:从「已知限制」里读出三处设计张力 原创
09-08
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式