数据库与迁移

Goat 使用在 Docker 中运行的 PostgreSQL,因此你无需直接在主机上安装或配置数据库服务器。

本地环境将 PostgreSQL 数据存储在以下目录中:

.cache/postgres

因此,在停止或重启容器后,数据不会消失。

这种方式既保留了本地工作的便利性,又能在不同计算机和操作系统之间维持可重复的环境。

启动数据库

若要仅启动数据库,请执行:

go run ./scripts run:script --path=herd/db/run.goat

启动后,PostgreSQL 可通过以下端口访问:

5433

连接参数从本地文件 .env 中读取。

Goat 使用带有以下前缀的值:

GOAT_DB_MAIN_*

其中定义了连接数据库所需的数据,例如数据库名称、用户、密码和主机。

请勿将真实的生产环境密钥提交到仓库中。

本地数据持久化

PostgreSQL 容器可以在不丢失数据的情况下停止和重新启动,因为实际的数据库文件位于其临时文件系统之外。

在本地环境中,数据存储在:

.cache/postgres

这意味着:

  • 重启容器不会删除数据库,
  • 重启开发环境会保留数据,
  • 可以在现有本地状态上执行迁移,
  • 你可以处理项目,而无需每次都从头重建数据库。

如果你需要一个干净的环境,请使用专用清理脚本,而不要手动删除 PostgreSQL 文件。

迁移

使用以下命令运行迁移:

goat run:script --path=herd/db/migrate.goat

该脚本会准备执行迁移所需的环境。

典型流程包括:

启动 PostgreSQL
        ↓
生成应用程序
        ↓
构建 CLI
        ↓
运行 db:migrate

因此,迁移不依赖于开发人员手动依次执行各个步骤。

初始数据库架构

应用程序的基础架构根据以下位置定义的模型生成:

herd/_model.goat

生成的 SQL 位于:

app/static/raw/data/sql/migrations/0001_schema.sql

该文件包含由应用程序模型生成的初始数据库结构。

其中可能包括:

  • 表,
  • 列,
  • 数据类型,
  • 键,
  • 关系,
  • 索引,
  • 由模型产生的约束。

这有助于保持应用程序领域模型与 PostgreSQL 基础架构之间的一致性。

模型与迁移

对以下内容的更改:

herd/_model.goat

可能会影响生成的架构结构。

例如:

向实体添加字段
        ↓
goat re
        ↓
生成的模型发生变化
        ↓
SQL 发生变化

但这并不意味着每次模型变更都会自动成为对现有数据库安全的迁移。

当数据库已经包含数据,或正被其他用户使用时,这一点尤其重要。

需要额外关注的变更示例:

  • 删除列,
  • 更改数据类型,
  • 添加字段 NOT NULL,
  • 更改关系,
  • 删除表,
  • 更改键或约束,
  • 重构已存储在数据库中的数据。

生成器可以描述目标结构,但如何安全地从当前数据状态过渡到新架构,可能需要手动准备迁移。

部署期间的自动迁移

脚本 herd/deploy.goat 会在新版本应用启动前、PostgreSQL 已就绪后运行 db:migrate。因此,普通的模型变更会随部署一起进入生产环境,无需在服务器上单独手动执行命令。

不涉及数据迁移的变更的典型流程:

# 1. 修改 herd/_model.goat
goat re

# 2. 创建并审查新的迁移
goat db:migration:generate --out-dir=goatapp/static/raw/data/sql/migrations

# 3. 部署应用;deploy 将自动运行 db:migrate
goat run:script --path=herd/deploy.goat

0001_schema.sql 只创建一次。db:migrate 仅执行新的、带编号的文件名。生成器会比较由 db:migrate 和 db:fresh 启动的临时数据库,然后生成供审查的迁移。

自动机制适用于保留数据的变更,例如添加表或可选列。它不会识别列重命名,也不会猜测数据应如何转换。

在对现有安装执行生产迁移前,goat deploy 会停止应用并创建站点的加密备份。归档包含 PostgreSQL 转储、持久化文件、当前运行配置和应用镜像。备份会在保持部署锁定的情况下还原到隔离数据库;备份或其验证失败会在运行任何迁移之前停止部署。
备份会本地存储在 .backups/goatcms.com/ 中,并远程存储在
~/deploy/goatcms.com/backups/ 中。密码通过共享机制
encrypt 获取;在 CI 中应设置 GOAT_ENCRYPT_PASSWORD。

手动验证和恢复备份:

goat remote:backup:verify --scope=goatcms.com --from=.backups/goatcms.com/<backup>.goatbackup
goat remote:restore --scope=goatcms.com --from=.backups/goatcms.com/<backup>.goatbackup --replace

当变更需要回填数据、转换类型、删除数据,或为现有记录添加约束时,请在
child/app/migrations/sql/ 中准备自己的 SQL 文件,例如
0002_backfill_customer_status.sql。随后 Deploy 还会运行
child:db:migrate;该文件只会执行一次,并记录在独立于应用迁移的历史中。

从模型快照生成迁移

生成器会在本地将迁移历史与完整模型快照进行比较:

goat db:migration:generate --out-dir=goatapp/static/raw/data/sql/migrations
# 审查 goatapp/static/raw/data/sql/migrations/0002_model_sync.sql
goat run:script --path=herd/deploy.goat

该命令不会连接生产环境。重新生成后,它会构建应用并启动临时 PostgreSQL 17,其中包含两个空数据库:一个执行
db:migrate,另一个执行 db:fresh。固定版本的 pg-schema-diff 会创建从迁移到快照的计划。没有差异时不会创建文件。

db:migration:generate 会生成供审查的 SQL,而 db:migrate 和
child:db:migrate 会在指定数据库上执行迁移。只有
goat run:script --path=herd/deploy.goat 会在部署过程中于生产环境运行这些命令。破坏性操作、类型变更、长时间锁定和索引重建都会标记警告,并需要人工评估。

检查迁移

在包含重要数据的数据库上应用架构变更之前,请检查生成或准备好的 SQL。

尤其应验证:

  • 迁移是否不会删除数据,
  • 列类型变更是否适用于现有值,
  • 新字段是否具有合适的默认值,
  • 新约束是否满足现有记录,
  • 迁移是否会导致大型表产生高成本锁定,
  • 是否可以安全地回滚变更。

应特别谨慎对待如下操作:

DROP TABLE
DROP COLUMN
ALTER COLUMN

因为它们可能导致不可逆的数据丢失。

清理本地数据库

要清空本地数据库,请运行:

goat run:script --path=herd/db/clean.goat

该脚本会删除本地数据库的数据和结构,使你能够从干净的状态重新开始工作。

它可能在以下场景中很有用:

  • 你想重新测试项目初始化,
  • 你以与本地数据库不兼容的方式更改了模型,
  • 你正在从空 schema 测试迁移,
  • 你想重建 fixture 数据,
  • 本地数据不再与当前版本的应用程序匹配。

注意破坏性操作

herd/db/clean.goat 会执行破坏性操作。

它可能永久删除:

  • 表,
  • 数据,
  • 本地数据库状态。

因此,只有在你确定配置指向正确环境时才使用它。

运行前,建议检查 .env 中 GOAT_DB_MAIN_* 的值,尤其是在项目可能连接到多个数据库时。

不要将 clean.goat 视为生产环境管理工具。

更改模型时的典型工作流

在开发环境中,数据结构的更改可能如下所示:

# 修改模型
vim herd/_model.goat

# 生成变更
goat re

# 检查生成的 SQL 和代码
git diff

# 执行迁移
goat run:script --path=herd/db/migrate.goat

# 运行测试
goat run:script --path=herd/test.goat

如果本地数据库处于与当前模型不兼容的状态,并且无需保留数据,可以将其清空:

goat run:script --path=herd/db/clean.goat
goat run:script --path=herd/db/migrate.goat

共享环境中的迁移

在本地开发期间方便的做法,不应自动照搬到测试、预发布或生产环境。

对于其他人也在使用的数据库,仅更改模型是不够的。

在部署 schema 变更之前:

  1. 检查当前 schema 与目标 schema 之间的差异,
  2. 审查迁移将执行的 SQL,
  3. 评估迁移对现有数据的影响,
  4. 创建最新的备份,
  5. 在真实数据的副本上测试迁移,
  6. 估算执行时间和可能的锁定,
  7. 准备回滚或修复失败迁移的方案,
  8. 然后才在目标环境中执行变更。

尤其重要的是,应在结构和规模接近生产环境的数据上测试迁移。在空的本地数据库上运行正常的迁移,在包含数百万条记录的大型表上可能会有完全不同的表现。

生成器不能替代数据迁移策略

Goat 可以根据模型生成结构,但不应被视为针对每个数据变更问题的自动答案。

以下两者之间存在重要区别:

目标 schema

和:

从当前 schema
安全地过渡到
目标 schema 的方式

例如,添加必填列可能需要多个阶段:

添加可选列
        ↓
填充现有数据
        ↓
部署使用新字段的代码
        ↓
添加 NOT NULL 约束

同样,更改数据类型或关系结构可能需要分阶段执行数据迁移。

生成器有助于维护应用程序结构,但数据安全的责任仍由迁移流程承担。

最佳实践

将 herd/_model.goat 视为应用程序模型的描述,而不是现有数据可安全迁移的保证。

对于本地更改,你可以快速重新生成应用并重建数据库。

对于涉及共享环境或生产环境的更改,务必分析其对现有架构和数据的影响。

最重要的原则是:

生成器可以描述数据库的目标结构,但要安全地在不同数据版本之间过渡,则需要经过审慎设计的迁移。