Openship 数据库层完整解析:Drizzle ORM 与 Postgres/PGlite 内嵌数据库迁移指南
【免费下载链接】openshipSelf-hosted deployment platform项目地址: https://gitcode.com/GitHub_Trending/ope/openship
🚀Openship是一款自托管部署平台(Self-hosted Deployment Platform),而支撑它的全部数据能力,就是位于 packages/db/ 下的数据库层。这一层以Drizzle ORM为核心,同时支持两种数据库驱动:生产级的Postgres,以及零配置即可运行的PGlite 内嵌数据库(WASM 版 PostgreSQL,无需安装任何服务)。所有数据库迁移(Migration)在应用启动时自动执行,迁移链已从0000一路演进到0116。本文将带你快速看懂:驱动如何选择、迁移链如何自动运行、PGlite 如何避免数据损坏,以及数据导出恢复怎么做。
一图看懂:为什么部署平台需要「双驱动」数据库层
上面就是 Openship 的控制台界面。界面上的每一个项目、部署记录、域名、服务器和通知,背后都对应数据库中的一张表。
Openship 的数据库层解决了一个典型矛盾:
- 开发者体验:希望
clone仓库就能跑起来,不想先装 Docker 和 Postgres; - 生产部署:希望用标准的 Postgres,获得并发、备份和运维工具链的完整能力。
解决方案就是一套接口,两种驱动:
| 场景 | 驱动 | 数据存放位置 |
|---|---|---|
| 生产 / Docker 自托管 | node-postgres 连接真实 Postgres | 你的 Postgres 服务 |
| 本地开发 / 桌面端 | PGlite 内嵌数据库 | 默认~/.openship/data,可用PGLITE_DATA_DIR指定 |
选择逻辑很简单:配置了DATABASE_URL(或POSTGRES_HOST/POSTGRES_PASSWORD等变量)就连 Postgres;否则自动降级为 PGlite,真正做到零配置启动。
统一接口:上层代码不关心底层是哪个数据库
数据库客户端的工厂逻辑集中在 client.ts 中:
postgresql://开头的连接串 → 创建node-postgres 连接池 + Drizzle实例(连接池上限 20,见 client.ts);- 空或未配置 → 创建PGlite 内嵌数据库实例。
两种驱动最终都归一为同一个Database类型,所有仓库层(Repo)和服务拿到的是同一个接口,完全不知道底层跑的是哪种数据库。这也是 Drizzle ORM 的价值:类型安全 + 一套查询语法覆盖两种后端。
📌 小细节:测试环境下(VITEST),PGlite 会自动切换到内存模式memory://,保证测试永不污染磁盘上的开发数据——见 client.ts。
迁移链解析:从 0000 到 0116 的自动演进
Openship 的迁移文件全部存放在 packages/db/drizzle/ 目录,目前已积累117 个迁移 SQL 文件:
- 起点:0000_init.sql —— 初始建表;
- 近期演进示例:0084_analytics_daily_rollup.sql(分析日汇总)、0108_credential.sql(凭据表)、0116_domain_ssl_challenge.sql(域名 SSL 验证)。
迁移如何自动运行?无论使用哪种驱动,应用启动时都会调用 Drizzle 的migrate(),把packages/db/drizzle/目录里尚未执行的迁移按顺序、每条一个事务地应用完——见 client.ts(Postgres 驱动)与 client.ts(PGlite 驱动)。
开发者的日常工作流只需三步:
- 修改表定义(
packages/db/src/schema/下对应文件); - 运行
db:generate脚本(基于 drizzle-kit,配置见 drizzle.config.ts),生成新的迁移 SQL; - 提交迁移文件,重启应用——迁移自动完成。
⚠️ 特别提醒:迁移目录通过OPENSHIP_MIGRATIONS_DIR环境变量可被重定向。桌面端 App 是bun build --compile编译的单体二进制,SQL 文件不在文件系统里,而是作为数据资源随二进制一起发布,这个变量就是为它准备的。
领域化 Schema:49 个文件,一个业务域一个表集合
表定义不是一团乱麻,而是按业务域拆分成 packages/db/src/schema/ 下的近 50 个文件,并在 index.ts 统一导出:
- 部署核心:project.ts、deployment.ts、service.ts
- 网络与边缘:domain.ts、route-rule.ts、edge-target-verification.ts
- 基础设施:servers.ts、server-container-status.ts、server-tunnel.ts
- 商业化:billing.ts、oauth.ts
- 运维:backup.ts、notification.ts、service-incident.ts
以 deployment.ts 为例,可以看到典型的 Drizzle 写法:text主键带dep_前缀、外键引用 project 和 organization 并配置级联删除(onDelete: "cascade")、trigger字段记录部署由 webhook 还是回滚触发。所有字段都有清晰的注释文档,读 Schema 就像读 API 文档。
所有数据库访问都被收口到仓库层(Repos)——见 packages/db/src/index.ts 的导出注释:“所有 DB 访问都经过这里”。业务代码不直接拼查询,而是调用createProjectRepo、createDeploymentRepo等工厂,这是全项目可测试性的关键。
避坑指南:PGlite 单进程锁与 Postgres 启动等待
内嵌数据库最大的风险是两个进程同时打开同一份数据导致不可逆损坏。Openship 在这里做了两重工程保障:
① PGlite 单实例文件锁— 实现见 pglite-lock.ts:
- 用原子创建(
O_EXCL)在数据目录旁生成<dir>.lock锁文件,保证同一时刻只有一个进程持有数据目录; - 锁内记录操作系统级的机器标识(machine-id / MachineGuid),区分「本机崩溃残留」和「另一台机器占用」;
- 崩溃自愈:发现前一个进程已死亡(死 PID)立即回收锁,
SIGKILL不会把目录锁死; - 锁文件刻意放在数据目录外部,因为 PGlite 首次初始化拒绝非空目录。
热重载场景下,新进程会最多等待 30 秒让旧进程优雅释放数据库,避免开发时反复出现 “already using the database” 报错。
② Postgres 就绪等待— 实现见 client.ts:
- 启动时最多等待90 秒(每 1 秒重试一次),覆盖宿主重启后 Postgres 崩溃恢复的慢启动;
- 致命错误立即失败而非空等:
28P01/28000(密码错误,通常是.env与数据卷不匹配)和3D000(数据库不存在)会立刻抛出可读的错误,帮你省下几十分钟排查时间; - 连接池挂载了空闲连接错误监听,Postgres 重启不会让整个 API 进程崩溃退出。
📌 配套工具:package.json提供了db:heal-pglite、db:heal-orphans等自愈脚本(入口在 scripts/heal-pglite.ts),遇到极端异常时有标准修复手段。
数据层能力延伸:导出、恢复与跨实例迁移
除了 CRUD,packages/db/ 还内置了一套结构化备份体系,核心在 dump.ts:
dumpDatabase/restoreDatabase:整库按拓扑顺序导出与恢复;dumpSubgraph/restoreSubgraph:项目级子图导出——把单个项目及其关联数据打包带走,用于团队模式迁移和项目转让;- 敏感字段自动处理:加密列(
ENCRYPTED_COLUMNS)在导出时剥离,防止密钥材料泄露到备份文件。
命令行入口同样齐备:db:dump与db:restore两个脚本(scripts/dump.ts / scripts/restore.ts),配合桌面端发布打包,让「把数据从一台自托管服务器搬到另一台」成为一条命令的事。
快速上手:5 分钟认识 Openship 数据库层
如果你想动手体验,先获取代码:
git clone https://gitcode.com/GitHub_Trending/ope/openship然后按场景二选一:
- 零配置模式:什么都不配,直接启动 API——PGlite 会在
~/.openship/data自动建库并跑完全部 117 条迁移; - Postgres 模式:设置
DATABASE_URL(或直接复用 docker-compose 中的POSTGRES_HOST/POSTGRES_PASSWORD变量名),启动时自动连池并执行迁移。
📚 关键文件地图:
- 驱动选择与启动等待:packages/db/src/client.ts
- PGlite 单实例锁:packages/db/src/pglite-lock.ts
- Postgres 咨询锁(跨进程串行化):packages/db/src/advisory-lock.ts
- 迁移 SQL 目录:packages/db/drizzle/
- 迁移生成配置:packages/db/drizzle.config.ts
- 仓库层出口:packages/db/src/repos/
总结
Openship 的数据库层是一套教科书级的自托管工程实践:Drizzle ORM提供类型安全的统一接口,Postgres 与 PGlite 双驱动兼顾生产严谨与开发零门槛,自动迁移链让 schema 演进零手工干预,而单实例锁、启动等待与结构化备份则把内嵌数据库最常见的坑提前堵死。理解了这一层,你就掌握了 Openship 所有项目、部署与计费数据的底层脉络。
【免费下载链接】openshipSelf-hosted deployment platform项目地址: https://gitcode.com/GitHub_Trending/ope/openship
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考