From e30db81b9dddd750d374514b0fdc935f7987c3d5 Mon Sep 17 00:00:00 2001 From: weichen Date: Mon, 21 Sep 2026 09:48:32 +0000 Subject: [PATCH] docs(audit): document storage CoW status and dedup visibility Co-authored-by: Cursor --- .../2026-09-21-document-storage-cow-status.md | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .doc/audit/2026-09-21-document-storage-cow-status.md diff --git a/.doc/audit/2026-09-21-document-storage-cow-status.md b/.doc/audit/2026-09-21-document-storage-cow-status.md new file mode 100644 index 00000000..b8e9054f --- /dev/null +++ b/.doc/audit/2026-09-21-document-storage-cow-status.md @@ -0,0 +1,46 @@ +--- +title: "文档存储 CoW(写时复制)现状审计" +status: done +created: 2026-09-21 +updated: 2026-09-21 +severity: low +--- + +# 文档存储 CoW(写时复制)现状审计 + +## 背景 + +用户诉求:上传文档要「几个人传同一份只存一份」,涉及可编辑文档时「考虑 CoW,写入时复制」。本审计确认系统当前的去重与写时复制实现状态,并明确本次收口补齐了什么。 + +## 现状分析 + +系统存在两套内容存储,均已采用内容寻址(Content-Addressed Storage, CAS),天然满足去重与 CoW 语义: + +### 1. 二进制资产:`internal/storage/cas.go` + +- 物理对象表 `storage_blobs` 以 `sha256` 为主键,`ref_count` 记录引用数。 +- 上传命中相同 sha 时([cas.go](../../internal/storage/cas.go) `storeFileDB`)**不写第二份物理文件**,仅 `ref_count + 1`,并新建一条 `file_references` 逻辑引用。→ 满足「多人传同一文档只存一份」。 +- 文件路径 `blobs///.bin` 由内容 hash 唯一决定;`StoreFile` 对已存在 blob **只读不覆写**(命中去重分支直接返回)。内容变化 → hash 变化 → 新路径新文件。→ 即 CoW:修改等价于写入新对象,旧对象不被就地改写。 + +### 2. 可编辑文档正文:`internal/docrevision/pg_store.go` + +- `document_content_blobs` 以 `content_sha256` 为键,`ensureBlobTX` 使用 `INSERT ... ON CONFLICT DO NOTHING`([pg_store.go:309](../../internal/docrevision/pg_store.go))。 +- 每次编辑保存生成新 `document_snapshots`,指向新的 `content_sha256`;未变化的正文块复用同一 blob 行。→ 编辑保存不会复制未改动内容,写入时才产生新 blob。→ 满足「可编辑文档 CoW」。 + +## 结论 + +- **去重**:物理层早已实现(sha256 + ref_count);本次补齐的是**逻辑提示**——把 `FileReference.IsDeduplicated` 透出到上传/列表 API 的 `deduplicated` 字段,并在前端附件卡片与文档预览面板展示「重复·已复用」,让"只存一份"对用户可见。跨项目/同项目的逻辑引用记录保留(权限与审计需要),不做合并(已与用户确认方案:物理一份 + 提示)。 +- **CoW**:语义已是事实实现,但**缺防回归守卫**。本次新增 `TestCAS_ImmutabilityCoWContract`([cas_test.go](../../internal/storage/cas_test.go))固化契约:① 相同内容重复写入 → 物理 blob 数不变、字节不变;② 内容变化写入 → 新 blob、旧 blob 不被覆写、历史引用仍读原始字节。未来任何"就地改写 blob"的实现都会在测试期失败。 + +## 严重度 + +`low` —— 现状正确,本次为可见性增强 + 契约固化,无缺陷修复。 + +## 关联整改 + +- 任务:[2026-09-21-upload-parse-ux-dedup.md](../ops/short-term/2026-09-21-upload-parse-ux-dedup.md) +- 验收报告:[2026-09-21-upload-parse-ux-dedup-test-report.md](../test-reports/2026-09-21-upload-parse-ux-dedup-test-report.md) + +## 开放问题 + +- `storage_blobs.ref_count` 目前只增不减;引用删除(若未来支持资产删除)需配套 decrement 与 blob 物理回收,属后续独立任务,不在本次范围。