Skip to content

decision request: pre-first-byte upstream stall — fail-fast or diagnostics-only, and default threshold (#5164) #5267

Closed

Description

@ranxianglei

🤖 Powered by ework · qwen3.8-27b

来源: #5164「上游首字节前卡住时静默持有客户端最长 12 分钟并燃烧宿主重试」——本单是该工作单的决策载体(修复方向 A/B/C + 默认阈值),不是新问题报告。无决策依赖的诊断部分(选项 B 内容)已先行开止血 PR(链接见 #5164 线程);若最终选 C,请拒掉该 PR。

查重: 无。按「decision request / first byte / stall timeout / watchdog」检索 open+closed,无同主题条目;#5164 本身是工作单。

一句话背景

上游在返回第一个响应字节之前卡住时,bili 现在会静默持有客户端连接最长 12 分钟(唯一硬杀是 UPSTREAM_TIMEOUT_MS 的 idle abort),30s first-byte 看门狗只 warn 且每 origin 每进程终身一次、之后永久沉默;宿主(dsh 实测 ~300s)先超时重试,每次重付全额 input tokens。机制与证据已在 #5164 钉死(对 master 逐条核实过)。

选项

A fail-fast + 可配置阈值:first-byte 看门狗到阈值即 abort 本次 upstream 请求并向客户端回 502 retryable=true(带 stage 说明),让宿主有意地、更早地重试,而不是静默持有到自己超时。阈值走 knob 纪律新增 network.firstByteTimeoutMs / BILI_FIRST_BYTE_TIMEOUT_MS(env > file > default),CONFIGURATION.md en/zh 双镜像;0 = 禁用(保持现 warn-only 姿态)。

B 只补诊断不改语义:去掉 per-origin 终身去重,改为每 (origin, session) 冷却窗口 warn(每个受影响 turn 各有日志行、跨 turn 不互相抑制、防刷屏保留)。客户端行为不变(仍被持有至 12min/宿主超时),token 燃烧环不变。该内容已作为止血 PR 先行实现(零行为风险,且是 A 验收标准②④的严格子集),链接见 #5164 线程。

C 维持现状:接受静默持有 + 终身一条 warn。

结构化八问

  1. 是什么问题:上游首字节前卡住时,bili 静默持有客户端最长 12 分钟,宿主超时重试每次全额重付 input tokens,日志没有任何一行能解释这次挂起。
  2. 什么原因导致的:两个独立机制——① pre-first-byte stall 没有有界快速失败(唯一硬杀是 12min idle abort,而宿主 ~300s 总是先放弃);② per-origin 终身去重 + wedge 期间 timer 不触发导致解除后首次 fire 烧掉唯一配额,诊断面在第一次 warn 后归零。
  3. 影响范围与严重程度(按选项):
  4. 怎么修复的(推荐 A 的修法思路):把现有 30s one-shot 看门狗从「只 log」升级为「log + abort」:到期 abort 该 fetch 的 AbortController(复用既有 clientAbort 链路),错误路径回 502 retryable、stage=first-byte-timeout;abort kind 不进 transport-retry 的 fail-fast 集合(不放大重试),计入 circuit failure 信号。诊断去重改造(B)已由止血 PR 先行落地,A 在其之上叠加,合计 ≤2 个 PR。
  5. 回归风险(按选项):
    • A:① 长 prefill 误杀(prefill>阈值)→ 默认值选择 + 显式 knob + 双向测试(stall<阈值不 abort / stall>阈值 abort+502+每 turn 有日志 / 健康流 idle re-arm 不受影响);② 个别宿主对 502-retryable 语义处理不当 → 502 是标准网关语义,已知宿主(dsh/opencode/pi/codex)对 5xx 均重试,PR 内逐车道确认;③ 与 PR [PR] fix: wedged proxy self-exits instead of holding its port forever (#4762) #4784 in-process 看门狗交互——正交(不同层),PR 内核对。
    • B:日志量增加(受 60s 冷却约束,上界 1 行/min/(origin,session));无客户端可见行为变化。
    • C:无(但燃烧环持续)。
  6. 是否破坏缓存:不涉及——三个选项都不触碰 prefix cache/增量游标/去重键;A 的 502 发生在首字节之前,尚无缓存写入。
  7. 是否改了设计方向:A 在主 forward 路径引入「proxy 主动回有界快速错误」——与既有 egress fuse(413)、Provider 429 (concurrency-style limit): turn re-driven every few seconds for minutes, keeps the account pinned at the limit #4204 429-hold local reject 同一族,非架构转向;B/C 不涉及。
  8. 是否引入新配置:A 新增 network.firstByteTimeoutMs(+ env BILI_FIRST_BYTE_TIMEOUT_MS),默认见子选项,0=禁用;新字段缺席=默认值,无需迁移。B/C 不涉及。

默认阈值子选项(仅 A 下)

子选项 默认 效果 风险
A-1(推荐) 120s 云端用户开箱即快速失败 未显式调高的本地长 prefill 用户在 prefill>120s 时收到 502(发版披露)
A-2 600s 更保守,覆盖多数本地 prefill 云端 stall 仍被持有 ~10min,燃烧环只部分缓解
A-3 12min(不变) 默认行为零变化,纯 opt-in 等价于 B 的效果面,失去默认修复意义

推荐:A + A-1(默认 120s)。理由:① A 的意义就是默认打破「静默持有→宿主自超时→全额重付」燃烧环,A-3 使其失效;② 120s 约为云端正常 TTFT(秒级)的 10–50×,误杀裕量充足;③ 长 prefill 本地模型用户的逃生门已经存在(BILI_UPSTREAM_TIMEOUT_MS 的存在证明该群体有调参习惯),新 knob 调高路径清晰;④ 502+retryable 让宿主更早、有意识地重试(dsh 每个失败 turn 省 ~180s 墙钟时间),且 bili 侧日志同时有 warn + 502 error 两条解释行。

对比表

维度 A(A-1) B C
打破 token 燃烧环 ✅(默认生效) ❌ ❌
消除分钟级静默持有 ✅(≤120s 快速错误) ❌ ❌
恢复日志可解释性 ✅(基于止血 PR) ✅(止血 PR 本身) ❌
行为变更/回归风险 中(长 prefill 误杀,可控) 无(仅日志量) 无
人工门禁 需要(本单) 不需要(已实现) 不需要
配置面 +1 字段(en/zh 双镜像) 无 无

状态:止血 PR(B 内容)已提交待 CI;A 等你拍板方向 + 默认值后进入实现。


Mirrored from ework issue #5262

Activity

  1. ranxianglei commented on Oct 30, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    止血 PR(B 内容)已提交:#5260 —— 定向测试 10/10 + typecheck 本地绿,全量回归看 CI。若拍 A,该 PR 是其诊断地基(验收②④),在其之上叠加 fail-fast;若拍 C,请拒掉它。方向与默认值等你拍板。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions