2026-10-16 14:45:38 -06:00
|
|
|
"""Housekeeping, nightly at 03:30 site time: prune the VISUAL change rows and gone cache rows, old job runs and
|
2026-10-16 17:13:31 -06:00
|
|
|
old claimed run requests. It never touches packets, the audit log or anything append-only. Then it checks the assets
|
|
|
|
|
storage against its warning threshold (assets.check_quota opens or resolves the alert)."""
|
2026-10-16 14:45:38 -06:00
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from datetime import datetime, time
|
|
|
|
|
|
2026-10-16 17:13:31 -06:00
|
|
|
from ...domain import assets
|
2026-10-16 14:45:38 -06:00
|
|
|
from ...feed.config import FeedSettings
|
|
|
|
|
from . import Counts, JobContext, JobSpec, seconds_until
|
|
|
|
|
|
|
|
|
|
AT = time(3, 30)
|
|
|
|
|
CACHE_DAYS = 90
|
|
|
|
|
RUNS_DAYS = 180
|
|
|
|
|
REQUESTS_DAYS = 30
|
|
|
|
|
|
|
|
|
|
STEPS: tuple[tuple[str, str, int], ...] = (
|
|
|
|
|
("row_changes", "DELETE FROM visual_row_changes WHERE at < now() - make_interval(days => %s)", CACHE_DAYS),
|
|
|
|
|
(
|
|
|
|
|
"material_changes",
|
|
|
|
|
"DELETE FROM visual_material_changes WHERE at < now() - make_interval(days => %s)",
|
|
|
|
|
CACHE_DAYS,
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"gone_rows",
|
|
|
|
|
"DELETE FROM visual_rows WHERE gone_batch IN"
|
|
|
|
|
" (SELECT id FROM visual_batches WHERE started_at < now() - make_interval(days => %s))",
|
|
|
|
|
CACHE_DAYS,
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"gone_materials",
|
|
|
|
|
"DELETE FROM visual_materials WHERE gone_batch IN"
|
|
|
|
|
" (SELECT id FROM visual_batches WHERE started_at < now() - make_interval(days => %s))",
|
|
|
|
|
CACHE_DAYS,
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"job_runs",
|
|
|
|
|
"DELETE FROM job_runs WHERE started_at < now() - make_interval(days => %s) AND status <> 'running'",
|
|
|
|
|
RUNS_DAYS,
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"job_requests",
|
|
|
|
|
"DELETE FROM job_requests WHERE claimed_at IS NOT NULL AND claimed_at < now() - make_interval(days => %s)",
|
|
|
|
|
REQUESTS_DAYS,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run(ctx: JobContext) -> Counts:
|
|
|
|
|
counts: Counts = {}
|
|
|
|
|
for name, sql, days in STEPS:
|
|
|
|
|
with ctx.conn.transaction():
|
|
|
|
|
counts[name] = ctx.conn.execute(sql, (days,)).rowcount
|
2026-10-16 17:13:31 -06:00
|
|
|
try:
|
|
|
|
|
usage = assets.check_quota(ctx.conn, ctx.settings)
|
|
|
|
|
except Exception as e: # the storage check never fails the pruning
|
|
|
|
|
ctx.log.exception("the storage quota check failed")
|
|
|
|
|
counts["quota_error"] = type(e).__name__
|
|
|
|
|
else:
|
|
|
|
|
counts["quota_pct"] = usage["pct"]
|
|
|
|
|
counts["quota_warn"] = usage["warn"]
|
|
|
|
|
if usage["alert"]:
|
|
|
|
|
counts["quota_alert"] = usage["alert"]
|
2026-10-16 14:45:38 -06:00
|
|
|
return counts
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def cadence(_: FeedSettings, now_site: datetime) -> int:
|
|
|
|
|
return seconds_until(now_site, AT)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SPEC = JobSpec(name="housekeeping", run=run, cadence=cadence, pausable=False)
|