Skip to content

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

Closed

Description

@ranxianglei

存储收尾第二波:九项一次清账(正确性 + GC + 姿态)

承接 #3818/#3819(六修+缺省翻 full)。那一波明确"不在本 PR"的三类遗留,现在一次清完,清完才发下一版。每项独立可回滚,统一一个 PR 交付。

A. 正确性类

1. #3841 — full 模式重启后被收割块复活

选型:schema v6 sessions.orphans_json 列(候选 a)。理由:索引自足(不依赖信封兜底)、additive NULLable 迁移零风险(既有的列探测迁移模式)、verify 采样的 digest 口径自然对齐(expect/got 同含孤儿内容)。b)块表 discriminator 动查询面大;c)加载期探测治标不治本(行仍是错的)。

  • 记账:reap 形状会话保存时,把 blockContents 中不在 state.blocks 的孤儿内容写入 orphans_json;
  • 重建:viewFor 把孤儿内容并入 blockContents,不进 state.blocks(reap 意图跨重启保真);
  • 存量行 NULL = 旧行为(升级前从未 reap 过的会话不受影响);
  • 验收:reap → 重启 → 块不复活、孤儿内容可 decompress、信封不再被污染(跨重启路径打桩,现有 stake-8 只盖冷读)。

2. #3798 — 增量 resync 冷重建丢共享历史

修法:canonical 行指针持久化。

  • session_ext 新键 inc:canonical-ptrs:每次 raw ingest 后记录当前 canonical 消息的有序 audit 行指针(seq 数组);
  • readCanonicalFromStore 冷读改按指针取行(不再用 base-seq 切计数——remapPlanToAbsolute 对同 hash 复用旧行,共享前缀会落在切点之前);
  • 折叠/信封-only 保存必须保留最后指针清单;指针缺失/畸形 → 返回 bad 逼客户端 resync(不供部分历史);
  • 旧纪元(无指针)= 兼容路径维持现行为;
  • 验收:同史重发/共享前缀编辑/冷缓存 delta/折叠后保存/追加,shadow+full 双模式回归。

3. #3788 / #3823 — win32 legacy-only 复测

构建装 Windows VM(node 22 → 内置 node:sqlite 直通 full);Node<22.5 的下载路径已有镜像回退+升级提示(见 #3489 核实),单测覆盖;回评请报告者 ≥0.1.204 复测。

B. 资源回收类(GC)

4. #3463 — index.db 只增不删:归档 + GC 入口 + 收缩

现状核实:content 表 refcount 已在(schema v5),deleteSession 已联动清算+零引用回收(store.ts:812-851)。真正缺的是活会话的过期归档(archiveAfterDays 拍过板从未落地)与收缩(freelist 不还给 OS)。

  • storage.archiveAfterDays(文件键+env,默认 365,0=关):超期会话归档——内容行释放、会话行留墓碑(可按 id 找回)、web 列表默认不显示;
  • 手动入口 POST /__bili/storage/gc(admin 面):跑一遍归档扫描 + 零引用清扫 + PRAGMA incremental_vacuum(auto_vacuum=INCREMENTAL 建库即设;存量库首次需全量 VACUUM,加 ?vacuum=full 显式触发并提示 2× 磁盘瞬时);
  • 验收:单测(归档墓碑/共享行存活/找回路径)+ 本机 index.db 实测数字写进 PR。

5. #3671 — 压缩原文永久驻留

本 PR 落地归档联动释放(归档会话的 blockContents 随内容行释放,摘要保留在墓碑)。单点依赖风险(插件失效=70MB 原文重放)的结构性修复是快照渲染(#3756,已合灰度 0%)——在 issue 回帖明确该归因,不在这重复造。

6. #3762 — 媒体按内容寻址(图片跨会话去重)

C. 姿态类

7. #3723 — dsh 会话无限涨:亲自压缩转正

BILI_DSH_AUTHORED_COMPACTION 缺省开(env 显式 0 可关),加文件键 dsh.authoredCompaction;本机 lane 狗粮先行;回帖 #3723。

8. #3489 — 多镜像回退:核实后关

master 已有 npmmirror 链(engine-bootstrap.ts:284-292)+ 升级 Node 提示(:322,#3788 诊断输出可证)。核实无缺项 → 关闭并回链。

9. #3633 — 多实例姿态文档

docs 落一段:单实例单卷(RWO)、NFS/网络卷禁止(WAL 需要字节范围锁)、replicas 端点不是集群、扩展=每实例一卷。


不在本 PR:GC 自动调度周期(手动入口先验证收益)、blockContents TTL(等归档联动实测)、#3354/#3765 族。

验收总闸:统一验证 harness(verify2964)+ 全 storage 家族绿 + 金桩零漂移(媒体 CAS lazy 模式不碰存量字节)+ CI full。

Activity

  1. changed the title [-]存储收尾第二波:九项一次清账(正确性 #3841/#3798 + GC #3463/#3671/#3762 + 姿态 #3723/#3489/#3633/#3788)[/-] [+]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)[/+] on Oct 20, 2026
  2. ranxianglei commented on Oct 20, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

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

    查重: 不重复。 open/closed 全量检索(关键词: orphans/resurrect、canonical resync、archiveAfterDays/vacuum/GC、media CAS、authoredCompaction、镜像回退)无并行 issue/PR;九个子项各自也均无在途修复。本 issue 是 #3818/#3819 的收尾批次,统一一个 PR 交付。

    标签: bug + dsh + scope/storage + p1(含正确性类数据完整性缺陷——#3841 收割块跨重启复活、#3798 冷重建丢共享历史,按 owner 修订属 p1 面;dsh 会话无限涨为第 7 项)。

    接单计划(按依赖排序,每项独立可回滚):

    1. A1 Reaped blocks resurrect after restart in full mode: stale index row outlives the #2695 reap and poisons the JSON envelope #3841:schema v6 sessions.orphans_json(候选 a),reap 记账 → viewFor 重建合并孤儿内容不进 state.blocks;
    2. A2 Incremental resync loses shared history during a cold canonical rebuild #3798:session_ext["inc:canonical-ptrs"] 持久化 canonical 行指针,冷读按指针取行,缺失/畸形返回 bad 逼 resync;
    3. B4 [storage] unified store only grows: content rows are never deleted or archived — index.db at 637 MB on a single dev machine (GC: refcount content, expire sessions, vacuum) #3463:storage.archiveAfterDays(默认 365,0=关)+ POST /__bili/storage/gc + incremental_vacuum;
    4. B5 Compressed source text persists permanently with no deallocation path: blockContents alone accounts for 78% of archive s | 压缩原文永久驻留且无释放路径:blockContents 单会话占存档 78%,与「插件失效即膨胀」耦合(附 13.6:1 / 749 次回捞实测) #3671:归档联动释放 blockContents(摘要留墓碑);
    5. B6 Same image bytes stored once per conversation/message copy — split media payloads into a content-addressed store (follow-up to #3751/#3748) #3762:媒体 sidecar 拆入 content CAS(sha256+refcount),lazy 迁移,金桩零漂移;
    6. C7 [Bug][dsh] Unbounded growth of raw history → 39.7MB session file overwhelms dsh desktop renderer process, entire UI beco | [Bug][dsh] 原始历史无上限增长 → 39.7MB 会话文件压死 dsh 桌面端渲染进程,UI 全部无响应 #3723:BILI_DSH_AUTHORED_COMPACTION 缺省开 + 文件键 dsh.authoredCompaction;
    7. C8 feat: multi-mirror fallback for SQLite engine self-download + Node-upgrade hint in degrade output #3489:核实 master 镜像链与升级提示后关单回链;C9 Multi-instance posture is undocumented, and "/__bili/replicas" means session checkpoints — not pod replicas #3633:多实例姿态文档;
    8. A3 SQLite unified storage degraded to legacy-only (bili v0.1.201, node v20.19.5, win32-x64) #3788/SQLite unified storage degraded to legacy-only (bili v0.1.202, node v24.21.0, win32-x64) #3823:win32 构建装 Windows VM(node 22 内置 node:sqlite 直通 full)复测,回帖请报告者 ≥0.1.204 复测。

    验收总闸:verify2964 harness + storage 家族绿 + 金桩零漂移 + CI full。开始动手,进展回帖。

  3. ranxianglei commented on Oct 20, 2026

    @ranxianglei
    OwnerAuthor

    PR #3911 交付九项:GC/归档(#3463)、reap 台账(schema v6,#3841)、媒体 CAS(schema v7,#3762)、增量指针(#3798)、dsh 代压缩默认开(#3723)、README 多实例章节(#3633)。#3788/#3823 请报告者在 ≥本版本复测(引擎镜像回退已落),#3489 核实关闭,#3671 归因快照渲染。测试:4 个新套件 10 测试打桩先行,家族全绿。

  4. ranxianglei commented on Oct 20, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

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

    收到——#3911 已覆盖本 issue 全部九项,我停止并行实现(本地只做到 A1/orphans_json 一半,弃用不推分支,设计选型与 PR 一致可作交叉印证),转为独立审核 #3911:按规范走方向关(对原 issue 九项逐条核验收)、回归关(既有行为清点)、代码关、测试有效性关、diff 整洁度关,结论用八问回帖。先拉 PR head 通读全量 diff。

  5. ranxianglei commented on Oct 20, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

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

    PR #3911 独立审核结论(审的是最终 head,含我补的 3 个 commit)

    方向关(①):九项逐条对过 issue 规格——A1 orphans_json 候选 a、A2 canonical-ptrs 指针化、B4 归档+GC+收缩、B5 归档联动释放、B6 媒体 CAS lazy 迁移、C7 缺省翻开+文件键、C8/C9 核实关单+文档,全部按你拍板的方向落地,无跑偏;#3671 的结构性修复正确归因 #3756 没有重复造。

    回归关(②):审过的既有行为清单——孤儿守卫语义(从「拒写」变「记账后写」,driftedSessions 兜底保留)、verify digest 口径(expect/got 经 completeBlockContents 对称性验证成立)、stale-row sweep 引用计数(发现泄漏,已修,见下)、turn-lock 409 合同(现有 incremental-lane 测试钉住,reorder 后语义不变)、lazy 迁移不碰存量字节(golden-storage-render 零漂移实测通过)、writerOff 安全阀路径、deleteSession/archiveSession 引用纪律。发现的回归:1 处(sweep 媒体泄漏),已当场修复。

    代码关(③)+ 测试有效性(④):4 个新套件是真测试(直查表证明存储层拆分、refcount 2→1→0、跨重启不复活、真实 HTTP e2e 打桩同史重发杀手形状);4 个新测试我先本地复跑全绿。diff 干净(25 文件,无 version/lockfile/无关改动)。

    审核中发现并直接修掉的 3 处(已 push 到 PR 分支,e23d9c70b..7dc3bbe):

    1. dd98738ab stale-row sweep 泄漏媒体 CAS 引用(store.ts):full-path sweep 删消息行时只 release content_hash,不清 message_media 映射也不 release media hash → 图片字节永久驻留、引用计数只增不减,与你自己写的「Same image bytes stored once per conversation/message copy — split media payloads into a content-addressed store (follow-up to #3751/#3748) #3762 refcount release on delete/archive」合同矛盾(deleteSession/archiveSession 都清了,唯独 sweep 漏)。已补 release+清理,并加回归测试(无此修复红、有则绿,RED/GREEN 双证)。
    2. 770e407ef ?vacuum=full 文档与实现不符(admin.ts):issue 规格和 CONFIGURATION.md 都写 ?vacuum=full,但路由是 req.url === "/__bili/storage/gc" 精确匹配且 vacuum 只读 body——按文档用会直接 404。已让路由接受查询串(query 优先于 body),行为不变、文档归真。
    3. 7dc3bbe4a schema v6 迁移注释陈旧(「never gutted into the index」与新设计矛盾),改对。

    留给你的两个决策点(未擅动):

    • F6 归档会话在 web 列表的可见性:issue 写「web 列表默认不显示」,实现是 webSummaryRows 不过滤 status='archived' 且注释明说「web list keeps showing it」。二者必居其一:要么按规格隐藏(默认列表排除 archived,restore 入口不受影响),要么认下「墓碑可见=可找回」的设计并在 issue 里改口。我倾向隐藏(归档物不该占列表心智,找回走 restore 端点)。
    • F3 reap 台账的 merge 语义:reapBlocks 把孤儿视图 merge 进 orphans_json、从不裁剪;若未来某次活状态把某孤儿从 blockContents 里删掉,台账里的旧文本会在下次 loadLegacyView 重新合入 → digest expect/got 漂移 → 该会话永久 drifted(envelope-served)。触发条件窄(reap 之后还要有 blockContents 裁剪),但改成 replace 语义(台账 := 当前未被 state.blocks 认领的 blockContents)一行就能关掉这个窗口。建议顺手改;你若认为 merge 是有意的我就留。

    其余信息项:F4 畸形指针 PR 选「回落 legacy」而非 issue 写的「返回 bad」——最坏后果是一次强制 409 resync 自愈,不会供错分支,判定可接受;F7 ingestSession upsert 会把 archived 墓碑隐式激活回 active(杂散流量复活会话),倾向可用优先,确认一下意图即可。

    本地定向验证(非全量,全量以 CI 为准):pr-3911 head 上 4 个新套件 10/10;改动面家族(storage-unified/ext/watermark/full-flip/json-writer-off/incremental-lane/dsh-authored-compaction/golden-storage-render)84 pass / 1 skip / 0 fail。CI 已在 7dc3bbe 上跑(static-gates 已过,test lanes 进行中)。

    结构化八问

    1. 是什么问题:Storage wrap-up: 6 fixes + switch default mode to full (single PR) | 存储收尾六修 + 缺省模式翻 full(一个 PR 统一处理) #3818/[PR] storage: default unified mode flips to full + six #3135 cleanups (#3818) #3819 之后存储侧九项遗留——reap 块跨重启复活、增量车道冷重建丢共享历史、index.db 只增不收、压缩原文永久驻留、图片跨会话重复存储、dsh 会话无限涨、镜像回退待核实关单、多实例姿态无文档、win32 复测。
    2. 什么原因导致的:reap 形状(块出 state.blocks 但内容留 blockContents)从未被统一索引记账,重启后旧行复活;resync 基线用「行数切点」而 remapPlanToAbsolute 对同 hash 复用旧行,共享前缀落在切点前;archiveAfterDays 拍板未落地且无收缩;媒体内嵌消息 JSON;代压缩缺省关。
    3. 影响范围与严重程度:p1 面=Reaped blocks resurrect after restart in full mode: stale index row outlives the #2695 reap and poisons the JSON envelope #3841(full 模式 reap 过的会话重启即污染,数据完整性)+ Incremental resync loses shared history during a cold canonical rebuild #3798(增量车道冷缓存 delta 409 循环,特定客户端受影响);其余为资源增长(GC 类)与体验(dsh 无限涨)。触发条件均列在各子单。
    4. 怎么修复的:A1=schema v6 orphans_json 台账(reap 时定点 UPDATE,loadLegacyView 合入 blockContents 不进 state.blocks);A2=session_ext 指针清单(每次 raw ingest 落 canonical 行 seq,resync 前清空回 legacy,缺失/畸形诚实降级);B4=age 扫描→JSON 导出+墓碑+全局零引用清扫+incremental_vacuum(建库即设 auto_vacuum=INCREMENTAL);B5=归档联动释放原文、摘要留墓碑;B6=imageBase64 拆入 content CAS(sha256 寻址复用 refcount,lazy 只拆新写,读侧 join 还原逐字节);C7=缺省翻开+文件键;C8/C9=核实关单+README 多实例章节。
    5. 是否有回归问题:一处(sweep 媒体引用泄漏)已修+测试钉住;高危模式逐项过过——digest 口径对称性成立、turn-lock 409 合同不变、lazy 迁移金桩零漂移、writerOff 路径保留、错误处理保持 fail-fast(409/400/501/503 各就各位)。
    6. 是否破坏缓存:不涉及——wire 字节逐位不变(round-trip 金桩+直查表双重验证),前缀缓存策略未动。
    7. 是否改了设计方向:不涉及——schema 纯 additive(v6/v7),Compressed source text persists permanently with no deallocation path: blockContents alone accounts for 78% of archive s | 压缩原文永久驻留且无释放路径:blockContents 单会话占存档 78%,与「插件失效即膨胀」耦合(附 13.6:1 / 749 次回捞实测) #3671 结构性修复仍归快照渲染(Snapshot rendering: proxy-lane outbound is now rendered authoritatively server-side; existing sessions are immune to ker | 快照渲染:proxy 车道出站从服务端正史渲染,存量会话对内核升级免疫(#3752 根治) #3756,灰度 0%),本 PR 只补回收路径。
    8. 是否引入了新的配置:是,均已在 issue 中由你设计:storage.archiveAfterDays(文件键+env BILI_STORAGE_ARCHIVE_AFTER_DAYS,默认 365,0=关)、dsh.authoredCompaction(文件 boolean;缺省行为变更:off→on,env 显式 0 可关,CHANGELOG 已披露)、新增管理端点 POST /__bili/storage/gc 与 /__bili/storage/restore(loopback-only)。

    判定:可合(等 CI test lanes 绿;合并动作在你)。两个决策点(F6/F3)不阻塞合并,可在下个 follow-up 处理;#3788 已回帖请报告者 ≥v0.1.204 复测,#3489 你已核实关闭,#3723/#3671 已回帖。

  6. added a commit that references this issue on Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions