SQL Vitess VTGate 的 query timeout 与 scatter query 限流实践

3次阅读

vtgate的query_timeout_ms仅控制单条查询从发出到收全响应的总耗时,超时即返deadline exceeded,但vttablet查询不自动取消;需启用enable_cancel_on_timeout=true才中断后端执行。

SQL Vitess VTGate 的 query timeout 与 scatter query 限流实践

VTGate 的 query_timeout_ms 怎么生效?

它只管单条 query 从 VTGate 发出到收到完整响应的总耗时,不区分是单分片还是 scatter query。一旦超时,VTGate 直接返回 deadline exceeded 错误,底层 vttablet 还在跑的查询不会被自动 cancel——除非你开了 enable_cancel_on_timeout(默认关)。

常见错误现象:query_timeout_ms=3000,但某次 scatter query 实际跑了 4.2s 才报错,说明超时判定发生在 VTGate 收包阶段,不是发包后立刻倒计时。

  • 必须配合 enable_cancel_on_timeout=true 才能真正中断后端 vttablet 上的执行
  • 该参数需在 VTGate 启动时通过 --enable-cancel-on-timeout 设置,运行时无法热更
  • 开启后对高并发 scatter 场景有轻微性能开销,因为要维护 cancel channel 和心跳检测

scatter query 为什么容易触发 timeout?

不是因为单个分片慢,而是所有分片响应时间的「最大值」决定整体耗时。比如 16 个分片,15 个 80ms 返回,1 个卡在 2.1s,那整个 query 就是 2.1s —— 如果你设了 query_timeout_ms=2000,就稳稳超时。

典型使用场景:按 user_id 分片的订单表,查某个用户最近 10 条订单,结果路由到单分片;但查“所有用户昨日订单总数”,就会 scatter 到全部分片。

  • scatter query 的实际耗时 ≈ max(各分片执行时间 + 网络 RTT)
  • VTGate 不做分片级 timeout,也不会跳过慢分片重试(不像某些 proxy 支持 partial result)
  • 如果底层 vttablet 有慢查询未加索引,scatter 会把问题放大 N 倍

怎么给 scatter query 加限流,而不是只靠 timeout?

query_timeout_ms 是被动防御,真要控压得用 max_memory_rowsconcurrent_batch_size 这类主动限流参数,它们作用于 VTGate 内部的 scatter 执行器。

参数差异很关键:max_memory_rows 控制单次 scatter 中 VTGate 内存里最多暂存多少行结果(防 OOM),concurrent_batch_size 控制同一时刻最多并发打多少个分片(防后端雪崩)。

  • --max-memory-rows=100000:超过则 VTGate 主动中止 scatter,返回 memory limit exceeded
  • --concurrent-batch-size=8:16 分片的 scatter 会分两轮,每轮 8 个并发,降低 vttablet 瞬时压力
  • 这两个参数必须设在 VTGate 启动命令里,配置中心或运行时改无效

timeout 和限流参数一起配,容易漏掉什么?

最常被忽略的是 vttablet 自身的 query-timeout。VTGate 超时了,但 vttablet 还在跑着那个慢查询,可能占满连接、拖垮其他请求。

所以必须两端对齐:VTGate 的 query_timeout_ms 应略小于 vttablet 的 --query-timeout=3s(比如设 2800),否则会出现 VTGate 已返回错误,但 vttablet 仍继续执行 200ms 的“幽灵查询”。

另一个坑是监控盲区:vitess 的 VTGateQueryCount 指标只计数成功 query,deadline exceeded 错误默认不计入,得额外看 VTGateErrors 并过滤 error_type="timeout"

text=ZqhQzanResources