feat(alerting): AlertingRetentionJob daily cleanup

Nightly @Scheduled(03:00) job deletes RESOLVED alert_instances older
than eventRetentionDays and DELIVERED/FAILED alert_notifications older
than notificationRetentionDays.  Uses injected Clock for testability.
IT covers: old-resolved deleted, fresh-resolved kept, FIRING kept
regardless of age, PENDING notification never deleted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-19 22:16:21 +02:00
parent 118ace7cc3
commit 1ab21bc019
2 changed files with 310 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
package com.cameleer.server.app.alerting.retention;
import com.cameleer.server.app.alerting.config.AlertingProperties;
import com.cameleer.server.core.alerting.AlertInstanceRepository;
import com.cameleer.server.core.alerting.AlertNotificationRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.Clock;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
/**
* Nightly retention job for alerting data.
* <p>
* Deletes RESOLVED {@link com.cameleer.server.core.alerting.AlertInstance} rows older than
* {@code cameleer.server.alerting.eventRetentionDays} and DELIVERED/FAILED
* {@link com.cameleer.server.core.alerting.AlertNotification} rows older than
* {@code cameleer.server.alerting.notificationRetentionDays}.
* <p>
* Duplicate runs across replicas are tolerable — the DELETEs are idempotent.
*/
@Component
public class AlertingRetentionJob {
private static final Logger log = LoggerFactory.getLogger(AlertingRetentionJob.class);
private final AlertingProperties props;
private final AlertInstanceRepository alertInstanceRepo;
private final AlertNotificationRepository alertNotificationRepo;
private final Clock clock;
public AlertingRetentionJob(AlertingProperties props,
AlertInstanceRepository alertInstanceRepo,
AlertNotificationRepository alertNotificationRepo,
Clock alertingClock) {
this.props = props;
this.alertInstanceRepo = alertInstanceRepo;
this.alertNotificationRepo = alertNotificationRepo;
this.clock = alertingClock;
}
@Scheduled(cron = "0 0 3 * * *") // 03:00 every day
public void cleanup() {
log.info("Alerting retention job started");
Instant now = Instant.now(clock);
Instant instanceCutoff = now.minus(props.effectiveEventRetentionDays(), ChronoUnit.DAYS);
alertInstanceRepo.deleteResolvedBefore(instanceCutoff);
log.info("Alerting retention: deleted RESOLVED instances older than {} ({} days)",
instanceCutoff, props.effectiveEventRetentionDays());
Instant notificationCutoff = now.minus(props.effectiveNotificationRetentionDays(), ChronoUnit.DAYS);
alertNotificationRepo.deleteSettledBefore(notificationCutoff);
log.info("Alerting retention: deleted settled notifications older than {} ({} days)",
notificationCutoff, props.effectiveNotificationRetentionDays());
log.info("Alerting retention job completed");
}
}