Skip to content

[Bug][session-order] Snapshot capacity handling drops newest conversations and exceeds the KV limit #6255

Open

Description

@Afloat16

Reproduction

At master dc041dd1ab1a014efa352ba47a6f31fc47f55284, normalizeForWrite() sorts session-order skeletons by recency only when their count exceeds 1,024. The byte budget can bind much earlier: with 40 conversations containing 4,000 kind/fragment pairs each, the legacy writer retains the 27 oldest records and drops the newest 13. After clearing memory and hydrating the file, the most recently active conversations have no persisted skeletons.

The same producer also uses its 8 MiB legacy-file budget for unified KV writes, although the store rejects extension values over 4 MiB. In the real SQLite migration regression, the write fails with extension value exceeds 4194304 bytes, leaving the KV snapshot empty and hydration on the legacy fallback path.

Proposed fix

Treat this as one bounded-snapshot producer fix: sort the union newest-first before either cap, measure serialized UTF-8 bytes, and use the existing store ceiling for KV migration and steady-state writes. Keep the legacy-file ceiling and the existing single-oversized-record fallback behavior. No new configuration, schema or fragment truncation.

Validation

  • Unchanged master: legacy recency and actual KV migration regressions fail; the small-entry/timestamp-tie control passes.
  • Fixed: 24 tests pass across order persistence, unified storage, neutral skeleton resolution and persona restart stability.
  • Coverage clears in-memory skeletons and removes the legacy file before KV-only rehydration, then exercises a subsequent bounded write with a new most-recent conversation.
  • Whole-project typecheck, build and diff whitespace checks pass.

Duplicate screening: existing PRs/issues and Afloat16's prior issues/fork branches were checked. #3405 introduced this producer and #5757 migrated it to KV. #6186 fixes write lifecycle, while #6203 fixes generic extension validation; neither makes the snapshot producer respect its carrier's size limit or prioritize recent entries under that limit.

Activity

  1. maozhuoshushu commented on Nov 12, 2026

    @maozhuoshushu

    本机 0.1.213(Windows,storage.unified=full)实测可以给这份复现补一组真实机器数字,两条都在同一台机器上同时成立:

    1)8 MiB 帽丢的是最新创建的会话。 session-order 解压后 8,347,598 字符,距 MAX_SNAPSHOT_BYTES 只剩 41,010 B;条目数 103(< 1,024),所以 normalizeForWrite 不走按 lastSeen 排序那一支,数组保持写入序——新建会话 append 在尾部。一旦越帽,break 截掉尾部,之后新建的会话全部静默丢弃,重启后没有持久化骨架(本机当前活跃会话在 idx 57,不在尾部,所以先丢的是 11-08/09 那批新建会话,再往后的新会话同样落不了盘)。这与你的 40 会话复现是同一机制。

    2)8 MiB 快照帽与 4 MiB KV 帽冲突,迁移会静默降级。 含 #5757 的构建里 hydrateOrderUnified() 首次迁移会走 setKvAsync(ORDER_NS, ORDER_KEY, {version,entries});8.34 MB 的 JSON 超过 EXT_VALUE_MAX_BYTES = 4 MiB,抛 [storage] extension value exceeds 4194304 bytes → kvReady = false → 之后每次 flush 回落 legacy 文件路径(上游是明文 JSON,8.34 MB/次)。也就是说本机升级后 kv_store 会一直是 0 行,写入量反而从打补丁后的 2.87 MB/次涨到 8.34 MB/次。

    3)TTL 救不了这个冲突。 按 lastSeen 丢整条后重压:7 天 5.23 MB、14 天 5.83 MB,都仍 > 4 MiB;要落进 KV 得压到 ≤3.8 天,代价是丢掉 3–4 天前 21 条共 4.3 MB 的活跃大条目。所以同意你的方向:先修生产者(两种帽都先按 recency 排序、按 UTF-8 字节计量、KV 用 4 MiB 帽),TTL 只能当 8 MiB 帽的安全阀,不能当迁移前提。

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions