"""Periodic retention cleanup for history tables.""" from __future__ import annotations import asyncio import logging import sqlite3 import time import aiosqlite from ..config import StorageCfg from ..core.stats import Stats log = logging.getLogger(__name__) async def run_retention(conn: aiosqlite.Connection, cfg: StorageCfg, stats: Stats) -> None: """Background task: periodically delete rows older than retention limits.""" interval = max(60, cfg.retention_interval_sec) while True: try: await asyncio.sleep(interval) await cleanup_once(conn, cfg, stats) except asyncio.CancelledError: raise except Exception: log.exception("retention sweep failed") stats.incr("retention_errors") async def cleanup_once(conn: aiosqlite.Connection, cfg: StorageCfg, stats: Stats) -> None: """Run one retention sweep; caller ensures this is not re-entrant.""" now = time.time() rows_total = 0 targets = ( ("ais_dynamic", cfg.retention.ais_dynamic_days), ("gps_fix", cfg.retention.gps_fix_days), ("raw_nmea", cfg.retention.raw_nmea_days), ("radio_telemetry", cfg.retention.radio_telemetry_days), ) for table, days in targets: if days <= 0: continue cutoff = now - days * 86400.0 try: cur = await conn.execute(f"DELETE FROM {table} WHERE ts < ?", (cutoff,)) await conn.commit() rows_total += cur.rowcount or 0 stats.incr(f"retention_deleted_{table}", cur.rowcount or 0) except sqlite3.OperationalError as exc: log.warning("retention: transient error on %s: %s", table, exc) stats.incr("retention_transient_errors") except Exception: log.exception("retention: error deleting from %s", table) stats.incr("retention_errors") if rows_total > 0: log.info("retention sweep removed %d rows", rows_total) stats.incr("retention_sweeps") __all__ = ["run_retention", "cleanup_once"]