From 3ffaf0cc4d94e3d93551ce5fc508654f1506cbca Mon Sep 17 00:00:00 2001 From: ryanv Date: Tue, 10 Mar 2026 14:30:10 +0000 Subject: [PATCH] check retention --- .env.example | 4 ++++ README.md | 15 +++++++++++++++ app/main.py | 2 +- app/models.py | 41 ++++++++++++++++++++++++++++++++++++++++- app/scheduler.py | 5 ++++- docker-compose.yml | 1 + 6 files changed, 65 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index c0347d7..4372fc9 100644 --- a/.env.example +++ b/.env.example @@ -3,3 +3,7 @@ DOCKER_REGISTRY=docker.io DOCKER_IMAGE=myorg/myapp IMAGE_TAG=latest + +# Optional: check retention (limits DB growth) +# CHECK_RETENTION_COUNT=5000 # keep last N checks per service (default 5000) +# CHECK_RETENTION_DAYS=30 # also delete checks older than N days (0=disabled) diff --git a/README.md b/README.md index 88761fb..63a2c9a 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,21 @@ docker run -p 8080:8080 -v $(pwd)/data:/app/data myapp:test Add services from the dashboard (e.g. `https://example.com`, `google.com:443` for TCP) and view reports. +### Check Retention + +To limit database growth, the app prunes old checks every 15 minutes: + +| Env var | Default | Description | +|---------|---------|-------------| +| `CHECK_RETENTION_COUNT` | 5000 | Keep last N checks per service | +| `CHECK_RETENTION_DAYS` | 0 (disabled) | Also delete checks older than N days | + +Example: keep 2000 checks per service and drop anything older than 30 days: + +```bash +docker run -e CHECK_RETENTION_COUNT=2000 -e CHECK_RETENTION_DAYS=30 ... +``` + ## Jenkins Pipeline The pipeline: diff --git a/app/main.py b/app/main.py index da48409..dbcd3bc 100644 --- a/app/main.py +++ b/app/main.py @@ -141,7 +141,7 @@ def report(service_id): status_filter = request.args.get("status") search = request.args.get("search", "").strip() or None page = max(1, int(request.args.get("page", 1))) - per_page = min(100, max(10, int(request.args.get("per_page", 25)))) + per_page = min(100, max(10, int(request.args.get("per_page", 10)))) stats = models.get_report_stats(service_id, from_ts=from_ts, to_ts=to_ts) checks_total = models.get_checks_count(service_id, from_ts=from_ts, to_ts=to_ts, status_filter=status_filter, search=search) checks = models.get_checks( diff --git a/app/models.py b/app/models.py index 9f9dfab..f7931be 100644 --- a/app/models.py +++ b/app/models.py @@ -2,12 +2,16 @@ import os import sqlite3 from contextlib import contextmanager -from datetime import datetime +from datetime import datetime, timedelta, timezone from pathlib import Path DATA_PATH = os.environ.get("DATA_PATH", "/app/data") DB_PATH = Path(DATA_PATH) / "monitor.db" +# Retention: keep last N checks per service, and optionally drop checks older than N days +CHECK_RETENTION_COUNT = int(os.environ.get("CHECK_RETENTION_COUNT", "5000")) +CHECK_RETENTION_DAYS = int(os.environ.get("CHECK_RETENTION_DAYS", "0")) or None + def _ensure_data_dir(): Path(DATA_PATH).mkdir(parents=True, exist_ok=True) @@ -232,3 +236,38 @@ def get_all_services_for_scheduler(): with get_db() as conn: rows = conn.execute("SELECT id, target, protocol, interval_seconds FROM services").fetchall() return [dict(r) for r in rows] + + +def prune_checks_retention() -> int: + """ + Remove old checks to limit storage. Keeps last CHECK_RETENTION_COUNT per service. + If CHECK_RETENTION_DAYS is set, also deletes checks older than that. + Returns number of rows deleted. + """ + with get_db() as conn: + deleted = 0 + # Delete checks older than N days (if configured) + if CHECK_RETENTION_DAYS: + cutoff = (datetime.now(timezone.utc) - timedelta(days=CHECK_RETENTION_DAYS)).isoformat() + cur = conn.execute("DELETE FROM checks WHERE timestamp < ?", (cutoff,)) + deleted += cur.rowcount + + # Keep only last N checks per service + service_ids = [r[0] for r in conn.execute("SELECT id FROM services").fetchall()] + for sid in service_ids: + # Get ids of checks to keep (most recent N) + keep_ids = conn.execute( + "SELECT id FROM checks WHERE service_id = ? ORDER BY timestamp DESC LIMIT ?", + (sid, CHECK_RETENTION_COUNT), + ).fetchall() + keep_ids = [r[0] for r in keep_ids] + if not keep_ids: + continue + placeholders = ",".join("?" * len(keep_ids)) + cur = conn.execute( + f"DELETE FROM checks WHERE service_id = ? AND id NOT IN ({placeholders})", + [sid] + keep_ids, + ) + deleted += cur.rowcount + + return deleted diff --git a/app/scheduler.py b/app/scheduler.py index d2314cf..f8a5f7e 100644 --- a/app/scheduler.py +++ b/app/scheduler.py @@ -2,7 +2,7 @@ from apscheduler.schedulers.background import BackgroundScheduler from app.checker import run_check -from app.models import get_all_services_for_scheduler +from app.models import get_all_services_for_scheduler, prune_checks_retention def _run_all_checks(): @@ -54,4 +54,7 @@ def start_scheduler(): # Sync job list every 60 seconds (only adds/removes when services change) scheduler.add_job(sync_jobs, "interval", seconds=60, id="sync_jobs") + # Prune old checks every 15 minutes (retention/compression) + scheduler.add_job(prune_checks_retention, "interval", minutes=15, id="prune_checks") + scheduler.start() diff --git a/docker-compose.yml b/docker-compose.yml index 97d45f2..0c619ca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,4 +10,5 @@ services: - ./data:/app/data environment: - VERSION=${IMAGE_TAG:-latest} + # Optional: CHECK_RETENTION_COUNT=5000, CHECK_RETENTION_DAYS=30 restart: unless-stopped