How it works
- Retention is a runtime setting per target, with presets of 30, 60, 90, 180 and 365 days, or 0 to keep forever. Change it in the cleanup page or with
PUT /api/v1/settings/runtime/{policy_key}. A saved value beats the environment variable, which beats the default. - Mode is
off,previeworon, stored as the runtime settingcleanup.<id>.mode.previewruns the scheduled cleanup as a dry run: it records how many rows it would delete and deletes nothing. - Origin decides the default mode. A target that replaces an older sweeper (
existing) defaults toon, so nothing changes for current installs. A target for data that was never bounded (new) defaults topreviewuntil an administrator enables it. - Schedule. One scheduler checks every target each
cleanup.interval(default 6 hours, minimum 15 minutes), spaces the targets a few seconds apart, and waits 5 minutes after boot before the first pass.cleanup.enabledturns the schedule off; manual cleanups still work. - Safety. Deletes run in time slices (one day of rows at a time), a run stops at a row cap and reports
more_remaining, a target never runs twice at once, a run never deletes rows younger than its window, and a cancelled run stops between slices. A dry run never writes. Reclaiming disk space (VACUUM) is a separate action, never a side effect.
Targets
The janitor stages only report. Their usage comes from the last recorded janitor pass, their mode follows the janitor policy (off, preview while it is in dry run, on), and the page links to Settings, Cleanup, Disk for changes.
Environment overrides
Each older environment variable is now the env override of the same runtime setting, with the old default, so nothing changes until someone saves a value in the dashboard.- Day counts (
APP_EMAIL_LOG_RETENTION_DAYSand similar) accept any whole number of days, not only the presets.0keeps the data forever, except for the two audit variables, where0is ignored and the default applies (it always meant that for audit). APP_EVENT_WEBHOOK_RETENTIONandAPP_ALERT_HISTORY_RETENTIONstay Go durations (720h) and also accept30d. They are converted to whole days, rounded up, so a window never shrinks (36hbecomes 2 days).- Other settings:
APP_CLEANUP_INTERVAL,APP_CLEANUP_ENABLED. - Limits, read from the environment only:
APP_CLEANUP_MAX_ROWS(rows per run, default 100000),APP_CLEANUP_SLICE(time span of one delete, default 24h),APP_CLEANUP_STAGGER(default 5s),APP_CLEANUP_STARTUP_DELAY(default 5m),APP_CLEANUP_HISTORY_DAYS(default 365),APP_CLEANUP_USAGE_TTL(default 30s),APP_CLEANUP_EXPORT_MAX(CSV rows, default 10000),APP_TOKEN_CLEANUP_BATCH(token delete batch).
Floors and weakening
- A target with a floor refuses a retention shorter than the floor. The API answers 400 with a field error that names the policy key, for example
retention.audit_log_days. 0 (keep forever) is always allowed. - Lowering the retention of a destructive target (the audit settings) needs
confirm_weakening: truein the settings request, like other security settings. The change is audited by the usualsettings_changerow. - The audit sweeper and the scheduled run raise a window below the floor (for example from an old environment variable) to the floor. The floors are fixed in code.
- Security audit entries never outlive their rule the other way either: their window is never shorter than the general window.
Audit, events and alerts
- Every run, scheduled or manual, including dry runs, writes an audit row with action
cleanup_run. The detail holds counts only: target, days, dry_run, rows, bytes, trigger and error. It never holds row contents. - Each run emits
cleanup.run, orcleanup.failedwith the error, through the event webhooks. - A failed run sends one notice per target per day to the enabled notification channels.
- Run history lives in the
cleanup_runstable and removes itself after 365 days.
API
Preview and run answer
{"rows", "bytes", "dry_run", "more_remaining", "error"} (dry_run is true for a preview or a dry run); error is an empty string on success, and a failure of the run itself is reported there with status 200 and recorded in history. The first-run review (GET /api/v1/settings/runtime/onboarding) adds an informational item with key cleanup.preview_mode whose value is the number of targets still in preview, janitor stages in dry run included. Each target in GET /api/v1/cleanup carries source (default, env or saved) for where its retention days came from, and usage.bytes is null when the store cannot measure size (API tokens).
Add a target
A new target is one file and one registration line, about 30 lines for a table with a timestamp column.- Add the retention setting next to the others in
internal/runtimesettings/retention.go: a key,KindInt,AllowedInts: RetentionPresets, the existing env var asEnv(withEnvConvertif it is a duration) andCheckValue: MinDaysCheck(floor)if it needs a floor. A security-relevant setting also setsRisk: RiskWeakensSecurity. - In the store, add a count of rows older than a cutoff and use the existing delete function. For a table with a plain timestamp column, add it to
cleanupTablesininternal/store/cleanup_stats.goand the built-in helpers measure and count it. - Build a
cutoffTargetininternal/cleanup/builtin.gowithMeta(id, category, titles, policy setting,Origin, floor), a measure function, a count function and a delete function, then add it toRegisterBuiltins.Origin: OriginNewstarts it in preview. - Add English strings under
cleanup:targets.<id>in the dashboard locale file. - Test that
Previewequals the rowsRunremoves, using the pattern ininternal/cleanup/builtin_test.go.
Target directly: Measure, Preview(days) and Run(days, opts), honouring opts.DryRun, opts.MaxRows and the context.