Immich 数据库迁移全链路:从 generate 到 revert 的 5 个关键动作
【免费下载链接】OpenCore-Legacy-PatcherExperience macOS just like before项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher
在 Immich 里做数据库迁移,最容易翻车的场景不在单分支,而在合并:两个分支各自新增一个迁移文件,合并后服务重启,Postgres 报 DDL 错误,进程直接挂掉。文件时间戳互不冲突,字典序也排得整整齐齐,但两个分支"静默地"以错误顺序合并了——后执行的 DDL 依赖的表根本还不存在。这个问题在合并前无法被肉眼发现,因为目录里只有带时间戳的文件,没有任何显式的顺序约定。
答案是一份被 git 跟踪的 ORDER 清单(server/src/schema/migrations/ORDER):每个迁移文件名去掉 .ts 后缀后占清单一行,新迁移必须显式登记进去。两个分支各自新增迁移时,冲突会落在清单文件上,强制开发者亲手决定先后顺序。这是用冲突噪音换顺序确定性的设计。
sql-tools 在中间做了什么
Immich 服务端的表结构全部用 TypeScript 声明,位于 server/src/schema,分三部分:tables/(声明式 API 描述的表定义,约 64 个文件,描述"数据库应该长什么样")、enums.ts 与 functions.ts(枚举与数据库函数)、migrations/(按时间戳排序的迁移文件加 ORDER 清单)。声明定义本身不动数据库,真正执行变更的是迁移文件里的 up() 函数——把已有数据库改成目标形态的执行单元。
两侧由 @immich/sql-tools(Immich 自研的迁移工具链,下文简称 sql-tools)桥接:它比对声明式 schema 与真实数据库的差异,自动生成迁移 SQL,再按 ORDER 清单顺序执行。
一次真实迁移(AddUserAvatarColorColumn:给 users 表加列,并把存量数据从 JSON 元数据回填到新列)长这样:
export async function up(db: Kysely<any>): Promise<void> { await sql`ALTER TABLE "users" ADD "avatarColor" varchar;`.execute(db); // 随后 UPDATE:把存量数据从 user_metadata 的 JSON 回填到新列 } export async function down(db: Kysely<any>): Promise<void> { await sql`ALTER TABLE "users" DROP COLUMN "avatarColor";`.execute(db); }迁移文件统一命名为 <毫秒时间戳>-<PascalCase 名称>.ts,从 1744910873969-InitialMigration 排到最新的业务迁移。时间戳前缀保证同目录内字典序即执行序——改动前缀等于篡改执行历史。
另有一类 up/down 都是空操作的占位迁移文件,它的存在只是为了维持 ORDER 清单与磁盘文件的一一对应,审阅时不必纠结内容。
服务启动流程本身就包含"运行所有未应用的迁移",开发环境重启 server 后新迁移会自动落到本地库,无需手动 run。
改 → 验 → 落 → 提交
改:声明先行,generate 出差异 DDL
先改 server/src/schema/tables 里的声明式定义,再让 sql-tools 比对差异、生成迁移:
# 前置:本地 Docker 已跑起 Postgres,默认连接 localhost:5432/immich,可用 DB_URL 覆盖 mise //server:migrations generate AddUserAvatarColorColumnmise 是仓库统一的开发任务运行器,//server:前缀表示在 monorepo 根目录执行 server 包任务,实际展开为 sql-tools -u <连接串> migrations generate。生成文件带时间戳前缀、落在 server 目录下,还不算最终产物。
验:人眼过一遍 up 与 down
整条链路里最容易踩坑的其实是这一步——机器生成的 DDL 只保证结构差异,不保证业务正确。核对三件事:
- DDL 是否符合预期(列类型、默认值、索引);
- down() 是否真的可安全回退(加列好回退,改类型丢数据);
- 数据回填逻辑有没有漏(存量数据是否被搬进新结构)。
落:移入 migrations 目录并在本地库应用
把生成文件手动移入 server/src/schema/migrations——该目录当前有 97 个迁移文件,命名全部是统一的时间戳格式——然后应用到本地库看结果:
# 前置:迁移文件已移入 migrations/;本地 Postgres 可达 mise //server:migrations run开发环境里也可以靠 server 重启自动应用,run 是手动等价物。
提交:sync-order 与 verify-order 一起过
先登记顺序,再提交:
# 前置:迁移文件已在 migrations/ 内且本地验证通过 mise //server:migrations sync-order # 把新迁移追加进 ORDER 清单 mise //server:migrations verify-order # 校验磁盘文件与清单完全一致迁移文件与 ORDER 必须同一提交进 git。漏掉 sync-order,verify-order 会在 CI 的 checklist 里被拦下来。
出事了怎么办
先定位,再动手。schema-check 是服务内置命令(实现见 server/src/commands/schema-check.ts),它把每个迁移归为三态:
- applied:已应用,正常路径;
- deleted:数据库里已应用,磁盘上文件不见了;
- missing:磁盘上有,还没应用到数据库。
"漂移"指磁盘声明与数据库实际状态之间的偏离。检测到漂移时,命令会列出漂移项并附一段自动生成的修复 SQL——源码标注 "Use at your own risk!",执行前必须人工确认。
三个处置手段:
| 命令 | 适用场景 | 生产环境 |
|---|---|---|
mise //server:migrations revert | 执行最新一条迁移的 down(),验证回滚逻辑是否可逆 | ⚠️ 禁止 |
schema-check(服务命令) | 核对三态,输出漂移清单与修复 SQL | ⚠️ 禁止 |
schema-drop+schema-reset(server/mise.toml 任务) | 本地库与迁移历史脱节时:DROP SCHEMA public CASCADE 后按 ORDER 重放全部迁移,会清空数据 | ⚠️ 禁止 |
revert 适合验证 down 逻辑;schema-reset 是"清空后重放全部 97 个迁移",本地状态彻底混乱(手工改过表、误删过迁移文件)时用它恢复最快。以上命令一律不要指向生产库,生产环境的回滚应走备份恢复,而不是迁移回退。
速查
以下命令都假设本地可达的 Postgres(localhost:5432/immich 或 DB_URL),//server:前缀表示在 monorepo 根目录执行。server 目录内也可用等价的 npm scripts(migrations:generate 等,见 server/package.json)。
| 命令 | 作用 |
|---|---|
mise //server:migrations create <name> | 创建空迁移骨架(up/down 占位) |
mise //server:migrations generate <name> | 比对声明式 schema 与数据库,自动生成迁移 DDL |
mise //server:migrations run | 执行所有未应用的迁移 |
mise //server:migrations revert | 回滚最近一次迁移(执行其 down) |
mise //server:migrations sync-order | 把新迁移登记进 ORDER 清单 |
mise //server:migrations verify-order | 校验清单与磁盘文件一致,CI checklist 会执行 |
【免费下载链接】OpenCore-Legacy-PatcherExperience macOS just like before项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考