package storage import ( "context" "log/slog" "time" ) func (r *MySQLRepository) StartCleanup(ctx context.Context, logger *slog.Logger) { if r.cfg.Dashboard.CleanupInterval <= 0 { return } go func() { r.cleanupOnce(ctx, logger) ticker := time.NewTicker(r.cfg.Dashboard.CleanupInterval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: r.cleanupOnce(ctx, logger) } } }() } func (r *MySQLRepository) cleanupOnce(ctx context.Context, logger *slog.Logger) { rows, err := r.Cleanup(ctx) if err != nil { if r.metrics != nil { r.metrics.RecordError(err) } logger.Error("dashboard cleanup failed", "error", err) return } if r.metrics != nil { r.metrics.RecordCleanup(rows) } if rows > 0 { logger.Info("dashboard cleanup finished", "rows", rows) } } func (r *MySQLRepository) Cleanup(ctx context.Context) (int64, error) { var total int64 if r.cfg.Dashboard.DedupRetention > 0 { cutoff := time.Now().Add(-r.cfg.Dashboard.DedupRetention) rows, err := r.deleteLimit(ctx, ` DELETE FROM dashboard_cdc_event_dedup WHERE created_at < ? LIMIT ?`, cutoff, r.cfg.Dashboard.CleanupBatchSize) if err != nil { return total, err } total += rows } if r.cfg.Dashboard.UserMetricDays > 0 { cutoffDate := time.Now().AddDate(0, 0, -r.cfg.Dashboard.UserMetricDays).Format("2006-01-02") deletes := []struct { sql string args []interface{} }{ { sql: ` DELETE FROM country_dashboard_daily_user_metric WHERE stat_date < ? LIMIT ?`, args: []interface{}{cutoffDate, r.cfg.Dashboard.CleanupBatchSize}, }, { sql: ` DELETE FROM country_dashboard_period_user_metric WHERE period_type <> 'ALL' AND period_end_date IS NOT NULL AND period_end_date < ? LIMIT ?`, args: []interface{}{cutoffDate, r.cfg.Dashboard.CleanupBatchSize}, }, { sql: ` DELETE FROM country_dashboard_game_period_user_metric WHERE period_type <> 'ALL' AND period_end_date IS NOT NULL AND period_end_date < ? LIMIT ?`, args: []interface{}{cutoffDate, r.cfg.Dashboard.CleanupBatchSize}, }, { sql: ` DELETE FROM country_dashboard_recharge_detail WHERE create_time < ? LIMIT ?`, args: []interface{}{cutoffDate, r.cfg.Dashboard.CleanupBatchSize}, }, } for _, item := range deletes { rows, err := r.execRows(ctx, item.sql, item.args...) if err != nil { return total, err } total += rows } } return total, nil } func (r *MySQLRepository) deleteLimit(ctx context.Context, query string, args ...interface{}) (int64, error) { return r.execRows(ctx, query, args...) } func (r *MySQLRepository) execRows(ctx context.Context, query string, args ...interface{}) (int64, error) { result, err := r.db.ExecContext(ctx, query, args...) if err != nil { return 0, err } return result.RowsAffected() }