Appearance
流式响应为什么会堵住:Backpressure 与 Gateway Timeout
前置:客户端断开后上游该不该停:Disconnect 与 Cancellation Propagation
核心问题:客户端消费事件太慢时,数据会堆在哪里,系统怎样把压力向上游传播而不是无限 buffer 到 OOM?
慢客户端可以把正常 Run 拖成内存事故
模型每秒产生几十个 token delta,Tool/Node 还不断产生状态事件;手机网络上的 SSE 客户端却只能每秒读几条。如果服务端为这个连接维护无界 asyncio.Queue,Runtime 越正常工作,内存反而越快增长。
这就是 streaming backpressure:producer 产生事件的速度超过 transport/consumer 能发送的速度。 它与 broker backlog、Runtime Ready Queue 是不同层的问题。
先把事件分成“可压缩”和“不可丢”
不是每个事件都要逐条可靠发送。
例如:
text
token.delta → 可合并/采样;通常不需要 durable replay
progress.updated → 可覆盖旧值
run.waiting_approval → 关键状态事件;必须能恢复/重新查询
run.completed → 关键 terminal fact;必须持久化
artifact.created → 通常需要稳定 reference因此慢 consumer 时可以把多个 token delta 合并成较大 chunk,却不能因为 buffer 满就丢掉 approval.required 或唯一的 run.completed 事实。后两者应该首先存在于 durable state/event store,stream 只是 delivery channel。
Bounded buffer 的满载策略必须显式
最简单的 producer/consumer 模型:
python
queue = asyncio.Queue(maxsize=256)真正重要的是 maxsize 到达后怎么办:
- producer 等待:把 backpressure 传回上游;
- coalesce:合并高频 delta/progress;
- drop non-critical:只允许对明确可丢事件;
- disconnect slow consumer:保护服务端资源;
- switch to polling/resume cursor:客户端稍后按 durable event id 补关键状态。
如果 Runtime 核心 loop 被单个慢浏览器永久阻塞,也不合理。通常需要把 durable event commit 与具体 connection delivery 解耦。
Gateway Timeout 是另一条独立边界
反向代理、CDN、Load Balancer 可能限制:
text
connect timeout
idle/read timeout
request duration
response buffering应用允许 SSE 连接 30 分钟,不代表中间层也允许。连接可能在 Run 仍然 RUNNING 时被 proxy 断开。
因此协议不能建立这个错误等式:
text
stream connected == Run alive
stream disconnected == Run cancelledRun lifetime 应由 durable Runtime policy 决定。客户端重新连接后,通过 run_id + last_event_id/cursor 查询当前状态或继续消费事件。
Heartbeat 解决 idle,不解决所有长连接问题
定期 heartbeat/comment 可以避免某些 idle timeout,也帮助客户端发现连接失活;但它不能保证所有 gateway 都允许无限 request duration,也不能解决代理 buffering 配置。
如果基础设施天然不适合超长流,设计应允许:
text
POST /runs → 202 + run_id
GET /runs/{id}
GET /runs/{id}/events (SSE, reconnectable)而不是要求创建 Run 的原始 HTTP request 一直不结束。
Backpressure 还要跨到 Event Store 设计
若客户端断开后需要补事件,事件必须有稳定顺序/cursor,例如:
text
run_id
event_seq
event_type
payload/ref
created_at但不必把每个 token delta 都永久存库。可以持久化状态变化和重要 artifact,瞬时 streaming delta 只用于体验。这样 durable storage 不会因为 token stream 爆炸,又能保证恢复时不会丢关键事实。
怎么验证
用一个故意很慢的 consumer,把读取速度降到 producer 的 1/20,持续运行足够长时间,断言:内存/buffer 有上限;关键 terminal/approval 事件仍可通过 durable API 获得;可压缩 delta 被合并;断开连接后 Run 按 policy 继续。
再在测试/预发环境设置短 gateway idle timeout,确认 heartbeat 或 reconnect path 有效。真正需要验收的是“连接可以断,Run 事实不能乱”。
自测
- Producer 每秒产生 1000 个 event,客户端只能消费 100 个。没有 backpressure 时内存和 latency 会怎样变化?
- Gateway timeout 与后端 Run 仍在正常执行可以同时发生。系统应怎样避免把网关超时误记成 Run 失败?