设计与实现解析)
TiDB 可配置 KV 读取超时tikv_client_read_timeout设计与实现解析【免费下载链接】tidbTiDB is built for agentic workloads that grow unpredictably, with ACID guarantees and native support for transactions, analytics, and vector search. No data silos. No noisy neighbors. No infrastructure ceiling.项目地址: https://gitcode.com/GitHub_Trending/ti/tidb本文以 TiDB 仓库中的设计文档 2023-06-30-configurable-kv-timeout.md 为主体结合仓库内已有的实现代码梳理「可配置 KV 请求超时」这一能力的动机、会话变量与查询 Hint 的用法、Get/BatchGet/Coprocessor 三类请求的超时传递链路以及超时重试与任务取消的边界问题。读完本文你将掌握如何为点查等短请求设置毫秒级读取超时以对抗单存储节点抖动并理解该能力从提案到落地的完整工程脉络。背景固定超时为何扛不住节点抖动TiKV 上的请求通常在毫秒级内完成但当某个 TiKV 节点出现磁盘 IO如 EBS 正在修复副本或网络延迟抖动时请求耗时可能飙升至数秒甚至更长。设计文档指出改造前 TiKV 请求的超时上限是固定值、不可调整的例如ReadTimeoutShort约 30 秒用于 Get RPC 请求ReadTimeoutMedium约 60 秒用于 BatchGet、Cop RPC 请求。具体常量定义在 client-go 仓库的internal/client/client.go中按设计文档引用约为 30s / 60s。由于超时配置通常长达数十秒当某个 TiKV 节点抖动时kv-client 或 TiDB 只能一直等待响应直到超时无法快速将请求重试或重定向到其他节点最终表现为应用侧查询延迟尖峰。文档作者建议为“总能由多个候选目标处理”的请求例如 follower 读、stale read 这类可由多个可用 peer 服务的请求设置快速重试从而显著改善对单个存储节点抖动/IO 延迟的容忍度。改进提案会话变量 查询级 Hint针对 pingcap/tidb#44771 提出的诉求设计文档给出两条配置路径会话变量tikv_client_read_timeout控制单个 TiKV 读 RPC 请求的超时。一旦用户设置该变量本会话内所有读 RPC 请求的超时都将使用该值。默认值为0此时保持原有的ReadTimeoutShort/ReadTimeoutMedium语义不变。语句级 Hint通过set_var提示词为单条查询设置超时例如SELECT /* set_var(tikv_client_read_timeout500) */ * FROM t WHERE id ?;Hint 方式影响范围仅限于单条语句因此设计文档认为它比会话级变量“更灵活、更安全”。设计文档中的示例stale read 场景-- 开启 stale read读取 5 秒前的数据 set tidb_read_staleness-5; -- 会话变量用法单位为毫秒 set tidb_tikv_tidb_timeout500; select * from t where id 1; -- 查询 Hint 用法单位为毫秒 select /* set_var(tikv_client_read_timeout500) */ * FROM t where id 1;注意示例第二行中的tidb_tikv_tidb_timeout是设计文档早期草案中的笔误。结合仓库实际实现见下文TiDB 真正注册的系统变量名为tikv_client_read_timeout常量定义见 pkg/sessionctx/vardef/sysvar.go#L313-L314单位统一为毫秒。落地后的两种用法在 pkg/sessionctx/variable/sysvar.go#L2070-L2082 中可以看到该变量已注册为完整 SysVar作用域ScopeGlobal | ScopeSession既支持全局设置也支持会话内设置默认值00 表示不启用、沿用客户端默认超时类型与取值范围TypeUnsignedMinValue 0、MaxValue math.MaxInt32支持 Hint 更新IsHintUpdatableVerified: true这正是/* set_var(...) */提示词能被合法生效的前提会话写回SetSession内部经tidbOptPositiveInt32(val, 0)将字符串解析为非负整数毫秒并存入SessionVars.TiKVClientReadTimeout。因此实际可写为-- 会话级本会话所有读请求超时 500ms set tikv_client_read_timeout500; -- Hint 级仅当前这条语句超时 500ms select /* set_var(tikv_client_read_timeout500) */ * from t where id 1;潜在问题设计文档对该方案的风险做了前置分析实现与运维时必须警惕不同请求类型对超时的敏感度不同会话变量会影响会话内所有 TiKV 读请求Get、BatchGet、Cop 等。1 秒超时对 Get 也许足够但对大 Cop 请求可能偏小导致大量 Cop 请求持续超时、无法正常完成。超时设置过小会放大重试压力值设得越小重试次数越多TiDB 集群负载压力随之上升。最坏情况下若设置的值小到没有任何一个副本能在期限内完成查询将一直重试直至达到最大 backoff 超时上限才返回——这比不设置更糟。TiDB 侧对“Cop 类长任务”还做了额外工程防护见下文 Coprocessor 路径实现现状。详细设计超时值如何从 SQL 一路传到底层设计文档按请求类型分别给出了数据面改造链路核心思想是把用户配置的超时值附加到快照读取选项与 RPC 上下文上最终转化为 TiKV 侧可判断的 deadline。为 Get / BatchGet 增加超时配置在KVSnapshot结构中增加字段改动位于 client-go 仓库type KVSnapshot struct { ... KVReadTimeout Duration }提供 set/get 接口client-go 与 tidb 两侧都要实现func (s *KVSnapshot) SetKVReadTimeout(KVTimeout Duration) { s.KVReadTimeout KVTimeout }在构建PointGet/BatchPointGet执行器时把超时值从StmtContext取出并写入KVSnapshot。将超时值填入 RPCContext.max_execution_duration_ms字段message Context { ... uint64 max_execution_duration_ms 14; ... }该字段此前只被Prewrite、Commit等写请求使用本次扩展后读请求也可携带执行时长上限。TiKV 侧做超时检查收到新的 PointGet / BatchPointGet 请求后从GetRequest/BatchGetRequest中读出kv_read_timeout并传给pointGetter。由于目前 TiKV 将任务派发到 read pool 后缺少取消机制文档建议采用“简化方案”——在进入下一步处理前检查 deadline若已超时则直接返回但任务一旦被 spawn 到 read pool 就无法再被取消。文档中的 Rust 伪代码在spawn_handle的任务体内、取快照前后以及SnapshotStore.get附近都预留了 “TODO: Add timeout check here” 的检查位点。为 Coprocessor 任务增加超时配置在copTask结构中增加字段type copTask struct { ... KVReadTimeout Duration }同样将超时值写入Context.max_execution_duration_ms字段同上。在parse_and_handle_unary_request中用传入的kv_read_timeout计算 deadline构造ReqContext时替换默认的max_handle_durationreq_ctx ReqContext::new( tag, context, ranges, self.max_handle_duration, // Here use the specified timeout value. peer, Some(is_desc_scan), start_ts.into(), cache_match_version, self.perf_level, );仓库实现现状提案已落地设计文档本身是提案而当前 TiDB 仓库中该特性已经实现可以从以下几处交叉验证设计如何变成代码会话变量已注册见上文 pkg/sessionctx/variable/sysvar.go#L2070-L2082这是整条链路在 SQL 层的入口。KV 快照选项携带超时pkg/kv/kv.go#L757-L758 在快照读选项中定义了TiKVClientReadTimeout uint64与设计文档KVSnapshot.KVReadTimeout一一对应。快照组装处生效pkg/store/driver/txn/snapshot.go#L149-L151 在Snapshot的选项装配逻辑中执行s.KVSnapshot.SetKVReadTimeout(time.Duration(val.(uint64) * uint64(time.Millisecond)))即把会话变量携带的毫秒值转换为time.Duration注入快照。Coprocessor 任务的超时装载pkg/store/copr/coprocessor.go#L323 的copTask拥有字段tikvClientReadTimeout uint64在构造任务时pkg/store/copr/coprocessor.go#L720-L722当未显式忽略该配置时执行task.tikvClientReadTimeout req.TiKVClientReadTimeout。Coprocessor 的默认超时与“忽略”开关一个值得注意的实现细节在 pkg/store/copr/coprocessor.go#L1826-L1829发送 Cop 请求前超时值默认取自全局配置config.GetGlobalConfig().TiKVClient.CoprReqTimeout只有当task.tikvClientReadTimeout 0时才改用用户配置的毫秒级超时。也就是说Cop 请求的默认超时并不由本特性接管用户设置tikv_client_read_timeout后Cop 路径也只有在显式取值大于 0 时才会生效。对应地pkg/store/copr/coprocessor.go#L380-L381 定义了ignoreTiKVClientReadTimeout bool用于“忽略tikv_client_read_timeout配置、改用默认超时”。在 pkg/store/copr/coprocessor.go#L2242、L2300、L2333、L2438、L2536 等多处 copTask 构造点都设置了ignoreTiKVClientReadTimeout: true。从源码结构看这主要服务于next 方式迭代的 cop 请求如全表扫描分批取数以及部分内部任务——这类长生命周期请求若套用毫秒级短超时会频繁超时正好呼应设计文档“Cop 请求超时设小会不断超时”的担忧属于工程上的兜底护栏。超时后的重试策略设计文档给出了超时发生时面向“多个可读 peer”的重试策略若第一次请求立即超时则尝试下一个可用 peer若所有 peer 均返回timeout错误则将timeout错误返回给上层上层收到timeout错误后进行 backoff并使用原始默认的ReadTimeoutShort/ReadTimeoutMedium值重试之后按原有逻辑继续处理。这套策略的用意很清晰先用用户配置的激进短超时快速探测副本是否可用一旦整轮都超时则退回宽松的默认超时再试避免因用户值设得过小而永远无法成功。文档同时强调超时重试的明细需要被请求追踪器request tracker妥善记录并纳入对应指标统计。超时任务的取消现状与未竟之事设计文档坦诚指出即使超时值配置到几百毫秒的量级任务一旦被提交到 TiKV 的 read pool仍存在两个无法立即取消的原因read task 被 poll 并执行时任务调度器中目前没有超时检查机制读操作是同步的如果读任务被慢 IO 阻塞任务既不会被调度也无法被取消。虽然可以在“获取快照之后”等若干位置插入简单的超时检查但要实现完备的超时检查与调度设计还需要让 TiKV 的读处理异步化属于前置条件复杂、工程量大的方向性工作。兼容性带有可配置超时值的请求只会在较新版本上生效理论上集群降级到旧版本时这些新增字段不会起作用也不会破坏既有行为。备选方案从 max_execution_time 派生超时设计文档还比较了一个“少引入新配置”的替代思路——派生超时Derived Timeouts。TiDB 已有查询级超时变量max_execution_time同样支持变量与 Hint 两种设置方式。若能让 RPC/TiKV 请求超时从max_execution_time智能推导就无需再暴露tikv_client_read_timeout例如查询级max_execution_time 1s时首次 RPC 超时按比例设为500ms首次 RPC 超时后的重试 RPC 使用剩余额度如400ms。这样从应用视角看TiDB 会在给定的max_execution_time内尽快完成请求无法按时完成则快速失败并返回超时错误。它的优点是不引入额外配置、心智负担小难点在于“超时推算策略”很难做到智能或自动——不同请求类型、节点压力、延迟特征都会影响合理值需要大量实验才能找到合适策略。最终文档倾向采用独立的可配置超时方案因为查询 Hint 粒度既灵活又安全。小结从 docs/design/2023-06-30-configurable-kv-timeout.md 这一提案出发TiDB 已完成从设计到落地的闭环SQL 层注册了tikv_client_read_timeout全局/会话变量并支持set_var查询 Hintpkg/sessionctx/variable/sysvar.go#L2070-L2082单位为毫秒默认 0 表示不启用快照层超时值经 pkg/kv/kv.go#L757-L758 的选项与SetKVReadTimeout注入 KVSnapshotpkg/store/driver/txn/snapshot.go#L149-L151随 Get / BatchGet 一并生效Coprocessor 层copTask携带超时字段并覆盖默认的CoprReqTimeout同时通过ignoreTiKVClientReadTimeout为长生命周期 Cop 请求保留默认超时pkg/store/copr/coprocessor.go#L1826-L1829 等。对延迟敏感的联机场景点查、stale read、follower 读将tikv_client_read_timeout设置为数百毫秒可以显著缩短节点抖动下的感知延迟——前提是结合本文提到的重试放大与 Cop 请求适配风险做充分的容量与压测评估。【免费下载链接】tidbTiDB is built for agentic workloads that grow unpredictably, with ACID guarantees and native support for transactions, analytics, and vector search. No data silos. No noisy neighbors. No infrastructure ceiling.项目地址: https://gitcode.com/GitHub_Trending/ti/tidb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考