use std::time::Duration; use crate::AppState; /// Background job: age retention + hard SQLite size cap (default 10 GiB). pub fn spawn(state: AppState) { tokio::spawn(async move { // Run once shortly after boot, then on the configured cadence. tokio::time::sleep(Duration::from_secs(15)).await; run_once(&state); let secs = state.config.cleanup_interval_secs.max(60); let mut ticker = tokio::time::interval(Duration::from_secs(secs)); ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); // Skip the immediate first tick (already ran above). ticker.tick().await; loop { ticker.tick().await; run_once(&state); } }); } fn run_once(state: &AppState) { let max = state.config.max_db_bytes; // Aim for 90% of the cap so we don't thrash at the ceiling. let target = (max as f64 * 0.90) as u64; match state .db .run_cleanup(state.config.retention_days, max, target) { Ok(report) => { tracing::info!( deleted_by_age = report.deleted_by_age, deleted_by_size = report.deleted_by_size, vacuumed = report.vacuumed, bytes_before = report.bytes_before, bytes_after = report.bytes_after, rows = report.rows_after, max_bytes = max, "sqlite cleanup finished" ); } Err(e) => tracing::warn!(error = %e, "sqlite cleanup failed"), } match state.db.get_settings() { Ok(settings) if settings.cleanup_logs_older_than_3_months => { match state .db .prune_health_results_older_than(crate::settings::HEALTH_LOG_RETENTION_DAYS) { Ok(n) if n > 0 => { tracing::info!(deleted = n, "pruned health logs older than 3 months") } Ok(_) => {} Err(e) => tracing::warn!(error = %e, "health log prune failed"), } } Ok(_) => {} Err(e) => tracing::warn!(error = %e, "load settings for cleanup failed"), } }