TiDB 内核解剖室SECTION 08

真停一台 TiKV 的 23 分钟 —— 15 秒检出、2 分钟判死、失效副本自动重建、恢复后回流,全程 2 秒级实况采样,写入零中断

1024 TiDB AIGC 黑客松 · 参赛作品 v0.2
store 1.112:20160
Up
6 peers
store 4.114:20160
Up
6 peers
store 5.110:20160
Up
3 peers
store 3153.110:20161
Up
6 peers
0 s10:39:21 → 11:02:35 · 窗长 23 分 14 秒
时刻—
store 4 状态—
store 4 副本—
副本合计—
正在取回采样数据……
这 23 分钟里,我们真停了一台在跑的 TiKV(192.168.3.114:20160)—— 不是暂停进程、不是模拟,是 systemctl stop 拉闸。 看四张店卡的点阵:store 4 的副本会被 PD 一个不剩地搬走(6 → 0),落到存活的三个店上;拉回来之后,再被自动回填、逐步均衡。 进度轨上的 5 个刻度就是这出戏的 5 个转折点。PD 对这件事的官方说法见 心跳丢失 → Disconnected → 超过 max-store-down-time 判 Down → 在存活 store 上补足副本 —— 本页把这句话拍成了 23 分钟的实况。回放是对 2 秒级采样序列的忠实重放,按 ×20 压缩,约 70 秒放完。
E3 · 写入探针实况 —— 全程 95 次 INSERT 的响应时间(每 10 秒一次,10:37:06 → 10:52:57)
10 20 40 60 ms → 峰值 49.6 ms · 10:52:16(再平衡启动) 故障期(注入 → 重新上线)· 11 分 11 秒 Down 判定 → 副本归位 · 8 分 48 秒 常态线 ≈ 10ms 10:37:06 10:42:08 10:47:06 10:52:06
纵轴是探针这条 INSERT 的往返耗时(毫秒)。两台店被搬副本、被重建副本的整个过程中,写入曲线始终贴着 10ms 的常态线 —— 唯一一次抬头是 49.6 ms,出现在 10:52:16 再平衡启动那一瞬。95 次里 0 次失败,没有一次超时重试。 逐点数据在 dev/probe/fr1-writes.jsonl,可下载复核。
E1 · 关键时刻表 —— 93 个关键帧压缩后的 14 行(⚙ = 控制器动作,其余为 2 秒级采样帧)
#时刻偏移store 4画面
110:39:52+31 sUp1:6 · 3153:6 · 4:6 · 5:3 —— 基线(7 region × 3 副本 = 21)
210:40:12+51 sUp⚙ systemctl stop tikv-20160@114 —— 真实断电注入
310:40:29+68 sDisconnectedUp → Disconnected(约 15 秒检出)· leader 归零
410:41:06+105 sDisconnected4: 7 → 6 —— PD 不等判死,先让副本让位
510:42:08+167 sDownDisconnected → Down(约 2 分钟 · max-store-down-time 已调 2m)
610:45:42+381 sDown4:5 —— 失效副本逐个迁移进行中(3153 承接 +1)
710:50:21+660 sDown4:4 —— 迁移收尾段(两个店合计 14 副本)
810:50:42+681 sDown4:2 —— 最后一批副本搬离
910:50:56+695 sDown4 清零 · 存活店承接 21/21 —— 副本归位完成(历时 8 分 48 秒)
1010:51:02+701 sDown⚙ systemctl start tikv-20160@114 —— 恢复注入
1110:51:23+722 sUpDown → Up(约 20 秒重新上线 · leader 归零)
1210:51:37+736 sUp4:1 —— 空店回填开始(恢复后 14 秒收到第 1 个副本)
1310:52:22+781 sUp4:6 · 3153:3 —— 批量回流:承接店让出、store 4 收回
1411:02:35+1394 sUp1:5 · 3153:5 · 4:6 · 5:7 —— 采样窗终点(均衡微调中)
「4:6」表示 store 4 当前持有 6 个副本。完整 93 个关键帧(含 44 条 operator 记录与 15 条控制器锚点)存于 dev/probe/fr1-timeline-keyframes.json,回放台就是对它的逐步重放;原始 2 秒级序列(586 个采样点)存于 dev/probe/fr1-timeline.jsonl 与 fr1-timeline2.jsonl。

E2失效副本自动归位 —— 6 → 0,存活店 21 / 21

21 / 21
判死后 8 分 48 秒,7 个 region 的三副本全部落在存活店上

PD 的补救不是等坏店回来,而是在存活店上补足缺失副本:store 4 的副本被逐个搬走(6 → 5 → 4 → 2 → 1 → 0),最后存活三店合计恰好 21 个副本位置。

  • 10:41:02{mv peer: store [4] to [5]} —— 故障后第一条搬离(与计数 4: 7 → 6 吻合)
  • 10:50:56store 4 清零 · 控制器断言 alive = 21 == 21 —— 归位完成
  • 10:52:18{mv peer: store [3153] to [4]} —— 恢复后反向回流开始

诚实声明:主力迁移时段(10:41:04 → 10:51:49)的 /operators 接口连续 310 次采样为空;同一时段副本计数序列 6 → 5 → 4 → 2 → 1 → 0 直接见证了搬离。两条独立观测,各说各的,本页不下推断。

搬离的安全前提来自官方调度约束:每 region 3 副本、同 region 副本不在同一店;调度动作本身见 副本操作。

E3写入零中断 —— 95 次探针,0 次失败

0
95 次 INSERT 中的失败次数(10:37:06 → 10:52:57 · 每 10 秒一次)

探针在一条独立连接上循环 INSERT,故障注入、判死、副本重建、回流全程不间断:曲线始终贴着 10 ms 常态线,唯一一次抬头是再平衡启动那一瞬(49.6 ms)。

min
7.5 ms
p50
10.1 ms
p95
16.8 ms
max
49.6 ms

条形按 0 – 50 ms 标尺等比映射(50 ms 为满格)。

E4恢复回流 —— 20 秒重新上线,14 秒收到第一个副本

20 s → 14 s
start 到 Up(10:51:02 → 10:51:23)· Up 到第一副本(10:51:23 → 10:51:37)

拉回来的 store 4 是一张空店(0 副本)。PD 先小步回填验证健康,再批量回流:10:52:22 时 store 4 已拿回 6 个副本、承接店 3153 让出到 3 个;再平衡期间出现全程延迟尖峰 49.6 ms(10:52:16),随后复归 10 ms 稳态。

11:02:35 采样窗终点时分布仍在微调(1:5 · 3153:5 · 4:6 · 5:7)—— 均衡是渐进的,不是开关。

E5控制器断言与终态独立校验

实验由控制器脚本按断言推进,每一步都回读核对:

  • 10:39:47B1 配置 max-store-down-time 30m → 2m · 回读 2m0s
  • 10:40:28store4 -> Disconnected (15s): True
  • 10:42:09store4 -> Down (max-store-down-time=2m) (100s): True
  • 10:47:10WAIT-TIMEOUT: add-peer appears after 300s —— 等搬离 operator 文本 300 秒未现(见 E2 声明)
  • 10:51:02replica repaired (233s) · alive = 21 == 21
  • 10:51:24store4 -> Up (20s): True · B8 回滚 30m · 回读 30m0s

实验后独立全量核查(11:04:53):

四店全部 Up(1 / 4 / 5 / 3153) 副本分布3153:6 副本 · 1:6 · 4:5 · 5:4(合计 21) Region 检查miss / down / offline / pending / learner = 0 / 0 / 0 / 0 / 0 SQL 校验lab.demo = 102 行(7 基线 + 95 探针) PD 成员三节点 health = true
为什么这页值得看:这是一次「把坏消息摊开」的实验 —— 我们真停了一台在服务的 TiKV,把 PD 从 15 秒检出、2 分钟判死、到 8 分 48 秒内自动补全副本的全过程拍了下来;拉回来之后,空店回填、批量回流、再平衡也都原样呈现。下面是三条边界:
  • 边界一 · 拓扑:3 节点实验集群(1 / 4 / 5 各持一个 TiKV,3153 为临时扩容的第 4 店)。生产环境节点更多,迁移的发起时机与速率会随拓扑与负载变化 —— 请把数字读作机制证据,不是容量基准。
  • 边界二 · 时间参数:为压缩观测窗口,max-store-down-time 从默认 30 分钟临时调为 2 分钟、实验后已回滚(回读 30m0s)。「判死约 2 分钟」是本次实验设置,不是默认行为;15 秒检出与 20 秒上线与参数无关。
  • 边界三 · 采样与注入方式:2 秒级轮询存在盲区,更短的瞬态可能落在两次采样之间;/operators 在主力迁移期连续 310 次为空(见 E2 诚实声明)。故障注入为 systemctl stop(进程级急停),不是物理断电或拔盘。
本轮实验环境
版本TiDB v8.5.7(三节点 + 第 4 店) 集群192.168.3.110 / .112 / .114 故障对象store 4 · 192.168.3.114:20160 第 4 店store 3153 · 192.168.3.110:20161(临时扩容) 故障方式systemctl stop tikv-20160@114 down-time30m → 2m → 30m(已回滚)
采集方法(可复跑)
采样器dev/fr1-sampler.py · 2 秒轮询 PD API 写入探针dev/fr1-writer.py · 每 10 秒 INSERT 控制器dev/fr1-control.py · 断言推进 产物dev/probe/fr1-timeline-keyframes.json(93 帧)· fr1-timeline.jsonl + fr1-timeline2.jsonl(586 点)· fr1-writes.jsonl(95 点)· fr1-control-out.txt
诚实边界
① 2 秒采样有盲区;主力迁移期 /operators 连续 310 次为空,两路观测各自成立(见 E2)。 ② max-store-down-time 实验期临时 2m、已回滚 30m;判死约 2 分钟不是默认值。 ③ systemctl stop 为进程级急停;3 节点 + 第 4 店为实验拓扑,非生产规模。
怎么核验这页
带虚线下划线的词都可以点开 —— 每张取证卡给出对应官方口径与引用。 回放数据源 = fr1-timeline-keyframes.json,与页内时刻表同源;原始 JSONL 在 dev/probe/ 下逐条可对。