Skip to content

fix: event append has no idempotency guard so identical payloads are re-persisted #523

Description

@LeXwDeX

Why

深度审计确认。本条在 481e31e2b7(summary.diffs 256 KiB 闸,见 #522 撤回说明)之后的当前代码上依然成立,是修复后剩余浪费的主因。

  1. packages/core/src/event.ts:273.insert(EventTable) 是全仓唯一的事件写入点,没有任何重复检测rg "dedup|idempot|lastData|hash\(data\)" packages/core/src/event.ts 返回空)。同 aggregate_id、同 type、payload 逐字节相同的事件被无条件重复追加

  2. 修复后数据实测(只统计 time_created >= 2026-08-28 16:05:55 的会话,即全部跑在带闸代码上):

    message.updated.1 事件总数        28,059 条 / 208.9 MiB
    distinct (会话, payload sha256)   21,960
    会话内重复 ≥2 次的 payload           721 组,涉及 6,820 条事件
    逐字节重复浪费                     134.3 MiB / 208.9 MiB = 64.3%
    

    即:闸住 diff 摘要之后,仍有 64.3% 的消息更新事件是逐字节重复写入,信息增量为零

  3. 修复前的同类证据(sha256 比对,长度恰为 18,455,856 字节的 15 条事件哈希后只有 3 个 distinct payload,重复发生在同一会话内部):

    会话 同一 sha256 出现次数 逐字节重复体积
    ses_034083992ffe 7 次e7cdedfa…,seq 276/293/310/325/345/357/367) 123 MiB
    ses_03408397affe 4 次(f3efbc8b…,seq 208/227/241/250) 70 MiB
    ses_034083993ffe 4 次(e876d42e…,seq 388/404/418/427) 70 MiB

    注:跨会话之间 payload 并不相同,仅长度巧合相等 —— 缺陷是会话内重复写入,不是跨会话复制

  4. 放大系数:ses_fe5fecc1e45c 有 8,117 条 message.updated.1 对应 2,082 条真实 message 行、8,330 个 part —— 平均每条消息被重发 3.9 次全量快照(流式更新每次都持久化完整状态)

  5. 全量快照语义下中间态无重建价值:若每个 aggregate 只保留 max(seq) 一条,message.updated.1 从 6.371 GiB 降至 0.141 GiB,可回收 6.229 GiB / 283,266 行(97.8%)

Scope

  • packages/core/src/event.ts(append / insert 路径 :273
  • packages/core/src/event/sql.tsEventTable 新增 payload hash 列 + migration)

Approach

  • insert 前增加幂等闸:比对同 aggregate_id + type 的上一条事件 payload hash,相同则跳过写入
  • hash 建议落列(如 data_hash)而非每次重算 —— 对 MiB 级 payload 反复 sha256 的代价不可接受;写入时算一次,比对走索引(现有 event_aggregate_type_seq_idx on (aggregate_id, type, seq) 可直接定位上一条)
  • 跳过的重复事件是否仍推进 event_sequence.seq 需按 sync 契约明确决定,两种语义都要在测试里固定下来
  • 该防线对所有全量快照型事件生效,不与具体业务字段(如 summary.diffs)耦合,与 fix: message snapshot persists untruncated session diff summary into the event log #522 的源头闸独立可叠加

Acceptance

  • 单测:对同一 aggregate 连续 publish 两次内容完全相同的事件,event 表只新增一行
  • 单测:内容不同时正常追加,seq 严格递增且无重复
  • 明确并测试 seq 语义:被跳过的重复事件是否占用 seq。以下三处读取路径必须在新语义下行为正确 —— core/src/event.tsgt(seq, after) + orderBy(asc(seq)) 增量读、opencode/src/server/routes/instance/httpapi/handlers/sync.ts 的客户端增量同步、opencode/src/control-plane/workspace.tswhere(eq(aggregate_id, sessionID)) + asc(seq) 全量重放
  • 回归:sync / workspace / projector 相关测试全绿
  • 实测口径:新建会话跑一轮多文件编辑后,message.updated.1 的「distinct payload 数 / 事件总数」应接近 1(当前 post-fix 为 21,960 / 28,059 = 0.78,即 64.3% 字节浪费)

Activity

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