数据库迁移与版本管理
本页描述 Chameleon 当前的 Alembic 迁移链路、执行方式与团队协作规范。
总览
项目使用 Alembic 管理 PostgreSQL 模式演进:
- 配置入口:
alembic.ini - 迁移环境:
ai_service/migrations/env.py - 版本脚本目录:
ai_service/migrations/versions/
env.py 会按顺序加载项目根目录的 .env 与 .env.local,再从环境变量读取 DATABASE_URL 作为迁移连接字符串。
运行时迁移策略
默认采用“开发自动,staging/production 手动”策略:
APP_ENV=development:默认DB_MIGRATION_MODE=auto,应用启动前自动执行uv run alembic upgrade head。APP_ENV=staging/production:默认DB_MIGRATION_MODE=manual,应用启动时不会自动迁移。- 可通过环境变量覆盖默认值:
DB_MIGRATION_MODE=auto|manual。
当前仓库的 Dokploy 发布链路会将 staging/production 的 DB_MIGRATION_MODE 写为 auto,因此迁移在 ai-service 容器启动阶段执行,而不是在 GitHub Actions 中单独执行。
空库(brand-new database)行为说明:
- 当前 revision 链主要用于兼容历史库增量演进;在核心业务表不存在时会安全 no-op。
- 随后应用启动事件会执行
init_database()(Base.metadata.create_all)创建当前模型所需全量表结构。 - 因此首次部署到全新 PostgreSQL 时,
alembic upgrade head不应再因缺表而失败。 - 每个 revision 都必须显式声明
EMPTY_DATABASE_POLICY: fresh_safe:可直接安全运行在空库上。legacy_compatible_noop:历史兼容迁移,缺少锚点表时必须安全短路。- 对
legacy_compatible_noop类型的 migration,统一通过ai_service/migrations/helpers.py中的MigrationSchemaGuard做表/列/索引存在性判断, 不再重复手写本地_has_table()/_has_column()逻辑。
生产发布建议:
- 确保部署环境配置
DB_MIGRATION_MODE=auto,并保证容器内DATABASE_URL可连通目标数据库。 - 对高风险迁移优先采用单实例滚动或维护窗口发布,避免多个实例并发启动同时触发迁移。
当前迁移链
截至当前仓库版本,主迁移链在 b1e350f626b1 之后再次分叉,并已在
fa12c4d6e8b9 重新收敛为单一 head。当前末端链路如下:
flowchart LR
V1[d9b7a1c4e5f6]
V2[aa91c2d4e6f7]
V3[b1e350f626b1]
V4[7d1e3c9b4a6f]
V5[2c6d9a4e8f11]
V6[e8c4a1b2d3f6]
V7[2a6f4c8d9b1e]
V8[fa12c4d6e8b9]
V1 --> V3
V2 --> V3
V3 --> V4 --> V5 --> V8
V3 --> V6 --> V7 --> V8
如需确认运行时的实际 head,以 uv run alembic heads 输出为准。
关键节点
| Revision | down_revision | 主要变更 |
|---|---|---|
58d932c51f0c |
None |
Alembic 基线 revision |
69fb951503de |
f1a9c3e8b472, f6a9b2c3d4e5 |
合并 skill_runtime 与 agent_versions 双分支 |
b1e350f626b1 |
aa91c2d4e6f7, d9b7a1c4e5f6 |
合并 grounding config 与 MCP/token statistics 双分支 |
e1f6c2b7a9d4 |
2a6f4c8d9b1e, 2c6d9a4e8f11 |
合并 encrypted_api_key_json 与 agent usage limit 双分支 |
fa12c4d6e8b9 |
e1f6c2b7a9d4 |
新增 Agent public access control 与 Fusion Agent API key support,恢复单一 head |
8c4d2e1f9b7a |
c3b8a1d4e5f6 |
新增 agent message type / response 配置 |
2f7c5e1d9a4b |
8c4d2e1f9b7a |
新增 orchestrator_key 并回填历史 chat agent |
b4d0f1a2c3e5 |
a7f4c2d9e1b3 |
新增 hide_rag_source_filename,同步到 agents / agent_versions 并默认保留原始 provenance |
常用命令
先确保环境变量可用,尤其是 DATABASE_URL 或 POSTGRES_* 组合配置。
# 查看仓库中的 head revision
uv run alembic heads
# 查看当前数据库版本
uv run alembic current
# 升级到最新版本
uv run alembic upgrade head
# 回滚一个版本
uv run alembic downgrade -1
# 仅同步 alembic_version,不执行真实 DDL
uv run alembic stamp <target_revision>
# 基于模型变更生成迁移脚本
uv run alembic revision --autogenerate -m "describe change"
镜像回滚时的版本错位应急
当部署链路使用 DB_MIGRATION_MODE=auto 时,新镜像会在启动阶段执行
uv run alembic upgrade head。如果之后只回滚镜像、没有同步处理数据库,
就可能出现下面的错位:
- 数据库中的
alembic_version已经指向新 revision - 回滚后的旧镜像不再包含该 revision 文件
- 旧镜像启动时无法解析当前数据库版本,报错
Can't locate revision identified by '<new_revision>'
推荐按以下顺序处理。
1. 先恢复服务可用性
如果旧镜像能够兼容“更前进”的数据库 schema,优先将回滚目标环境的
DB_MIGRATION_MODE 临时改为 manual,然后重新拉起旧镜像。
这样旧镜像启动时不会再执行 alembic upgrade head,可先避开 Alembic
版本解析失败。这个方案适合“旧代码通常能容忍新增列,但不能容忍未知 revision”
的场景。
2. 再决定是否同步数据库版本
在可以访问目标数据库的环境中执行:
uv run alembic heads
uv run alembic current
然后根据目标旧镜像的 revision 选择下面两种方式之一。
方案 A:只校准 Alembic 版本号
当旧镜像可以兼容当前 schema,只是 alembic_version 指向了旧镜像不认识的
revision 时,优先使用:
uv run alembic stamp <target_revision>
stamp 只会更新 alembic_version,不会删除列、索引或表,适合生产应急。
方案 B:真实执行数据库回退
只有在确认目标迁移脚本的 downgrade() 可执行、且回退不会误删仍然需要的数据时,
才使用:
uv run alembic downgrade <target_revision>
此方案会执行真实 DDL。若 migration 的 downgrade() 包含 drop_column、
drop_table 或数据清理语句,回退后可能无法恢复。
3. 恢复后的运行建议
- 若只是为了临时恢复旧镜像,建议在问题彻底定位前继续保持
DB_MIGRATION_MODE=manual - 若已通过
stamp或downgrade让数据库与旧镜像 revision 对齐,再决定是否恢复auto - 任何需要长期保留的回滚方案,都应明确“镜像回滚是否同时要求数据库回退”
团队协作规范
1. 变更模型必须配套迁移
涉及以下文件变更时,应同时提交 Alembic revision:
ai_service/storage/models.pyai_service/storage/model_domains/*.py- 与表结构强耦合的服务逻辑或 API 字段
2. 迁移脚本应可读可回滚
upgrade()和downgrade()都必须可执行。- 对线上敏感变更应拆分为多步小迁移,避免一次性高风险改动。
3. 空库策略必须显式声明
- 新建 revision 时,必须保留模板里的
EMPTY_DATABASE_POLICY常量,不允许省略。 - 如果 migration 依赖旧表、旧列或旧索引,必须改成
EMPTY_DATABASE_POLICY_LEGACY_COMPATIBLE_NOOP,并使用MigrationSchemaGuard.from_alembic(...)做 schema guard。 tests/integration/test_migration_empty_database_noop.py会自动扫描所有 revision:- 检查是否声明了受支持的
EMPTY_DATABASE_POLICY - 对
legacy_compatible_nooprevision 执行空库 no-op 回归测试
4. 与文档保持同步
修改数据模型后,请至少同步以下文档:
docs/database/schema.mddocs/database/migrations.md- 若 API 字段变化,同步
docs/guides/rag-knowledge-management.md或docs/api/endpoints.md
变更检查清单
在提交与数据库相关的 PR 前,建议逐项确认:
- 已生成并提交迁移脚本
- 本地执行
uv run alembic upgrade head成功 - 本地执行
uv run pytest tests/integration/test_migration_empty_database_noop.py -q成功 - 关键回滚路径可用
uv run alembic downgrade -1 - 文档导航与内容已同步