news 2026/9/9 15:04:16

Immich 数据库迁移全链路:从 generate 到 revert 的 5 个关键动作

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Immich 数据库迁移全链路:从 generate 到 revert 的 5 个关键动作

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 AddUserAvatarColorColumn

mise 是仓库统一的开发任务运行器,//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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 15:03:37

从零实现Rollup:以太坊Layer2扩容原理与工程实践

如果你在2021年那轮行情里用以太坊做过转账&#xff0c;一定记得一次简单Transfer动辄几十美元Gas费的酸爽。我后来做链上数据分析工具时&#xff0c;发现不少项目已经开始把业务从主网迁移到Layer 2&#xff0c;而Layer 2赛道里最被主流认可的方案就是Rollup。这篇文章不打算重…

作者头像 李华
网站建设 2026/9/9 15:02:57

光储充换电站优化调度:用户负荷与分时电价互动建模及Matlab实现

光储充换电站的优化调度&#xff0c;我去年下半年花了挺长时间在折腾这个方向。最近有位读者给我发来一个复现需求&#xff0c;标题很长&#xff1a;"考虑用户充电负荷与最优分时电价互动的光储充换电站优化模型研究"&#xff0c;要求用Matlab代码实现。拆开看其实就…

作者头像 李华
网站建设 2026/9/9 15:02:47

JMeter数据服务性能基准测试实战:从脚本编写到结果分析

JMeter这个东西&#xff0c;我在数据服务性能测试里用了好多年了。说实话&#xff0c;提起性能基准测试&#xff0c;很多人第一反应是上LoadRunner&#xff0c;或者直接写脚本用wrk、ab去压。但如果你测的是数据服务——不管是内部REST API、微服务网关&#xff0c;还是某种数据…

作者头像 李华
网站建设 2026/9/9 15:01:38

LabVIEW集成OCR实现文字识别:从选型到落地全攻略

1. 测试现场的真实痛点&#xff1a;LabVIEW凭什么要"会认字"1.1 一个产线追溯场景的具体画像我接过一个不算复杂但很典型的项目&#xff1a;一条组装线上的工位需要把产品侧面的序列号拍下来&#xff0c;和MES系统里的订单号做比对&#xff0c;对上就放行&#xff0c…

作者头像 李华
网站建设 2026/9/9 15:01:25

LLM 推理基础设施规划:GPU 选型、容量设计与成本优化

这里写自定义目录标题欢迎使用Markdown编辑器一、为什么推理基础设施规划如此困难二、第一步&#xff1a;明确你的工作负载类型三、第二步&#xff1a;用六个维度量化需求四、第三步&#xff1a;GPU 选型与容量计算五、第四步&#xff1a;本地与云端的容量组合六、第五步&#…

作者头像 李华
网站建设 2026/9/9 15:01:17

如何 5 分钟用 Docker 部署 Hermes WebUI:三种容器模式完整指南

如何 5 分钟用 Docker 部署 Hermes WebUI&#xff1a;三种容器模式完整指南 【免费下载链接】hermes-webui Hermes WebUI: The best way to use Hermes Agent from the web or from your phone! 项目地址: https://gitcode.com/GitHub_Trending/he/hermes-webui 想把 He…

作者头像 李华