# 业务数据存储优化与指标报告 > 状态:已完成第一阶段可审计代码改造;未执行破坏性清理。 > 目标:减少无效任务文件、把任务元数据从“内存 + 散落目录 JSON”提升为“可恢复元数据存储”,并为后续 PostgreSQL 迁移留出接口。 ## 1. 优化前现状 ### 任务存储 - 任务状态 / 事件只存在于 `JobManager._jobs` 内存中,服务重启即丢失。 - `POST /api/jobs` 每次提交都会立刻创建 `service_workspace/{job_id}/input`、`output` 和配置文件。 - 任务完成后没有 TTL / 引用检查清理逻辑;历史任务目录会一直留在磁盘。 - `service_workspace` 中大量任务目录只是“曾经跑过一次”的产物,没有被任何素材或模板引用。 ### 素材存储 - 素材文件保留在 `service_assets/{asset_id}/asset.*`,元数据写在目录内 `meta.json`。 - 普通的 `POST /api/assets` 没有写入 `sha256`,只有 `.wcd` 导入路径开始做内容去重。 - 前端贴纸 `tint` 仍放在 localStorage,没有回到服务端统一维护。 ### 当前实际磁盘基线(2026-08-06 扫描) | 项 | 数量 / 大小 | |---|---| | `service_workspace` 任务目录 | 193 个 | | `service_workspace` 总大小 | 2.44 GiB / 2,618,726,669 bytes | | 被素材 `job_id` 引用的任务目录 | 6 个 | | 未被任何素材引用的任务目录 | 187 个 | | 未引用任务目录总大小 | 2.38 GiB / 2,556,506,642 bytes | | 素材文件 | 40 个,合计约 95.6 MiB | | 素材中重复内容多占空间 | 6 个额外文件,约 2.34 MiB | | 设计模板 JSON | 4 个 | ## 2. 优化方案与已落地改动 ### 1) 新增业务元数据存储层 新增 `backend/service/metadata_store.py`: - SQLite 单文件 `backend/service_metadata/app.db`。 - 建 `jobs` 和 `job_events` 两张表。 - 任务创建、状态更新、产物路径、SSE 事件都会落库。 - `JobManager` 启动时可以从数据库恢复任务,不再完全依赖内存。 - 表结构有意保持“一行元数据 + JSONB/JSON 字段”风格,后续迁移到 PostgreSQL 时主体字段不变。 ### 2) 任务目录可审计与可清理 扩展 `backend/service/storage.py`: - `job_dir_size()` / `job_dir_info()`:按任务统计占用。 - `stale_job_dirs()`:按“未被素材引用、不在元数据库、可选按年龄”筛选遗留目录。 - `remove_job_dir()`:提供精确清理,只清理 `service_workspace` 下的任务目录。 新增 `backend/service/storage_metrics.py`: - 默认 `dry-run`,只扫描并输出可回收空间。 - 只有显式 `--apply` 才会删除遗留任务目录。 - 示例: ```bash cd backend .venv/bin/python -m service.storage_metrics --max-age-days 0 .venv/bin/python -m service.storage_metrics --max-age-days 0 --json ../docs/storage-metrics.json # 确认后执行 .venv/bin/python -m service.storage_metrics --max-age-days 0 --apply ``` ### 3) 素材去重基础 - `.wcd` 导入路径已按 `sha256` 去重素材。 - `service_assets/{id}/meta.json` 中新增 `sha256` 字段。 - 后续 `POST /api/assets` 也可以统一补充哈希,形成服务级去重。 ### 4) 可实时查询指标 新增只读接口: ```text GET /api/maintenance/storage-summary ``` 返回指标包括: - `job_dir_count`:当前任务目录数。 - `referenced_job_ids`:被素材引用的任务数。 - `stale_job_count`:可回收任务数。 - `reclaimable_bytes`:可回收字节数。 - `jobs_in_db` / `events_in_db`:当前元数据分录数。 - `dry_run_only`: `true`,明确该接口不做删除。 ## 3. 优化后指标 ### 空间收益(只做审计,未执行删除) | 指标 | 优化前 | 优化后可清理 | 优化后保留 | |---|---:|---:|---:| | 任务目录 | 193 | 187 | 6(被素材引用) | | 任务文件占用 | 2.44 GiB | 2.38 GiB | ~59.3 MiB | | 任务空间占用 | 100% | 可回收 97.6% | 首个保护区约 2.4% | 换算: - 2,556,506,642 bytes ≈ 2.38 GiB。 - 若执行清理,仅任务目录可释放约 **2.38 GiB**。 - 清理后任务目录可降到约 **59.3 MiB**,即保留的部分仍是当前贴纸真正引用的任务产物。 ### 素材去重收益 当前 40 个素材文件中有 6 个属于重复内容,去重后: - 少存 6 个文件。 - 可节省 2,455,630 bytes,约 **2.34 MiB**。 - 素材文件数量从 40 → 34 个唯一内容。 这个数字目前不大,因为很多重复不到 100KB;真正大头仍是任务目录。 ### 效能与可维护性收益 1. **任务可恢复** - 原来重启服务后任务状态、进度、事件全部丢失。 - 现在 `jobs` / `job_events` 落库,启动时可恢复。 2. **查询由全盘扫描变为索引查询** - 原来 `list_jobs` 只读内存;任务详情依赖内存里的事件列表。 - 现在有持久化事件表和 `job_id` 索引,可追溯历史。 3. **清理依据可计算** - 原来“哪个目录能删”靠人工判断。 - 现在可统计“是否被素材引用、是否在元数据库、目录多老”,避免误删正在使用的任务。 4. **存储成本上限可控** - 配合 TTL 清理,后续每新增任务产生的产物会在保留期后被回收。 - 不会继续无限制累积。 ## 4. 建议后续执行步骤 1. 确认当前项目不再需要 187 个旧任务产物后,执行: ```bash cd backend .venv/bin/python -m service.storage_metrics --max-age-days 0 --apply ``` 2. 把 `metadata_store.py` 从 SQLite 迁移到 PostgreSQL: - 安装 SQLAlchemy / asyncpg。 - `docker-compose.yml` 增加 PostgreSQL 服务。 - 将 `jobs` / `job_events` / `assets` / `design_documents` 迁到 PG。 3. 把贴纸 `tint` 从 localStorage 迁到 `assets` 元数据,并由后端 `PATCH /api/assets/{id}` 维护。 4. `POST /api/assets` 统一补 `sha256`,实现服务级素材去重。 5. 增加后台定时清理任务,例如保留 7 天、30 天两档。 ## 5. 相关文件 - `backend/service/metadata_store.py` - `backend/service/job_manager.py` - `backend/service/storage.py` - `backend/service/storage_metrics.py` - `backend/service/app.py` - `docs/DESIGN_DATA_STORAGE_PLAN.md`