Skip to content

[PR] Storage wrap-up, nine items in one batch: GC/archive + reap ledger + media CAS + incremental pointers + dsh generation c | 存储收尾九项一车:GC/archive + reap 台账 + 媒体 CAS + 增量指针 + dsh 代压缩默认开 + 多实例文档 (#3901) - #3911

Merged
ranxianglei merged 7 commits into
masterfrom
2026-10-20_storage-wrapup-2
Oct 20, 2026

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Fixes #3901, fixes #3463, fixes #3762, fixes #3841, fixes #3798, fixes #3633

One PR, nine items — the storage wrap-up wave agreed in #3901. kernel-change: none (all src/, schema, tests, docs).

What

Item Fix Where
#3463 GC storage.archiveAfterDays age sweep (default 365, 0=off) archives idle sessions (full export to sessions/archive/ + tombstone row keeps the label + releases content/media refs); manual POST /__bili/storage/gc (?vacuum=full|incremental) and POST /__bili/storage/restore; auto_vacuum=INCREMENTAL always on store/shadow/persist/admin
#3841 reap journal schema v6 sessions.orphans_json: reaped blocks get their block rows deleted (refs released) and their contents journaled; views merge orphans into blockContents without resurrecting state.blocks; restart no longer revives reaped blocks or pollutes envelopes schema/store/ingest/shadow
#3762 media CAS schema v7 message_media: image bytes content-addressed in the CAS, message rows store {"$media":hash} (byte-identical hydration on read), one copy per distinct image across sessions, refcount release on delete/archive schema/ingest/store
#3798 incremental pointers inc:canonical-ptrs ordered row pointers replace the base-seq COUNT slice — resyncs that share history reuse rows below the cutoff instead of losing the prefix; resync invalidates pointers pre-response (crash window falls back to the old slice semantics, never the wrong branch); turn lock reordered: 409 turn-in-flight stays for the response phase, ack-then-wrap-up durability chain is awaited by the next turn incremental-lane
#3723/#3733 dsh flip dsh.authoredCompaction default ON (0.1.203 dogfood window held; failure mode = checkpoint doesn't shrink, never data loss); env BILI_DSH_AUTHORED_COMPACTION=0 or file false restores the #1729 refusal knobs/config
#3633 docs README "Running multiple instances": one-conversation-one-instance, L7 header-hash sticky routing (ClientIP affinity is a failover anti-pattern), RWO one-volume-per-instance, /__bili/replicas = checkpoints not pods README
#3788/#3823 win32 legacy-only reports — engine bootstrap mirror fallback + upgrade hint already landed (#3489 A/B); asking reporters to retest on this release; CI's windows matrix exercises the same download path comments on the issues
#3671 attribution: resident blockContents shrink follows GC/archive; the structural fix is the snapshot-render lane (#3756) issue comment
#3489 verified landed; closing comment

Tests (staked red first, then implementation)

  • tests/inc-canonical-pointers.test.ts (3) — identical resync keeps the shared prefix; edited-tail resync drops the replaced branch; malformed pointers force an honest resync, never a partial history
  • tests/storage-reap-journal.test.ts (2) — reaped blocks don't survive restart; journal idempotent
  • tests/storage-archive-gc.test.ts (3) — knob matrix; archive tombstone + shared content rows survive; restore + age sweep
  • tests/media-cas.test.ts (2) — same image across two sessions stored once; byte-identical round-trip + refcount ladder
  • tests/incremental-lane.test.ts 19/19, tests/storage-unified.test.ts 14/14 (v7 expectation), tests/dsh-authored-compaction.test.ts 16/16 (off-leg pins the explicit escape hatch), bounded/scale/replicas/goldens/writer-off/web-sessions/identity-registry/fold-reconcile families all green; tsc clean

Notes

  • Schema v6+v7 are additive (new column + new table, CREATE TABLE IF NOT EXISTS migrations; existing rows keep working — media rows hydrate lazily, old rows inline as before).
  • The turn-lock reorder in incremental-lane.ts keeps the documented 409 contract bit-identical for concurrent senders (pinned by the existing turn-in-flight test) while making ack-then-next-turn ordering durable.

…pointers + dsh-authored flip + multi-instance docs (#3901)

- #3463: storage.archiveAfterDays (default 365) age sweep + POST /__bili/storage/gc|restore; auto_vacuum=INCREMENTAL
- #3841: schema v6 sessions.orphans_json reap journal — reaped block rows deleted, orphan contents merged into views, never resurrected
- #3762: schema v7 message_media media CAS — imageBase64 content-addressed, {$media:hash} refs, byte-identical hydration, refcount release
- #3798: inc:canonical-ptrs row pointers replace the base-seq count slice; resync invalidates pre-response; turn lock reordered (409 in response phase, ack-then-wrap-up chain awaited)
- #3723/#3733: dsh.authoredCompaction default ON (env 0 / file false = #1729 refusal)
- #3633: README 'Running multiple instances' posture section

Fixes #3901, fixes #3463, fixes #3762, fixes #3841, fixes #3798, fixes #3633
@ranxianglei ranxianglei changed the title 存储收尾九项一车:GC/archive + reap 台账 + 媒体 CAS + 增量指针 + dsh 代压缩默认开 + 多实例文档 (#3901) [PR] Storage wrap-up, nine items in one batch: GC/archive + reap ledger + media CAS + incremental pointers + dsh generation c | 存储收尾九项一车:GC/archive + reap 台账 + 媒体 CAS + 增量指针 + dsh 代压缩默认开 + 多实例文档 (#3901) Oct 20, 2026
@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-20_storage-wrapup-2 (7dc3bbe)

Option A — Install this exact build (recommended)

npm install -g billion-context@0.0.4659-pr.3911.205724e

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr3911.tgz
npm install -g package

Install instructions are refreshed on each push.

Cosmetic follow-up to 8409552 (assertion fixes only): test names and comments still read v4/v5 while asserting v7, and the v4-fixture comment did not say why the newer additive shapes riding in the shared DDL re-land as no-ops.
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

查重: 不重复,但发现一个需要处置的关联项:PR #3466 仍 open 且同样声明 Closes #3463(它是「step 1」:refcount 回收路径 + 手工 GC 入口,正文写明 step 2 archiveAfterDays 自动化不在其中)。本 PR(#3911)按 #3901 九项一车完整覆盖 #3463(含 step 2)。若核对后 #3466 无独有内容,按机制归并它应被本 PR supersede 关闭——读完代码后逐项确认再报。其余相关项(#3818/#3135)已 closed,无重叠。

初判状态:

  • head e23d9c7 CI 全绿(30 passed / 14 skipped = gated e2e 车道);GitHub 判定可合并
  • 分支落后 master 49 个提交;与 master 侧变更交集 6 个文件(CHANGELOG.md、CONFIGURATION.md、CONFIGURATION.zh-CN.md、README.md、src/persist.ts、tests/incremental-lane.test.ts)→ 需逐文件做语义冲突核查
  • kernel-change: none ✓;25 文件 +1291/-52

标签: pr + dsh + scope/storage + p1(含「重启后坏块复活」#3841 与「存储只增不减」#3762 两个 p1 类缺陷;混合车含功能/文档故不打 bug)

审核进行中:全量 diff 通读 + 既有行为回归清点 + 定向测试,随后给结构化八问结论。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

查重: 重复触发——本条与先前通知是同一 head(8a53a0f)、同一组失败 job,按一次处理,不重复修。

结论先行:CI 已全绿可合。 8a53a0f 上的两个红门 + 随后暴露的两个存储迁移测试,都已在分支上修掉并验证。当前 head e23d9c70b 的所有门禁与已出结果的测试分片全部 success。

结构化八问

  1. 是什么问题:PR(Storage cleanup wave 2: Batch-clearing nine items (Correctness #3841/#3798 + GC #3463/#3671/#3762 + Posture #3723/#3489/ | 存储收尾第二波:九项一次清账(正确性 #3841/#3798 + GC #3463/#3671/#3762 + 姿态 #3723/#3489/#3633/#3788) #3901 九项存储收尾)推上去后 CI 两处红——①两个静态门禁(config-surface-guard、unused-exports);②门禁修完后又冒出两个存储迁移测试断言失败(ubuntu 22/3 与 24/3 分片)。
  2. 什么原因导致的:
    • 门禁①:storage.archiveAfterDays(本 PR 新增配置字段)在 website/config-reference/*.yaml 没有 seed 条目;门禁②:src/storage/ingest.ts 的 splitMediaContent 带 export 但只被本模块用(INTERNAL_ONLY)。
    • 测试:本 PR 把 STORAGE_SCHEMA_VERSION 从 5 提到 7(v6 reap-journal 列、v7 media-CAS 表),但两个迁移测试仍钉死期望值 5;且 watermark 测试的"较新版本 no-op"分支把库滚到 6——现在 6 < 7,open() 会执行迁移而不是 no-op,该分支语义被版本提升悄悄改变。
  3. 影响范围与严重程度:仅 CI/测试面,不影响任何运行行为;不修则 PR 无法合并(门禁硬拦 + 测试确定性失败,非 flake)。
  4. 怎么修复的:
    • 2d671f139:给 process.yaml 补 storage.archiveAfterDays seed(CONFIGURATION.md 中英同步已有对应行)+ 去掉 splitMediaContent 的 export。(该提交在我接单前已在分支上,我做了本地复跑验证。)
    • 840955294(owner 提交):两个测试断言 5→7,no-op 分支探针版本 6→8(高于当前 schema head,未来再 bump 也不会失效)。
    • e23d9c70b(我):纯注释/命名对齐——两个测试名与注释还写着 v4/v5 却断言 v7,统一改成 v7;并补一句说明为什么 v4 形状夹具里夹带的 v6/v7 新表/新列会被迁移的存在性探测安全跳过。无断言变化。
    • 过程说明:我最初并行写了一版相同修复,发现 owner 的 8409552 已覆盖同一行后丢弃了自己的重复提交,只保留上面的注释对齐增量,避免冲突堆叠。
  5. 是否有回归问题:无。这三个提交零 src 改动(仅 yaml seed、一个 export 关键字、测试文件);逐条核过既有行为:配置解析、导出面、迁移链路(v1→v7 各步幂等)均不变。本地定向复跑三个存储测试文件(storage-ext / storage-ingest-watermark / storage-unified)33 pass / 0 fail / 1 条件性 skip。
  6. 是否破坏缓存:不涉及。
  7. 是否改了设计方向:不涉及。
  8. 是否引入了新的配置:修复本身未引入;storage.archiveAfterDays 是本 PR(Storage cleanup wave 2: Batch-clearing nine items (Correctness #3841/#3798 + GC #3463/#3671/#3762 + Posture #3723/#3489/ | 存储收尾第二波:九项一次清账(正确性 #3841/#3798 + GC #3463/#3671/#3762 + 姿态 #3723/#3489/#3633/#3788) #3901)自身的设计产物,门禁修复只是补了它的文档 seed(default 365、env BILI_STORAGE_ARCHIVE_AFTER_DAYS)。

CI 状态(head e23d9c7)

  • 原红门:config-surface-guard ✅、unused-exports ✅(另 static-gates ×2、lock-sync、kernel-guard、docs-sync、version-guard、lane-guard、plan、pi-subagents-guard、test-import-graph、docs-coverage、build-artifact 全绿)
  • 原红测试:test (ubuntu-latest, 22, 3) ✅(含两个失败文件的分片)、(ubuntu-latest, 24, 1) ✅、(windows-latest, 22, 1/2) ✅、(ubuntu-latest, 22, 1/2) ✅;其余 node-24/windows-24 分片排队中,不触及本次改动文件。

自动修复通道用量:2 次以内,无需设计决策。判定:可合(待剩余矩阵分片自然转绿即可,预期无意外)。

awork added 3 commits October 20, 2026 14:52
…#3901)

The full-path authoritative sweep deleted stale message rows and released
their content hashes but never touched message_media — the CAS refcount
leaked forever and the image bytes were never reclaimable, contradicting
the #3762 release contract that deleteSession/archiveSession already keep.
Release each referenced media hash and drop the mappings with their row.
Regression test pins red→green.
…spec (#3463 review, #3901)

CONFIGURATION.md and the issue spec document '?vacuum=full', but the route
matched req.url exactly so any query string fell through unrouted and the
full VACUUM was unreachable as documented. Accept both forms; query wins
over body.
…3901)

The v6 migration note still said the reap shape 'must stay envelope-served,
never gutted into the index' — the opposite of what this column does: the
index now journals reaped orphan content across restarts.
@github-actions

Copy link
Copy Markdown
Contributor

📦 Merged Build Published

This merge to master was published as 0.0.649-master.9ffb852 (rolling master tag).

npm install -g billion-context@0.0.649-master.9ffb852  # this exact build
npm install -g billion-context@master  # always the newest merged build

npm view billion-context@master prints the current version at any time.

@ranxianglei

Copy link
Copy Markdown
Owner Author

Review follow-up (single-file archive): sessions are small and numerous, so a per-session export file would spray tiny files across the archive dir. Archiving now appends ONE JSON line per session to a single archive.jsonl (append-only; a crash mid-append leaves at most one truncated line, which the restore reader skips), and restore rewrites the log without the restored session's lines. No per-session files. New test pins: single log file, one line per session, restore removes exactly that line, re-archive appends fresh. Also rebased your three review commits (vacuum=full on the endpoint, media-ref release on the stale-row sweep, stale comment) — all green locally.

ranxianglei added a commit that referenced this pull request Oct 20, 2026
[PR] storage: GC archive as one append-only JSONL log (follow-up to #3911)
ranxianglei pushed a commit that referenced this pull request Oct 20, 2026
…ve/restore + bili storage gc (#3928)

Thin command layer over the store APIs #3911 exposed over HTTP (POST
/__bili/storage/gc, POST /__bili/storage/restore): no new store semantics,
no scheduler.

- sessions ls: id, label, decoded size, last-updated, storage status
  (active/archived); DB rows unioned with the JSON tree like bili export.
- sessions rm <id>: full delete via the existing deleteSession discipline
  (content refcount release incl. media), VACUUM afterwards (--no-vacuum).
- sessions archive/restore <id>: wrap archiveSession/restoreArchived on
  the single append-only archive.jsonl log; unified-store-only commands
  refuse legacy lanes with the BILI_STORAGE_UNIFIED hint.
- storage gc [--days N] [--vacuum full|incremental] [--dry-run]: prints
  the sweep plan (age-sweep candidates, zero-ref content rows, ~pages)
  from read-only queries, then runs the same unifiedGc() the HTTP
  endpoint calls; --dry-run stops after the plan.

Additive store surface backing the plan/statistics: GcPlan/gcPlan
(read-only prediction), contentZeroRefReport, sessionStorageStats,
vacuum(mode), status column in sessionSummaries/webSummaryRows.

Tests: tests/storage-maintenance-cli.test.ts demonstrates every command
against scratch stores and pins gc parity with direct store.gc().
Docs: README en/zh 'Storage maintenance CLI' + CONFIGURATION en/zh.
ranxianglei pushed a commit that referenced this pull request Oct 20, 2026
…ve/restore + bili storage gc (#3928)

Thin command layer over the store APIs #3911 exposed over HTTP (POST
/__bili/storage/gc, POST /__bili/storage/restore): no new store semantics,
no scheduler.

- sessions ls: id, label, decoded size, last-updated, storage status
  (active/archived); DB rows unioned with the JSON tree like bili export.
- sessions rm <id>: full delete via the existing deleteSession discipline
  (content refcount release incl. media), VACUUM afterwards (--no-vacuum).
- sessions archive/restore <id>: wrap archiveSession/restoreArchived on
  the single append-only archive.jsonl log; unified-store-only commands
  refuse legacy lanes with the BILI_STORAGE_UNIFIED hint.
- storage gc [--days N] [--vacuum full|incremental] [--dry-run]: prints
  the sweep plan (age-sweep candidates, zero-ref content rows, ~pages)
  from read-only queries, then runs the same unifiedGc() the HTTP
  endpoint calls; --dry-run stops after the plan.

Additive store surface backing the plan/statistics: GcPlan/gcPlan
(read-only prediction), contentZeroRefReport, sessionStorageStats,
vacuum(mode), status column in sessionSummaries/webSummaryRows.

Tests: tests/storage-maintenance-cli.test.ts demonstrates every command
against scratch stores and pins gc parity with direct store.gc().
Docs: README en/zh 'Storage maintenance CLI' + CONFIGURATION en/zh.
ranxianglei pushed a commit that referenced this pull request Oct 20, 2026
…ve/restore + bili storage gc (#3928)

Thin command layer over the store APIs #3911 exposed over HTTP (POST
/__bili/storage/gc, POST /__bili/storage/restore): no new store semantics,
no scheduler.

- sessions ls: id, label, decoded size, last-updated, storage status
  (active/archived); DB rows unioned with the JSON tree like bili export.
- sessions rm <id>: full delete via the existing deleteSession discipline
  (content refcount release incl. media), VACUUM afterwards (--no-vacuum).
- sessions archive/restore <id>: wrap archiveSession/restoreArchived on
  the single append-only archive.jsonl log; unified-store-only commands
  refuse legacy lanes with the BILI_STORAGE_UNIFIED hint.
- storage gc [--days N] [--vacuum full|incremental] [--dry-run]: prints
  the sweep plan (age-sweep candidates, zero-ref content rows, ~pages)
  from read-only queries, then runs the same unifiedGc() the HTTP
  endpoint calls; --dry-run stops after the plan.

Additive store surface backing the plan/statistics: GcPlan/gcPlan
(read-only prediction), contentZeroRefReport, sessionStorageStats,
vacuum(mode), status column in sessionSummaries/webSummaryRows.

Tests: tests/storage-maintenance-cli.test.ts demonstrates every command
against scratch stores and pins gc parity with direct store.gc().
Docs: README en/zh 'Storage maintenance CLI' + CONFIGURATION en/zh.
ranxianglei pushed a commit that referenced this pull request Oct 20, 2026
…ve/restore + bili storage gc (#3928)

Thin command layer over the store APIs #3911 exposed over HTTP (POST
/__bili/storage/gc, POST /__bili/storage/restore): no new store semantics,
no scheduler.

- sessions ls: id, label, decoded size, last-updated, storage status
  (active/archived); DB rows unioned with the JSON tree like bili export.
- sessions rm <id>: full delete via the existing deleteSession discipline
  (content refcount release incl. media), VACUUM afterwards (--no-vacuum).
- sessions archive/restore <id>: wrap archiveSession/restoreArchived on
  the single append-only archive.jsonl log; unified-store-only commands
  refuse legacy lanes with the BILI_STORAGE_UNIFIED hint.
- storage gc [--days N] [--vacuum full|incremental] [--dry-run]: prints
  the sweep plan (age-sweep candidates, zero-ref content rows, ~pages)
  from read-only queries, then runs the same unifiedGc() the HTTP
  endpoint calls; --dry-run stops after the plan.

Additive store surface backing the plan/statistics: GcPlan/gcPlan
(read-only prediction), contentZeroRefReport, sessionStorageStats,
vacuum(mode), status column in sessionSummaries/webSummaryRows.

Tests: tests/storage-maintenance-cli.test.ts demonstrates every command
against scratch stores and pins gc parity with direct store.gc().
Docs: README en/zh 'Storage maintenance CLI' + CONFIGURATION en/zh.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment