fix(deploy): 全新部署链路健壮性(0004 幂等 + 迁移前置 + 空库守卫)
- 0004_metric_aggregate_unique 改为幂等:新库基线已由模型 uniqueIndex 建出 uniq_metric_agg,再次 CREATE 会以 1061 重复键名中断 migrate up, 导致 0005_agent_settings 建不出来(全新部署实测复现) - 新增 database.IndexExists:跨 SQLite/MySQL/PostgreSQL 判断索引是否存在 - 新增 database.EnsureSchemaInitialized:服务启动遇到空库时明确失败并给出 执行 aiops migrate up 的指引,避免「服务起来了但页面全是空数据」 - run.sh docker up/upgrade/update 先跑一次 aiops migrate up 再启动服务 // Logan.Li
This commit is contained in:
1 parent
7a96381dc7
commit
f3a4fb569f
5 files changed
+57
No files matched your search
@@ -155,6 +155,11 @@ func main() {
|
||||
log.Warn("--skip-auto-migrate 已废弃;服务启动始终不会执行数据库迁移")
|
||||
}
|
||||
|
||||
// 空库直接失败并给出明确指引,避免服务带病启动(全新部署需先执行一次 migrate up)。 // Logan.Li
|
||||
if err := database.EnsureSchemaInitialized(db); err != nil {
|
||||
log.Fatal("数据库尚未初始化:请先执行 `aiops migrate up` 建表(服务启动不会自动建表)", zap.Error(err))
|
||||
}
|
||||
|
||||
// 初始化种子数据
|
||||
seed.SeedData(db)
|
||||
// 中间件监控默认目标(aiops-redis / aiops-mysql)// Logan.Li
|
||||
|
||||
@@ -70,6 +70,17 @@ func All() []Migration {
|
||||
Version: "0004_metric_aggregate_unique",
|
||||
Description: "指标聚合表唯一约束(upsert 生效)",
|
||||
Apply: func(db *gorm.DB) error {
|
||||
// 新库基线已由模型自带 uniq_metric_agg(uniqueIndex 标签)建出该索引,
|
||||
// 此时必须跳过:否则 CREATE UNIQUE INDEX 会以 1061 重复键名中断整个 migrate up,
|
||||
// 导致后面的迁移(如 0005_agent_settings)永远建不出来。
|
||||
// 只有老库(历史 idx_agg 为非唯一索引)才需要去重 + 重建唯一索引。 // Logan.Li
|
||||
exists, err := database.IndexExists(db, "monitor_metric_aggregates", "uniq_metric_agg")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return nil
|
||||
}
|
||||
return executeDialectSQL(db, "0004_metric_aggregate_unique")
|
||||
},
|
||||
},
|
||||
|
||||
@@ -84,6 +84,18 @@ func (n indexNamedNaming) UniqueName(table, column string) string {
|
||||
return n.NamingStrategy.IndexName(table, column)
|
||||
}
|
||||
|
||||
// EnsureSchemaInitialized 校验库结构是否已初始化(即已执行过 `aiops migrate up`)。
|
||||
// 服务启动不做结构迁移,但遇到空库必须明确失败,避免「服务已起来、页面全是空数据」的静默故障。 // Logan.Li
|
||||
func EnsureSchemaInitialized(db *gorm.DB) error {
|
||||
if db == nil {
|
||||
return fmt.Errorf("数据库连接为空")
|
||||
}
|
||||
if !db.Migrator().HasTable("iam_users") {
|
||||
return fmt.Errorf("未检测到业务表,数据库尚未初始化")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BootstrapSchema 为空数据库创建初始基线结构。
|
||||
// 该函数只能由显式的 `aiops migrate up` 调用,服务启动路径禁止调用。 // Logan.Li
|
||||
func BootstrapSchema(db *gorm.DB) error {
|
||||
|
||||
@@ -61,3 +61,25 @@ func CleanupSNMPMetricsSQL(driver string, retentionDays int) string {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// IndexExists 判断某表上是否已存在指定名称的索引(跨 SQLite / MySQL / PostgreSQL)。
|
||||
// 供版本化迁移做幂等判断:新库基线已由模型(uniqueIndex 标签)建出同名索引时,
|
||||
// 迁移不应再建一次,否则 MySQL 会以 1061 重复键名中断整个 migrate up。 // Logan.Li
|
||||
func IndexExists(db *gorm.DB, table string, index string) (bool, error) {
|
||||
var (
|
||||
query string
|
||||
count int64
|
||||
)
|
||||
switch DriverName(db) {
|
||||
case "sqlite":
|
||||
query = "SELECT COUNT(*) FROM sqlite_master WHERE type = 'index' AND tbl_name = ? AND name = ?"
|
||||
case "postgres":
|
||||
query = "SELECT COUNT(*) FROM pg_indexes WHERE schemaname = current_schema() AND tablename = ? AND indexname = ?"
|
||||
default:
|
||||
query = "SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?"
|
||||
}
|
||||
if err := db.Raw(query, table, index).Scan(&count).Error; err != nil {
|
||||
return false, fmt.Errorf("检查索引 %s.%s 是否存在失败: %w", table, index, err)
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
Reference in new issue
Block a user