Skip to content

fix(timeline): 冷会话回放每页只算本页,不再逐页重算整个会话 - #1723

Merged
chen-ran merged 1 commit into
mainfrom
fix/replay-page-bounded
Nov 1, 2026
Merged

chen-ran merged 1 commit into
mainfrom
fix/replay-page-bounded

Conversation

@chen-ran

@chen-ran chen-ran commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

Author

  • Human
  • Agent

Type

  • bug
  • feat
  • test

Summary

ListSessionEventsBySessionPageBeforeWithinBytes 占满数小时,Platforms 配置保存拿不到连接,30 秒后超时。

原因:LoadEventsForReplay 按页(256 行)循环,但每一页的 SQL 都先把整个会话重算一遍:

  • message_rows:会话全部可见历史行关联各自的来源事件,从 event_data 解出 event_cursor 再按消息聚合;
  • changed_messages:已折叠消息关联它们的全部事件,再解一遍 event_cursor、再聚合;
  • 对游标之前的全部候选事件做 octet_length(event_data::text) 并累计。

这些 CTE 都标了 MATERIALIZED,LIMIT 截不住,一次回放的数据库工作量是"页数 × 会话规模"。

这个 PR 的改动:

  1. 语义:已折叠的消息不再因为后来的编辑或删除回到上下文。 摘要按这条消息的行记录的样子代表它。changed_messages 因此整段删掉;合成时的 changedSince 和 CheckpointMessage.EventCursor 一并去掉;ClassifyHistoryCheckpointMessages 不再关联事件表。这是 feat(bots): add runtime diagnostics workbench #695 评论里讨论过的取舍。
  2. 每页只算本页。 每个候选事件只查它自己那条消息的历史行(走 (session_id, source_message_id) 索引),判断是否被折叠;先按游标取够一页,再只对这一页计算并累计字节。进程侧 64 MiB 预算和逐页字节上限的语义不变。

需要知道的行为差异:

  • 热会话里,编辑一条已折叠的消息,仍会推进 Discuss 的未消费判断并唤醒一轮,但这轮上下文里看不到这次编辑。这和现在"checkpoint 已折叠、但游标尚未消费的输入"的处理一致(TestBuildMentionGatesOnWatermarkNotFolding),这里没有另做处理。

不在这个 PR 里:

  • 同会话回放去重、回放失败时不再静默新建空会话、回放并发上限(后续 PR)。
  • feat(compaction): 以 history checkpoint 取代压缩覆盖层 #1700 引入的另一条慢查询 ListHistoryMessagesAfterWithinBytes(Server 侧,单独排查)。
  • 没有为完整排序键新增索引。现在用的是 (session_id, received_at_ms) 加增量排序,最后一页会把已折叠的剩余事件扫一遍(每个事件一次索引查询),整次回放仍只是 O(会话事件数)。

Related Issues

Refs felinics/Memoh-Cloud#695

Validation

测试

  • go test ./internal/chat/... ./internal/channel/discuss/ ./internal/channel/inbound/ 通过。
  • 本地 PostgreSQL 18 上跑 TestPostgres*:internal/chat/timeline、internal/chat/message、internal/chat/checkpoint、cmd/agent、internal/agent/application 全部通过。
  • TestPostgresReplayAndClassificationFollowTheCheckpoint 改为断言:被折叠的消息在折叠后被编辑,也不再回放。
  • TestComposeContextWithCheckpointKeepsAnEditedMessageFolded 取代原来"折叠后有变化就重新组成"的测试。
  • golangci-lint run ./internal/chat/... 无告警。

回放耗时对比(本地 PostgreSQL 18;合成会话共 30,000 个事件,每个约 3.3 KB,其中 2,048 个在 checkpoint 之后;按 LoadEventsForReplay 的方式逐页执行到结束)

每页 整次回放(9 页)
main 约 330 ms,每页都一样 约 3.0 s
本 PR 3–8 ms;最后两页约 80 ms(扫一遍已折叠的剩余事件) 约 0.19 s

EXPLAIN 的计划是:idx_session_events_session_received 倒序索引扫描,每个事件在 idx_bot_history_messages_session_source 上做一次索引查询,没有整会话的聚合。这是合成数据上的结果,没有在生产数据和 Cloud 的 RLS 上测过。

开发环境(本分支启动的隔离 devenv:docker compose,换了端口,数据卷全新)

  • 模型是本地 OpenAI 兼容 fixture,上下文窗口 40k。它的回复会列出收到的用户消息里有哪些标记(MK数字,以及摘要对应的 SUMMARY),所以回复内容本身就反映了这次请求的上下文。
  • Telegram 指向本地的 fake Telegram Bot API,在 Platforms 页面添加 Telegram 平台并保存(PUT /bots/{id}/channel/telegram 返回 200,耗时 59 ms)。
  • 在群里依次发:MK11、MK12(大消息,触发一次真实折叠)、MK13、MK20。重启 channel 容器让这个会话变冷,再发 MK21。
  • Channel 日志:timeline replay payload admitted ... event_count=2 persisted_event_count=4,即 4 个已持久化事件里,被折叠的 MK11、MK12 在 SQL 里就被排除,只回放了 2 个。
  • 重启前 bot 回复 SUMMARY, MK13, MK20;重启后回复 SUMMARY, MK13, MK20, MK21。冷回放后的上下文和重启前一致,只多了新消息,被折叠的消息没有以原文重新出现。

未验证:开发环境里没有验证"渠道消息折叠后又被编辑",因为 fake Telegram 不能注入编辑事件;这个行为由上面的 PostgreSQL 集成测试覆盖。也没有做连接池压力测试。

Screenshots / Recordings

以下截图来自 Agent 在本分支开发环境里的操作,只说明流程走通,不代表人工 QA;回放的依据是上面的 Channel 日志。

Telegram Discuss 会话:Channel 重启前,bot 看到 SUMMARY, MK13, MK20;重启后第一条消息 MK21 经冷回放,bot 看到 SUMMARY, MK13, MK20, MK21。
Channel 重启前后的 Discuss 会话

在 Platforms 页面保存 Telegram 平台配置成功(这里只说明保存流程走通,不是压力测试)。
Platforms 保存成功

Human QA

  • Human QA passed

🤖 Generated with Claude Code

回放 SQL 每取一页(256 行),都要先把整个会话的历史行和事件聚合两遍
(message_rows、changed_messages,都标了 MATERIALIZED),LIMIT 截不住,
一次回放的数据库工作量是"页数 × 会话规模"。Memoh-Cloud #695 中,Channel
重启后 8 条连接被这条查询占满数小时,配置保存拿不到连接而超时。

- 已被 checkpoint 折叠的消息,之后的编辑、删除不再让它回到上下文:摘要按
  它的行记录的样子代表它。changed_messages 因此删掉,合成时的
  changedSince 和 CheckpointMessage.EventCursor 一并去掉,
  ClassifyHistoryCheckpointMessages 不再关联事件表。
- 每个候选事件只查它自己那条消息的历史行(走
  (session_id, source_message_id) 索引)来判断是否被折叠;先按游标取够
  一页,再只对这一页计算并累计字节。

合成的 3 万事件会话(2k 未折叠)上,一次完整回放从约 3.0 s 降到约 0.19 s,
每页约 3 ms。

Refs felinics/Memoh-Cloud#695

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added bug Fixes a bug or unexpected behavior change:server Changes backend code, configuration, or API contracts size:S PR size uses the larger of added or deleted lines, excluding generated files labels Nov 1, 2026
@chen-ran
chen-ran merged commit e82c2ca into main Nov 1, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Fixes a bug or unexpected behavior change:server Changes backend code, configuration, or API contracts size:S PR size uses the larger of added or deleted lines, excluding generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant