Repository navigation
会话历史加载成本:assistant/message 的 stream 字段重复存储占 80.7%,交付窗口固定 50 条且客户端无持久缓存 #8879
Replies: 2 comments
|
补充交付路径上的两个问题,并更正我自己一处数据。 0. 先更正我自己的一个数字报告正文里"同一 10.4 MB 客户端资源"是未压缩测量值——当时 curl 没带 1. 带内容哈希的资源没有缓存头
字节量不大(第 3 节合计约 0.5 MB),但这是纯粹的浪费,而且与同项目内 2. 会话历史在应用层不压缩WebSocket 握手(客户端主动请求了 permessage-deflate): 会话历史正是走这条通道,因此在应用层完全不压缩。这直接放大了本报告的核心问题:一个 32 MB 解压后的 transcript 会以 32 MB 过线,只可能被传输层压缩缓解(SSH 我实测了传输层压缩的效果,供判断该由哪一层负责:
即:传输层压缩对交互延迟无损,对批量载荷收益显著——但它不该是这条路上唯一的压缩。 3. 更正后的体积对照
HTTP 路径上的压缩是有效的(约 2×)。缺的是两处:哈希资源的缓存头、以及 WS 通道的压缩。 建议
|
|
本贴暂无回应,补一条可能有助于排期的信息:它与 #8846、#8849 是同一主题的第三种形态,而那个主题在 #8849 里已由 三份报告是同一条原则的三种违反
前两条是"局部坏状态 → 整体不可用",本条是"局部冗余 → 整体体验劣化"——同一个设计取向的三种表现:局部问题没有被局部化,而是由全局承担。 一条独立复核(第二台机器,同一构建)同一构建 0.2.0-rc.2、另一台机器(macOS / Node 26)上,
⇒ 这不是某个特定会话的偶发现象,而是该字段的固定结构开销: 请求
边界第 2 节的两组数字来自两台不同机器、不同会话,所以它证明的是比例的稳定性,不是逐字节一致;端口/协议侧我仍无法从外部读到客户端的实际请求参数( |
Uh oh!
There was an error while loading. Please reload this page.
Summary
长会话的历史加载有三个互相放大的问题,合起来让「查看历史」几乎不可用:
assistant/message的data.stream是模型输出的逐块流式日志,而data.message已经存了同一份组装后的最终内容。实测stream占该会话总字节的 80.7%,体积是message的 4.4 倍(小块各自带time/index/blockType包装的膨胀)。paginate()从会话末尾反向取,DEFAULT_MAX_MESSAGES = 50。一个 3,672 step / 约 1 万条消息的会话,翻到顶部需要点击「加载更多」两百来次。resumeCursor,「重连时保留已发布窗口」),页面一刷新即全部丢弃并重新拉取。三者叠加的结果:用户为一份重复存储的数据付出等待,却仍被限制在每页 50 条地翻页。
Reproduction
被测会话(生产环境,单个 agent 会话):
按事件类型的字节与行数分布:
assistant/messagetool/resulttool/calluser/messageagent/inbox/splicedrequest/headerstep/start+step/end规模来自回合数而非畸形单条:3,672 个 step,77.2 MB ÷ 3,672 ≈ 21 KB/step,完全线性累积。
继续拆
assistant/message(3,671 条,合计 56.6 MB)的data各字段:streammessageusageturn/step一条 73 KB 样本的内部构成:
stream与message承载同一段推理正文;stream因被切成大量小块、每块重复type/time/index/blockType,膨胀到 4.4 倍。分页行为(
dsh-api-session-controller/lib/index.js):调用处:
其中
DEFAULT_MAX_MESSAGES = 50。服务端迁移成本(对照组,非病灶):同会话 v3→v4 迁移实测
即迁移是一次性、封顶约 3.5 秒,不是历史慢的主因。
传输:会话经 SSH 隧道跨机访问,隧道未开压缩。同一 10.4 MB 客户端资源:
Current behavior
重复持久化:每条 assistant 消息把流式 chunk 序列(含每 chunk 的时间戳与索引)与组装后的
message一并落盘。这是纯粹的冗余——message已足以重建内容,stream仅对「逐字回放」有意义,但代价是全部历史的 80.7%。同一段推理正文存两遍,且reasoningEffort: high下单步reasoningTokens达 4,345 / 5,624,冗余量随推理深度线性放大。窗口不可配置:
DEFAULT_MAX_MESSAGES = 50是硬编码常量,不是 Cordis 配置项。协议层是开放的——wire contract 里maxMessages?: number与turnWindow?: {minMessages, minTurns}都是 optional 请求字段,客户端也实现了loadOlder()/loadThrough(seq)——但dsh-client-ui-chat对用户只暴露performanceUsage与transcriptView,没有任何控制历史窗口的设置。客户端不缓存:对客户端 bundle 全文检索,
indexedDB0 处、serviceWorker0 处、caches0 处。localStorage有 33 处,但只存界面状态(侧栏宽度、快捷键、每会话的 conversation store、待发请求),且其约 5 MB 配额本就装不下这类历史。唯一近似缓存的是内存中的已发布窗口,注释自述「The domain retains its published window during reconnection」——只在 WebSocket 重连时续传,页面刷新即失效。服务端另有
dsh-session-projection-cache,但它只省服务端重复计算,不减少传往客户端的字节。Expected behavior
建议按收益排序:
stream重复(收益最大、改动最局部)。若仅传/存message,本会话由 77.2 MB 降至约 31.5 MB(−59%)。可选路径:默认不持久化 chunk 日志;或只在可回放窗口内保留并定期压实;或对stream使用紧凑编码(chunk 边界增量而非每块重复type/time/index)。maxMessages/turnWindow,缺的只是暴露:给ui-chat增加一个窗口设置,或把DEFAULT_MAX_MESSAGES提为可配置常量。当前 50 条对万级消息的会话实际不可用。asOfSeq校验的 IndexedDB 缓存:命中则只拉增量,未命中才全量。这能把「刷新即重拉」消除,且对上述两条是正交增益。运输层加压缩(
ssh -C)能缓解 2.1 倍,但那只是掩盖前两条——真正的问题是根本不该传这么多。Environment
@deepseek-ai/dsh0.1.7-rc.2(dsh-api-session-controller、dsh-session-format-v3-to-v4、dsh-client-ui-chat同版本)dsh webdsh-session-format-v3-to-v4),迁移成本实测封顶 3.5 秒,与本文问题独立All reactions