Files
wordcloud/learning/runbooks/02-safe-update-data-preservation-runbook.md
T

11 KiB
Raw Blame History

受控更新:如何在升级版本时保留数据(runbook)

对应你的问题:“给我一个能够保留数据来更新的技术文档,从而实现运维的可控” 一句话目标:升级代码、重建容器、打补丁时,项目所有的用户数据都不丢、可回滚、可验证。

这份文档是“按步骤照着做”的故障/操作手册。不是空谈原则,是每一步都有命令、都有验证点。


0. 先说结论:这个项目现在更新会丢什么

当前(2026-08-13)数据分两块:

数据 存放位置 Docker 里会不会丢
任务产物(输入/输出/配置) backend/service_workspace/ 已挂卷 wordcloud_workspace,不丢
贴纸/素材 backend/service_assets/ 已挂卷 wordcloud_assets,不丢
工程项目 backend/service_projects/ 已挂卷 wordcloud_projects,不丢
设计模板 backend/service_design_templates/ 已挂卷 wordcloud_design_templates,不丢
上传字体 backend/service_fonts/ 已挂卷 wordcloud_fonts,不丢
任务元数据(SQLite backend/service_metadata/app.db ⚠️ 没挂卷,容器重建即丢
订单数据 backend/service_orders/ ⚠️ 没挂卷,容器重建即丢

⚠️ 红色标记的两项是当前最要紧的缺口

  • service_metadata/app.db 是任务状态/事件的权威来源(metadata_store.py)。 它写进了容器的可写层,不在任何 named volume 里。 docker compose up -d --build(重打镜像、重建容器)时,这层会被清空 → 任务记录清零
  • service_orders/ 是新增的运行时目录,同样没进卷。

其余 5 个目录因为有 named volume,容器重建时数据还在。

所以“保留数据来更新”的第一步不是写代码,是先确认要保的数据都在卷里/都有备份。 光指望 docker compose down 不删数据是不够的——重建容器才危险,而更新几乎必然会重建。


1. 数据全景:更新前必须认识的东西

1.1 文件数据(前端/后端所有运行时产物)

backend/
├─ service_workspace/     # 任务目录,每个 job 一个 {job_id}/input|output|配置
├─ service_assets/        # 素材库,每个 {asset_id}/asset.* + meta.json
├─ service_projects/      # 工程项目
├─ service_design_templates/  # 设计模板 JSON
├─ service_fonts/         # 上传字体
├─ service_metadata/app.db   # 业务元数据(SQLitejobs / job_events 两张表)★ 易丢
└─ service_orders/        # 订单数据 ★ 易丢

关键点:

  • 产物文件(SVG/PNG/字体/Excel)存文件系统,不是数据库里。
  • 一份任务结果有时自带一个 word_locations.db(词云算法自己的 SQLite 结果文件),在那任务目录里,跟 service_metadata/app.db 不是一回事。

1.2 元数据(数据库)

service_metadata/app.dbmetadata_store.py 生成的 SQLite 单文件:

表 jobs        — 任务主表(job_id、status、stage、progress、params、artifacts
表 job_events  — 任务进度事件(SSE 落库,重启可恢复)

这文件很小(几百 KB 级),但它是“任务可恢复”的关键。更新时必须单独备份。

1.3 不算项目数据的(别备份、也不要提交)

backend/.venv/           # 本地 Python 虚拟环境
frontend/node_modules/   # npm 依赖
backend/.runtime/        # 运行时日志(可丢,日志另按需归档)
*.so / dist/             # 编译产物,重新构建即可

2. 受控更新流程(四条主线)

无论本地还是 Docker,都按 备 → 查 → 更 → 验 → 回 五步走:

① 备份数据   →  ② 记录当前版本   →  ③ 升级   →  ④ 验证   →  ⑤(出问题)回滚

下面的命令直接复制可用。


3. 第一步:备份(更新前必做,最好 5~10 分钟搞定)

3.1 备份文件数据

# 在项目根目录执行,生成带时间戳的备份包
TS=$(date +%Y%m%d_%H%M%S)
BACKUP_ROOT=$(pwd)/.backups
mkdir -p "$BACKUP_ROOT"

tar -czf "$BACKUP_ROOT/wordcloud-data-$TS.tar.gz" \
  -C backend \
  service_workspace service_assets service_projects \
  service_design_templates service_fonts service_metadata service_orders

3.2 单独稳妥备份 SQLite 元数据库(推荐用 sqlite 的在线备份,避免写一半)

sqlite3 backend/service_metadata/app.db ".backup '.backups/metadata-$TS.db'"

如果服务正在跑,用 .backup 而不是直接 cp,因为 cp 可能拷到写一半的文件。

3.3 校验备份真的可读(光备份不看等于没备)

tar -tzf "$BACKUP_ROOT/wordcloud-data-$TS.tar.gz" | wc -l          # 能看到条数
sqlite3 "$BACKUP_ROOT/metadata-$TS.db" "select count(*) from jobs;" # 能查到任务数

3.4 本地开发:备份产物

本地数据直接躺在 backend/ 下,copy 出去即可:

cp -r backend/service_workspace backend/service_assets ~/wcd-backup-$TS

4. 第二步:记录当前版本(为了能回滚)

更新前记下“现在跑的是哪一版”,否则回滚都不知道回哪。

  • Docker:记镜像 Digest 或 Tag
    docker inspect wordcloud-backend --format '{{.Image}}  {{.Config.Image}}'
    docker image ls | grep backend
    
  • 代码:记 commit
    git rev-parse HEAD
    

把这些写进 .backups/UPDATE-LOG-$TS.txt

更新前 commit:  abc1234
更新前镜像:     wordcloud-backend:latest  (digest sha256:...)
更新时备份包:   wordcloud-data-20260813_1015.tar.gz

5. 第三步:升级

5.1 Docker 部署(最常见,也最危险)

# 拉/构建新镜像并重建容器
docker compose up -d --build

# 容器重建后,立刻(不要拖)确认关键接口活着
curl -fsS http://localhost:8000/api/health
curl -fsS http://localhost:8000/api/maintenance/storage-summary | jq '{jobs_in_db, events_in_db}'

jobs_in_db 应该是更新前的数字——如果变成 0,说明 service_metadata 没进卷、被重建清掉了 → 走“恢复”那一步。

5.2 本地开发

cd backend && ./start-dev.sh      # 会重新编译 C++、起 uvicorn
# 或前端
cd frontend && npm install && npm run dev

本地 uvicorn --reload 重建的是代码不是数据目录,一般不丢数据,但仍建议更新大改动前备份。


6. 第四步:验证(更新的“可控”就体现在这一步)

升级完不是“能打开首页”就完事,要验证数据层

# 1) 服务健康
curl -fsS http://localhost:8000/api/health

# 2) 数据还在(拿更新前记录的 baseline 对比)
curl -fsS http://localhost:8000/api/maintenance/storage-summary \
  | jq '{job_dir_count, referenced_job_ids, jobs_in_db, events_in_db, reclaimable_bytes}'

# 3) 素材/列表有东西(随便挑一个列表接口)
curl -fsS http://localhost:8000/api/assets | jq 'length'

# 4) 能真正跑一个最小词云任务(端到端冒烟)
curl -fsS http://localhost:8000/api/jobs -X POST \
  -H 'Content-Type: application/json' \
  -d '{"text":"hello world","shape":"rectangle"}' \
  && sleep 3 && curl -fsS http://localhost:8000/api/jobs | jq '.[0]'

通过的标准(缺一不可):

  • jobs_in_db / events_in_db > 0,且与更新前基线一致或更高。
  • 素材列表数量没掉。
  • 冒烟任务能 success。

7. 第五步:回滚(更新失败时)

更新最怕“上了下不来”。回滚分两个层面,先回数据、再回代码。

7.1 Docker:切回旧镜像 + 恢复数据

# 1) 停掉当前
docker compose down

# 2) 恢复文件数据(把备份解开到已挂卷的位置)
#    注意:named volume 可以通过临时容器写回,例如恢复 service_assets
docker run --rm \
  -v wordcloud_assets:/target \
  -v "$(pwd)/.backups":/bak \
  alpine sh -c 'rm -rf /target/* && tar -xzf /bak/wordcloud-data-XXX.tar.gz -C /target service_assets 2>/dev/null; echo done'

# 3) 恢复 SQLite(如果它丢了)
cp .backups/metadata-$TS.db backend/service_metadata/app.db   # 本地路径法
#    容器内恢复:挂载校验后重建容器时会重新读 /app/service_metadata/app.db

# 4) 用旧镜像/旧 tag 拉起
docker compose up -d --build   # 或 docker compose pull 旧 tag 后 up

更省事、更可靠的开销小做法:更新前对卷打快照。 现在有组件还进不了卷时,快照是唯一能把它们一起保住的兜底。

7.2 代码回滚

git checkout <更新前commit>
# 或不改代码,直接复用旧镜像 tag

8. 把“易丢数据”变成“永不丢”的根治建议

上面 runbook 是“治标”——每次更新都备份。要“治本”,把下面两件事做了,第 0 节的红色缺口就消失:

8.1 让 service_metadataservice_orders 进 Docker 卷

docker-compose.ymlbackend volumes 里加两行,并在 volumes: 声明:

services:
  backend:
    volumes:
      - wordcloud_workspace:/app/service_workspace
      - wordcloud_assets:/app/service_assets
      - wordcloud_projects:/app/service_projects
      - wordcloud_design_templates:/app/service_design_templates
      - wordcloud_fonts:/app/service_fonts
      - wordcloud_metadata:/app/service_metadata      # ← 新增
      - wordcloud_orders:/app/service_orders          # ← 新增

volumes:
  wordcloud_workspace:
  wordcloud_assets:
  wordcloud_projects:
  wordcloud_design_templates:
  wordcloud_fonts:
  wordcloud_metadata:                                 # ← 新增
  wordcloud_orders:                                   # ← 新增

注意:service_metadata 当前安装在容器的 /app/service_metadataPROJECT_ROOT/app)。 加上卷后,app.db 会持久化到卷,容器重建不再清零。

8.2 定期备份(不只在更新时才备)

加一个 cron(示例每天 02:17,避开整点):

17 2 * * *  cd /path/to/wordcloud && ./scripts/backup-data.sh

配套的 scripts/backup-data.sh 只需做第 3 步那三件事:打包文件 → sqlite .backup → 校验。


9. 更新风暴口诀

更新五步:备份 → 记版本 → 升级 → 验证数据 → 能回滚。 最危险的不是 docker compose down,而是重建容器;重建前看卷、重建后看 jobs_in_db


10. 相关文件

  • docker-compose.yml:数据卷挂载(改这里补缺口)
  • backend/service/metadata_store.pyservice_metadata/app.db 的来源
  • backend/service/storage_metrics.py:任务清理审计(dry-run → --apply
  • docs/DEPLOYMENT.md:部署与产物目录清单
  • docs/DATA_STORAGE_OPTIMIZATION.md:任务存储现状与清理策略