4500335da7
此前每次启动裸跑 AutoMigrate + 一串手写索引/回填,且启动路径上有两处 DROP TABLE CASCADE 的 legacy 迁移;多实例并发启动无锁 → 并发 ALTER/建索引竞争,一方报错即掉降级。 务实硬化(不引外部工具,保留 gorm 结构体为源): - PG advisory lock:整段迁移在 pg_advisory_lock 内串行,多实例同时启动只有一个进锁跑, 其余阻塞等待。取锁 60s 超时兜底(取不到带告警继续,AutoMigrate/索引多幂等)。 - 破坏性 legacy 迁移移出默认路径:migrateLegacyIntIDs/migrateDocLinkToID(DROP TABLE CASCADE)默认不跑,仅 ALLOW_LEGACY_SCHEMA_MIGRATION=1 时执行;检测到旧 schema 但未开 只告警不动手。现网早已是雪花 id 本就不触发,但从此不再是启动就可能 DROP。 - 版本化 runner:AutoMigrate 之外的步骤(3 个部分唯一索引 + NULL 余额回填)登记为 schemaSteps,各跑一次并记入 sundynix_schema_migration 表,下次跳过;某步失败即停、 不记录、下次重试。将来 AutoMigrate 做不了的破坏性/数据迁移在末尾追加新 id 即可。 pgsql.go 的迁移块收敛为一句 runMigrations(db)。带 runner 单测(跑一次/跳过/追加/失败即停)。 现网 DB 已有的索引/回填重跑无害(IF NOT EXISTS / WHERE IS NULL),跑后记录。build+vet+test 绿。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
66 lines
1.9 KiB
Go
66 lines
1.9 KiB
Go
package store
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// 版本化 runner:首次跑全部、再跑全跳过、追加只跑新步。
|
|
func TestRunVersionedMigrations_RunsOnceThenSkips(t *testing.T) {
|
|
p := newTestStore(t)
|
|
var ran []int
|
|
steps := []migrationStep{
|
|
{1, "a", func(*gorm.DB) error { ran = append(ran, 1); return nil }},
|
|
{2, "b", func(*gorm.DB) error { ran = append(ran, 2); return nil }},
|
|
}
|
|
if err := runVersionedMigrations(p.db, steps); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(ran) != 2 {
|
|
t.Fatalf("首次应跑 2 步,得 %v", ran)
|
|
}
|
|
|
|
ran = nil
|
|
if err := runVersionedMigrations(p.db, steps); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(ran) != 0 {
|
|
t.Fatalf("第二次应全跳过(已记录),得 %v", ran)
|
|
}
|
|
|
|
steps = append(steps, migrationStep{3, "c", func(*gorm.DB) error { ran = append(ran, 3); return nil }})
|
|
if err := runVersionedMigrations(p.db, steps); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(ran) != 1 || ran[0] != 3 {
|
|
t.Fatalf("应只跑新增步骤 #3,得 %v", ran)
|
|
}
|
|
}
|
|
|
|
// 某步失败即停:后续步骤不跑,且失败步骤不记录(下次从它重试);已成功步骤不重跑。
|
|
func TestRunVersionedMigrations_StopsOnFailure(t *testing.T) {
|
|
p := newTestStore(t)
|
|
var ran []int
|
|
steps := []migrationStep{
|
|
{1, "ok", func(*gorm.DB) error { ran = append(ran, 1); return nil }},
|
|
{2, "boom", func(*gorm.DB) error { return errors.New("boom") }},
|
|
{3, "after", func(*gorm.DB) error { ran = append(ran, 3); return nil }},
|
|
}
|
|
if err := runVersionedMigrations(p.db, steps); err == nil {
|
|
t.Fatal("步骤 #2 失败应返回 error")
|
|
}
|
|
if len(ran) != 1 {
|
|
t.Fatalf("步骤 #3 不该在 #2 失败后跑,得 %v", ran)
|
|
}
|
|
// 重试:#1 已记录跳过(不重复跑),#2 再次失败即停。
|
|
ran = nil
|
|
if err := runVersionedMigrations(p.db, steps); err == nil {
|
|
t.Fatal("重试仍应在 #2 失败")
|
|
}
|
|
if len(ran) != 0 {
|
|
t.Fatalf("已应用的 #1 不该重跑,得 %v", ran)
|
|
}
|
|
}
|