Files
backend/docs/17-生产数据迁移.md
T
34047007@qq.com 3c85ded216
CI / backend (push) Waiting to run
CI / frontend (push) Waiting to run
feat: 生产部署准备 — 构建加速 + 24迁移链分批脚本 + 搜索性能优化
- Dockerfile 腾讯源 + BuildKit 缓存(backend pip / frontend npm ci),gitea 锁 1.27.1
- .dockerignore 纳入版本管理(防密钥进镜像)
- 新增 migrate_prod.sh:24 个迁移分 4 批执行,含镜像新鲜度 + DB 起点校验
- cb07d6b1df01 移除 search_tsv 回填(延迟到 g0h1i2j3k4l5 统一全量回填)
- 模型 journal_iso/volume/issue/pages → Text(对应迁移 95c18ebf31e4 / 55105f0bb1d7)
- 搜索优化:tsvector 主路径 + COUNT 截断(10000) + pub_date 排序,admin 聚合单查询
- statement_timeout 30s→60s
2026-08-10 00:33:16 +08:00

10 KiB
Raw Blame History

17. 生产数据迁移实施方案

日期: 2026-08-09 版本: v1.0 关联: 16-部署运维方案.mddeploy.sh 部署流程)、10-生产部署文档.md(操作手册) 状态: 待执行——生产 DB 落后本地 24 个迁移,本方案拆分 4 批,大数据量批次延后手工执行


1. 背景与现状

生产服务器(123.207.9.209)的数据库 schema 停留在 2026-07-17alembic head = 6b662a8c5235),而本地开发代码已演进到 2026-07-30head = 55105f0bb1d7),中间有 24 个迁移待执行

关键数据规模(影响迁移耗时与锁表风险):

  • global_literature123 万行
  • global_literature_tags348 万行
  • global_tags2.1 万行

⚠️ 核心约束: 24 个迁移形成线性链(01→24 顺序依赖),无法跳过任何一个直接跑到 head。所以"分开做"= 把链切成 4 段,每段执行到中间 checkpoint,大数据量/锁表操作集中在后段,由人工控制执行时机。


2. 迁移链全景(24 个迁移,生产 head → 本地 head)

# Revision 内容 数据量/风险
01 cb07d6b1df01 19 列 JSON→JSONB(全表重写)+ 3 新列 + entry_terms + search_tsv 触发器重建 + 8 索引(B-tree×4 + GIN×4 🔴 重:全表重写
02 6492b887b779 meshed_date 列 + UPDATE 回填(date_completed 非空行) 🟠 中:全表回填
03 1421ea169bb6 5 个日期字段 TIMESTAMPTZ→DATE 🔴 重:锁表
04 f3b135d62407 pipeline_runs.processed_date 🟢
05 0ee585329fc6 pipeline_runs.metadata 🟢
06 db1cc822f2da global_journals.nlm_subsets 🟢
07 34bc08516f3a global_literature.is_preprint 🟢
08 d641e2f7a4ee auid_data / cois_statement / vernacular 相关 🟢
09 52ec204acfd9 pharmacological_actions JSONB 🟢
10 38bb4e1f8498 investigators / personal_name 🟢
11 cac545862583 新建 user_saved_filters 表 🟢
12 d8f6c3563587 筛选列 B-tree×4 + mesh_headings GIN 🟠 中:大表建索引
13 a4b7c8d9e0f1 journal_iso trgm GIN 🟠 中:大表建索引
14 e5f6a7b8c9d0 search_tsv 触发器函数(作者 simple 词典) 🟢
15 d9e7c8b1a2f3 authors trgm GIN 🟠 中:大表建索引
16 e0f1a2b3c4d5 search_tsv 触发器重建(+chemical/gene,无回填) 🟢
17 f1a2b3c4d5e6 author_names_text 列 + UPDATE 回填 + trgm 索引 🔴 重:全表回填
18 g0h1i2j3k4l5 search_tsv 全量回填(+mesh/keywords+ 触发器 🔴 重:全表回填
19 e0764f6d7c21 tag_ids ARRAY + BRIN×2 + 部分索引×4 + tag_ids GIN 🟠 中:大表建索引
20 b01b8f27c596 tag_ids 回填348 万行 array_agg 聚合) 🔴 重:大数据量回填
21 af4a8b2ec873 search_tsv 全量回填(修复 mesh_headings key+ 触发器 🔴 重:全表回填
22 d166cde6083b volume/issue VARCHAR(50)→VARCHAR(200) 🟢
23 95c18ebf31e4 volume/issue VARCHAR(200)→Text 🔴 重:锁表
24 55105f0bb1d7 journal_iso/pages VARCHAR(100)→Text 🔴 重:锁表

3. 分段原则

  1. 链是线性的(01→24),不能跳段;分段 = 每批执行到中间 checkpoint,不改变迁移内部顺序。
  2. 大数据量放后边🔴 重活集中在批次 1(链头,无法避免,见下)和批次 4(全部延后)。
  3. 新代码依赖前置 schema:批次 1 的 JSON→JSONB / 日期类型变更,本地模型代码已经按 JSONB/Date 定义——在批次 1 完成前,新代码无法运行。这是必须最先执行、无法延后的部分。
  4. 批次 4 = 最重:含 348 万行 tag_ids 回填 + 3 次全量 search_tsv 回填 + 2 次锁表类型变更,全部延后到深夜低峰手工跑

4. 批次划分与目标 Checkpoint

批次 覆盖迁移 目标 checkpoint 脚本参数 内容摘要 建议时段
1 [01][03] 1421ea169bb6 ./migrate_prod.sh 1 JSON→JSONB 全表重写 + meshed_date 回填 + 日期锁表 ⚠️ 必须与发新代码同窗口(含 destructive,见下)
2 [04][11] cac545862583 ./migrate_prod.sh 2 纯加列/新表,additive 可提前,任意时段
3 [12][16] e0f1a2b3c4d5 ./migrate_prod.sh 3 索引(大表建)+ 触发器重建,additive 可提前,建议低峰
4 [17][24] head55105f0bb1d7 ./migrate_prod.sh 4 大数据量回填 + 类型锁表(含 destructive 新代码已部署后,深夜低峰

⚠️ destructive 窗口纪律(2026-08-10 修正,防止批次 1 单独跑崩老代码):

  • 批次 1 含 1421ea169bb65 日期字段 TIMESTAMPTZ→DATE)——类型收窄,属 destructive。老代码把 date_completed/pubmed_revised 等读成 datetime,迁移后 DB 返回 datedatetime 专属调用会崩。因此批次 1 绝不能脱离代码更新单独跑——必须和 build 新镜像 + 发新代码同一维护窗口(迁移完几秒内新镜像接管),或新代码先发再跑批次 1(但新代码依赖 JSONB,见 §3 约束,建议同窗口)。
  • 批次 2/3 纯 additive(加列/新表/索引/触发器重建),老代码完全兼容,可在部署前任意时段提前跑,缩小维护窗口。
  • 批次 4 含 95c18ebf31e4/55105f0bb1d7VARCHAR→Text——类型加宽、老代码兼容,但按 destructive 判定仍算;必须在新代码已部署后、深夜低峰跑(大数据量回填 + 锁表)。
  • 推荐执行序:批次 2/3(提前)→ 维护窗口:build 新镜像 → 批次 1 → 发新 backend/worker/frontend → 深夜:批次 4。批次 1 与发代码之间的窗口必须控制在分钟级内。

5. 前置条件(执行迁移前必须完成)

以下由部署流程(docs/16 §2 deploy.sh)或人工准备:

  1. 新 backend 镜像已构建(必须包含 24 个新迁移文件)。生产镜像当前是 07-17 旧版,用旧镜像跑迁移会报 Can't locate revision '6b662a8c5235'2026-08-09 事故根因)。验证方式:
    docker compose -f docker-compose.prod.yml build backend
    

    构建加速已落地(2026-08-10): backend Dockerfile 已加腾讯 pip/apt 源 + BuildKit 缓存,frontend Dockerfile.prod 已加腾讯 npm 源 + npm ci首次 build 约几分钟pip 全量从腾讯源装),之后代码级 build 秒~1 分钟(只 COPY . .)。构建前提:宿主机 daemon 已配腾讯 registry-mirrors 已配)、BuildKit 需 Docker 22.06+/23.05+ 29.6.1)。

  2. ⚠️ 批次 1 必须与发新代码同窗口(见 §4 destructive 纪律):批次 1 的日期收窄迁移会让老代码读崩,不能提前单独跑。批次 2/3 可提前 additive 跑。
  3. 数据库快照(安全网,回滚用):部署前拍全量 pg_dump -Fc
  4. postgres 容器 healthy
  5. 脚本位于 /root/scilit/(与 docker-compose.prod.yml 同目录)。

6. 实施步骤(生产服务器手工执行)

6.1 脚本位置与用法

脚本:backend/scripts/migrate_prod.sh(已随代码提交,部署时同步到 /root/scilit/

cd /root/scilit
chmod +x migrate_prod.sh          # 首次
./migrate_prod.sh 1               # 批次 1
./migrate_prod.sh 2               # 批次 2
./migrate_prod.sh 3               # 批次 3
./migrate_prod.sh 4               # 批次 4(最重,深夜)

脚本内置安全网:

  • 镜像新鲜度检查:执行前确认 backend 镜像含本地 head55105f0bb1d7),否则报错并给出修复指引(防 08-09 事故重演)。
  • 起点校验:检查 DB 当前 revision 是否等于该批次的预期起点,防止乱序/重复。
  • 幂等DB 已在目标 checkpoint 时直接跳过。
  • 退出码门控:迁移命令失败(非 0)即中止,set -euo pipefail

6.2 每个批次的验证点

# 执行后确认当前版本
docker compose -f docker-compose.prod.yml run --no-deps --rm backend alembic current

预期结果(逐批):

批次 执行后 alembic current
1 1421ea169bb6
2 cac545862583
3 e0f1a2b3c4d5
4 55105f0bb1d7 (head)

7. 注意事项

  1. 执行序(destructive 窗口纪律)批次 2/3(additive)可提前跑 → 维护窗口内:build → 批次 1 → 发新代码 → 深夜:批次 4。批次 1(日期收窄)与发代码必须同窗口(分钟级内衔接),批次 4(大数据量回填 + Text 加宽)须在新代码已部署后深夜跑。
  2. 锁表窗口:批次 1(日期锁表)、批次 4volume/issue/pages→Text 锁表)期间,对 global_literature 的写入被阻塞。建议选业务低峰。123 万行上的回填(02/17/18/20/21)预计每步数秒到数分钟,总计约 10-20 分钟。
  3. 批次 4 完成后,DB 即升级到本地最新 head——代码与 schema 完全对齐。批次 4 未跑时新代码可运行,但 tag_ids 为空、搜索 tsvector 覆盖不全(回填数据缺失),功能受限但不报错。
  4. 失败处理:若某批次迁移失败,脚本退出非 0;DB 停在失败前一个 revision,可重跑该批次(alembic 只应用未执行的迁移)。涉及数据回退时用 §5 快照(pg_restore --clean --if-exists,见 docs/16 §2 rollback.sh)。

8. 回滚预案

  • 迁移失败 / 需回退数据:使用部署前快照 pg_restore --clean --if-exists --no-owner -d scilit <快照> 恢复(docs/16 §2 rollback.sh 完整流程)。
  • 代码回滚:换回旧镜像 tag + up -d --no-depsdocs/16 §1 双 tag 原子回滚)。
  • 迁移本身是单向的alembic 各迁移均有 downgrade(),但破坏性迁移(类型变更/回填)downgrade 不恢复数据——正式回滚走快照,不走 downgrade。

9. 执行记录

日期 批次 执行结果 备注
(待填) 1
(待填) 2
(待填) 3
(待填) 4