diff --git a/docs/changelog/index.md b/docs/changelog/index.md index 74135df5..c2bebd39 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -28,6 +28,7 @@ sidebar: false ### 变更 +- 系统定期垃圾清理由每 2 小时改为每日执行一次(凌晨 3 点,Asia/Shanghai),降低非必要高频扫描。 - 服务工作者(SW)注入挑战页改为前台无感知:不再显示「加载中…」文案,页面空白,仅通过浏览器控制台输出 `[sw-challenge]` 调试信息(注册成功/失败/回退重定向),注入过程不打扰访客。 - 性能指标(CPU/内存/磁盘/网络)与访问日志的保留时长解耦:新增三库共用的 `metric_retention_days` 配置(默认 3 天),系统垃圾清理每日任务按独立短留存清理指标快照;访问日志仍按 `log_retention_days_*` 清理。 - ClickHouse 改为默认关闭:`clickhouse.enabled` 缺省或为 `false` 时不启用(此前会被强制置为 `true`),日志/指标由 PostgreSQL/SQLite 主库承担;显式 `true` 或设置 `CLICKHOUSE_HOST` / `CLICKHOUSE_ENABLED=true` 时启用。 diff --git a/internal/infra/persistence/migrator/goose/postgres/202608090002_update_system_cleanup_schedule.sql b/internal/infra/persistence/migrator/goose/postgres/202608090002_update_system_cleanup_schedule.sql new file mode 100644 index 00000000..2896c651 --- /dev/null +++ b/internal/infra/persistence/migrator/goose/postgres/202608090002_update_system_cleanup_schedule.sql @@ -0,0 +1,13 @@ +-- +goose Up +-- 系统定期垃圾清理改为每日执行一次(凌晨 3 点,Asia/Shanghai)。 +-- 此前为每 2 小时(0 */2 * * *)高频扫描,日常清理收益有限,降频减少非必要扫描。 +UPDATE w_schedules +SET cron = '0 3 * * *', + updated_at = CURRENT_TIMESTAMP +WHERE task_type = 'system_cleanup'; + +-- +goose Down +UPDATE w_schedules +SET cron = '0 */2 * * *', + updated_at = CURRENT_TIMESTAMP +WHERE task_type = 'system_cleanup'; diff --git a/internal/infra/persistence/migrator/goose/sqlite/202608090002_update_system_cleanup_schedule.sql b/internal/infra/persistence/migrator/goose/sqlite/202608090002_update_system_cleanup_schedule.sql new file mode 100644 index 00000000..2896c651 --- /dev/null +++ b/internal/infra/persistence/migrator/goose/sqlite/202608090002_update_system_cleanup_schedule.sql @@ -0,0 +1,13 @@ +-- +goose Up +-- 系统定期垃圾清理改为每日执行一次(凌晨 3 点,Asia/Shanghai)。 +-- 此前为每 2 小时(0 */2 * * *)高频扫描,日常清理收益有限,降频减少非必要扫描。 +UPDATE w_schedules +SET cron = '0 3 * * *', + updated_at = CURRENT_TIMESTAMP +WHERE task_type = 'system_cleanup'; + +-- +goose Down +UPDATE w_schedules +SET cron = '0 */2 * * *', + updated_at = CURRENT_TIMESTAMP +WHERE task_type = 'system_cleanup'; diff --git a/internal/infra/persistence/migrator/system_cleanup_migration_test.go b/internal/infra/persistence/migrator/system_cleanup_migration_test.go new file mode 100644 index 00000000..1e180430 --- /dev/null +++ b/internal/infra/persistence/migrator/system_cleanup_migration_test.go @@ -0,0 +1,95 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +package migrator + +import ( + "database/sql" + "fmt" + "os" + "strings" + "testing" + "time" + + "github.com/Rain-kl/Wavelet/internal/model" + "github.com/glebarez/sqlite" + "github.com/pressly/goose/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +const ( + systemCleanupPreviousMigration = int64(202608090001) + systemCleanupMigration = int64(202608090002) + systemCleanupTaskType = "system_cleanup" +) + +func TestSystemCleanupScheduleMigrationSQLite(t *testing.T) { + dbPath := t.TempDir() + "/system-cleanup-migration.db" + gormDB, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{ + DisableForeignKeyConstraintWhenMigrating: true, + }) + require.NoError(t, err) + sqlDB, err := gormDB.DB() + require.NoError(t, err) + sqlDB.SetMaxOpenConns(1) + t.Cleanup(func() { require.NoError(t, sqlDB.Close()) }) + + runSystemCleanupScheduleMigration(t, gormDB, sqlDB, dialectSqlite, "goose/sqlite") +} + +func TestSystemCleanupScheduleMigrationPostgres(t *testing.T) { + dsn := strings.TrimSpace(os.Getenv("OPENFLARE_TEST_POSTGRES_DSN")) + if dsn == "" { + t.Skip("OPENFLARE_TEST_POSTGRES_DSN is not set") + } + gormDB, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ + DisableForeignKeyConstraintWhenMigrating: true, + }) + require.NoError(t, err) + sqlDB, err := gormDB.DB() + require.NoError(t, err) + sqlDB.SetMaxOpenConns(1) + + schema := fmt.Sprintf("system_cleanup_migration_%d", time.Now().UnixNano()) + require.Regexp(t, `^[a-z0-9_]+$`, schema) + require.NoError(t, gormDB.Exec(`CREATE SCHEMA "`+schema+`"`).Error) + require.NoError(t, gormDB.Exec(`SET search_path TO "`+schema+`"`).Error) + t.Cleanup(func() { + assert.NoError(t, gormDB.Exec("SET search_path TO public").Error) + assert.NoError(t, gormDB.Exec(`DROP SCHEMA IF EXISTS "`+schema+`" CASCADE`).Error) + assert.NoError(t, sqlDB.Close()) + }) + + runSystemCleanupScheduleMigration(t, gormDB, sqlDB, dialectPostgres, "goose/postgres") +} + +func runSystemCleanupScheduleMigration( + t *testing.T, + gormDB *gorm.DB, + sqlDB *sql.DB, + dialect string, + dir string, +) { + t.Helper() + goose.SetBaseFS(migrationFS) + require.NoError(t, goose.SetDialect(dialect)) + require.NoError(t, goose.UpTo(sqlDB, dir, systemCleanupPreviousMigration)) + + assertSystemCleanupCron(t, gormDB, "0 */2 * * *", "迁移前应为每 2 小时") + + require.NoError(t, goose.UpTo(sqlDB, dir, systemCleanupMigration)) + assertSystemCleanupCron(t, gormDB, "0 3 * * *", "迁移后应为每日凌晨 3 点") + + require.NoError(t, goose.DownTo(sqlDB, dir, systemCleanupPreviousMigration)) + assertSystemCleanupCron(t, gormDB, "0 */2 * * *", "回滚后恢复每 2 小时") +} + +func assertSystemCleanupCron(t *testing.T, gormDB *gorm.DB, wantCron, msg string) { + t.Helper() + var schedule model.Schedule + require.NoError(t, gormDB.Where("task_type = ?", systemCleanupTaskType).First(&schedule).Error) + assert.Equal(t, wantCron, schedule.Cron, msg) +}