- 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
10 KiB
17. 生产数据迁移实施方案
日期: 2026-08-09 版本: v1.0 关联: 16-部署运维方案.md(deploy.sh 部署流程)、10-生产部署文档.md(操作手册) 状态: 待执行——生产 DB 落后本地 24 个迁移,本方案拆分 4 批,大数据量批次延后手工执行
1. 背景与现状
生产服务器(123.207.9.209)的数据库 schema 停留在 2026-07-17(alembic head = 6b662a8c5235),而本地开发代码已演进到 2026-07-30(head = 55105f0bb1d7),中间有 24 个迁移待执行。
关键数据规模(影响迁移耗时与锁表风险):
global_literature:123 万行global_literature_tags:348 万行global_tags:2.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. 分段原则
- 链是线性的(01→24),不能跳段;分段 = 每批执行到中间 checkpoint,不改变迁移内部顺序。
- 大数据量放后边:🔴 重活集中在批次 1(链头,无法避免,见下)和批次 4(全部延后)。
- 新代码依赖前置 schema:批次 1 的 JSON→JSONB / 日期类型变更,本地模型代码已经按 JSONB/Date 定义——在批次 1 完成前,新代码无法运行。这是必须最先执行、无法延后的部分。
- 批次 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] | head(55105f0bb1d7) |
./migrate_prod.sh 4 |
大数据量回填 + 类型锁表(含 destructive) | 新代码已部署后,深夜低峰 |
⚠️ destructive 窗口纪律(2026-08-10 修正,防止批次 1 单独跑崩老代码):
- 批次 1 含
1421ea169bb6(5 日期字段 TIMESTAMPTZ→DATE)——类型收窄,属 destructive。老代码把date_completed/pubmed_revised等读成 datetime,迁移后 DB 返回date,datetime专属调用会崩。因此批次 1 绝不能脱离代码更新单独跑——必须和 build 新镜像 + 发新代码同一维护窗口(迁移完几秒内新镜像接管),或新代码先发再跑批次 1(但新代码依赖 JSONB,见 §3 约束,建议同窗口)。- 批次 2/3 纯 additive(加列/新表/索引/触发器重建),老代码完全兼容,可在部署前任意时段提前跑,缩小维护窗口。
- 批次 4 含
95c18ebf31e4/55105f0bb1d7(VARCHAR→Text)——类型加宽、老代码兼容,但按 destructive 判定仍算;必须在新代码已部署后、深夜低峰跑(大数据量回填 + 锁表)。- 推荐执行序:批次 2/3(提前)→ 维护窗口:build 新镜像 → 批次 1 → 发新 backend/worker/frontend → 深夜:批次 4。批次 1 与发代码之间的窗口必须控制在分钟级内。
5. 前置条件(执行迁移前必须完成)
以下由部署流程(docs/16 §2 deploy.sh)或人工准备:
- 新 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)。 - ⚠️ 批次 1 必须与发新代码同窗口(见 §4 destructive 纪律):批次 1 的日期收窄迁移会让老代码读崩,不能提前单独跑。批次 2/3 可提前 additive 跑。
- 数据库快照(安全网,回滚用):部署前拍全量
pg_dump -Fc。 - postgres 容器 healthy。
- 脚本位于
/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 镜像含本地 head(
55105f0bb1d7),否则报错并给出修复指引(防 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. 注意事项
- 执行序(destructive 窗口纪律):批次 2/3(additive)可提前跑 → 维护窗口内:build → 批次 1 → 发新代码 → 深夜:批次 4。批次 1(日期收窄)与发代码必须同窗口(分钟级内衔接),批次 4(大数据量回填 + Text 加宽)须在新代码已部署后深夜跑。
- 锁表窗口:批次 1(日期锁表)、批次 4(volume/issue/pages→Text 锁表)期间,对
global_literature的写入被阻塞。建议选业务低峰。123 万行上的回填(02/17/18/20/21)预计每步数秒到数分钟,总计约 10-20 分钟。 - 批次 4 完成后,DB 即升级到本地最新 head——代码与 schema 完全对齐。批次 4 未跑时新代码可运行,但
tag_ids为空、搜索 tsvector 覆盖不全(回填数据缺失),功能受限但不报错。 - 失败处理:若某批次迁移失败,脚本退出非 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-deps(docs/16 §1 双 tag 原子回滚)。 - 迁移本身是单向的:alembic 各迁移均有
downgrade(),但破坏性迁移(类型变更/回填)downgrade 不恢复数据——正式回滚走快照,不走 downgrade。
9. 执行记录
| 日期 | 批次 | 执行结果 | 备注 |
|---|---|---|---|
| (待填) | 1 | ||
| (待填) | 2 | ||
| (待填) | 3 | ||
| (待填) | 4 |