feat: time-based alert retention policy#6616
Conversation
Delete alerts older than a configured number of days, together with their incident links, so the database stops growing forever. Disabled unless KEEP_ALERT_RETENTION_DAYS is set to a positive value.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6616 +/- ##
==========================================
- Coverage 30.49% 30.46% -0.04%
==========================================
Files 101 101
Lines 11741 11774 +33
==========================================
+ Hits 3581 3587 +6
- Misses 8160 8187 +27 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
The patch coverage number is a measurement artifact rather than missing tests. The unit tests job runs Reproduced both ways locally: with |
Summary
Closes #4802
Adds a time-based alert retention policy so the alert tables stop growing forever (#6524 reports a ~20GB postgres database after a month). When
KEEP_ALERT_RETENTION_DAYSis set to a positive value, Keep periodically deletes alerts older than that many days, together with their incident links. The feature is disabled by default, so nothing changes for existing deployments.This is a native, database-agnostic version of the MySQL cleanup CronJob referenced in the issue (helm-charts
db-cleanup-cronjob.yaml), which does not work for postgres or sqlite deployments.KEEP_ALERT_RETENTION_DAYSKEEP_ALERT_RETENTION_INTERVALKEEP_ALERT_RETENTION_BATCH_SIZEHow it works
delete_alerts_by_retentioninkeep/api/core/db.pydeletes in batches and commits per batch, so there are no long transactions and an interrupted run resumes cleanly on the next cycle. Deletion order per batch keeps foreign keys satisfied:lastalerttoincident, thenlastalert, thenalerttoincident, thenalert.LastAlertrows are selected byalert_idrather than by timestamp: a fingerprint that is still active keeps itsLastAlert(which points at a fresh alert) while its oldalertrows are purged, and a fully stale fingerprint is removed entirely.keep/api/tasks/process_retention_task.pymirrorsprocess_watcher_task.py: with Redis it runs through arq guarded by an NX lock, otherwise it runs as an asyncio loop guarded by a file lock. The arq cron is only registered when retention is enabled, since a destructive job should be opt-in.Two deliberate divergences from the cronjob, to keep behavior safe and the scope reviewable:
alerttogroupis not touched because the table was dropped from the schema in 2024 and has no model anymore.alertenrichment,alertaudit,alertraw,alertfield) are not purged. Enrichments are keyed by fingerprint and also serve incident and alert-instance enrichment, so deleting them based on alert age is not safe without more care.Tests
tests/test_alert_retention.py, running on the standarddb_sessionfixture:batch_size=2over 3 alerts) and removes theirLastAlert,AlertToIncidentandLastAlertToIncidentrows while fresh alerts surviveLastAlertand latest alert while old rows are purgedLastAlertToIncidentrows are cleaned up for purged fingerprints