golang-migrate Transaction Safety Contract¶
概念概覽
核心衝突¶
核心知識¶
核心衝突¶
golang-migrate 預設對每個 .sql 檔案包裝一個 transaction。而 CREATE INDEX CONCURRENTLY 和 DROP INDEX CONCURRENTLY 在 PostgreSQL 中**明確禁止在 transaction 內執行**,執行後會造成 migration dirty state,且難以自動回復。
常見誤區¶
- 開發者誤以為加上
CONCURRENTLY可以做 online DDL,但在 golang-migrate 環境下這是**虛假能力** — 指令根本無法執行成功。 - 對小型 admin/audit table,標準
CREATE INDEX(短暫 lock)是正確選擇。
正確修法(不只修眼前這個)¶
掃描整個 migrations/postgres/ 目錄找所有違規 DDL,而不是只修觸發問題的那個檔案(案例中 032 和 042 都有問題)。
CI 防線建立¶
// test/migrations/migration_lint_test.go
// 用 Go test 實作,比 grep shell script 更穩定:
// - 能跳過 comment 內的 CONCURRENTLY 關鍵字
// - 能處理 edge case(大小寫、內嵌字串等)
Migration Runner 雙軌問題¶
若外部 migrate/migrate CLI 寫 schema_migrations 表,而 cmd/twirpserver --migrate 寫 game_schema_migrations 表,兩者互不感知,會導致 dirty state 診斷困難。統一 runner 的做法:docker-compose.yml 的 migrate service 改用同一個 twirpserver image + --migrate flag。
經驗教訓¶
-
CONCURRENTLY DDL 在 golang-migrate 下是虛假能力,應禁止使用
-
發現一個違規 migration 時,要全掃目錄而非只修單一檔案
-
Migration lint 用 Go test 實作比 grep shell script 更可靠
-
雙 schema_migrations 表是 dirty state 難以診斷的根因
常見陷阱¶
-
只修觸發問題的 migration,沒有掃描歷史檔案
-
以為加 CONCURRENTLY 就能 online DDL,忽略 golang-migrate 的 transaction 包裝
-
用 grep shell script 做 migration lint,遇到 comment 或大小寫時誤判
最佳實踐¶
-
repo 內所有 migrations/*.sql 禁止 CONCURRENTLY DDL,用 CI test 強制執行
-
docker-compose 的 migrate service 與 production migrate runner 使用同一 image
-
bootstrap test 要同時驗 fresh DB + upgrade path 兩種場景
相關概念¶
- ci-gate-contract----dangling-------dangling---
- docker-compose-local-dev----dangling-------dangling---
- postgres-ddl-locking----dangling-------dangling---
- Release Gate Layering
- TestBot Anti-Drift Strategy
相關視角¶
以下頁面與本概念共享主題,但從不同角度切入。保留獨立視角同時提供交叉參考:
- golang-migrate Version Collision Fix — 共享:
golang-migrate,migration/ 獨特:database
來源 Sessions¶
| 日期 | Session | 貢獻摘要 |
|---|---|---|
| 2026-03-30 | 45cbfb38-30e4-4de0-9d9c-1ea02a62e945 | 揭示 CREATE INDEX CONCURRENTLY 在 golang-migrate transaction 架構下的根本衝突,並建立完整的 migration 安全契約(lint CI 防線) |