Retention nicht pauschal, sondern pro Frequenz – mit dem Enum als Single-Source-of-Truth
Eine pauschale Aufbewahrungsdauer für Monitoring-Daten ist entweder Speicherverschwendung oder Verlust nützlicher Historie. Wie ich beide Probleme mit einer einzigen Methode am Enum aus der Welt schaffe.
Monitoring-Daten haben eine unangenehme Eigenschaft: Sie wachsen sehr ungleich.
Eine Probe, die einmal pro Tag läuft, produziert 365 Zeilen pro Jahr. Eine Probe, die jede Minute läuft, produziert 525.600. Wer in beiden Fällen 30 Tage Historie hält, hat im einen Fall ein paar Dutzend Datensätze und im anderen Fall ein DB-Problem.
In Portchecks habe ich das anfangs naiv mit einem festen Wert gelöst – und nach drei Tagen Betrieb gemerkt, dass das nicht funktioniert.
Das eigentliche Bedürfnis
Wenn du eine minütliche Probe forensisch betrachtest, willst du sehen, wie ein Service in den letzten Stunden gezuckt hat. Drei Tage davon reichen vollkommen. Niemand schaut sich an, wie der Port vor drei Wochen um 14:23 reagiert hat.
Bei einer stündlichen oder täglichen Probe ist das anders: Da willst du Wochenmuster erkennen können. Wann fällt der Service regelmäßig aus? Korreliert das mit Deployments? Eine Woche Historie ist hier das Minimum.
Anders gesagt: Die sinnvolle Aufbewahrung hängt nicht von einer Policy ab, sondern direkt von der Frequenz. Wenn ich die Frequenz schon im Enum kodiere, sollte die Retention dort auch hingehören.
Die Umsetzung
Das CheckInterval-Enum hatte vorher nur Cases und eine nextRunAfter()-Methode. Ich habe zwei Methoden ergänzt:
enum CheckInterval: string
{
case Minutely = 'minutely';
case TenMinutely = '10-minutely';
case FifteenMinutely = '15-minutely';
case ThirtyMinutely = '30-minutely';
case Hourly = 'hourly';
case Daily = 'daily';
case Weekly = 'weekly';
case Monthly = 'monthly';
public function isSubHourly(): bool
{
return in_array($this, [
self::Minutely,
self::TenMinutely,
self::FifteenMinutely,
self::ThirtyMinutely,
], true);
}
public function retentionDays(): int
{
return $this->isSubHourly() ? 3 : 7;
}
}
Und das Cleanup-Command, das täglich um 03:15 läuft, ist genau so kurz, wie man es sich erhofft:
public function handle(): void
{
foreach (CheckInterval::cases() as $interval) {
$cutoff = now()->subDays($interval->retentionDays());
PortCheckResult::query()
->whereHas('portCheck', fn ($q) => $q->where('interval', $interval->value))
->where('checked_at', '<', $cutoff)
->chunkById(1000, fn ($rows) => $rows->each->delete());
}
}
Keine Configs, kein Magic Number irgendwo in einer Klasse. Die einzige Stelle, an der ich entscheide „minütlich = 3 Tage, alles andere = 7 Tage", ist genau dort, wo auch das Intervall lebt.
Was das löst, was eine Config nicht löst
Drei Dinge:
- Lesbarkeit: Wenn ich in einem halben Jahr ein neues Intervall hinzufüge (z. B. „alle 5 Minuten"), zwingt mich der Code, sofort über die Retention nachzudenken. Eine Config in
config/portchecks.phpwürde ich vielleicht vergessen. - Testbarkeit: Das Enum ist deterministisch und lässt sich in Tests ohne Config-Mock direkt nutzen.
CheckInterval::Minutely->retentionDays()braucht kein Setup. - Konsistenz: Wenn jemand „seit wann gibt es eigentlich Daten zu diesem Check?" beantworten will, kann er auf dieselbe Methode zugreifen, statt sich aus drei verschiedenen Configs eine Antwort zusammenzubauen.
Wo es nicht passt
Wenn die Retention am Ende doch pro Tarif konfigurierbar werden soll („Business-Plan bekommt 30 Tage Historie"), dann gehört die Methode nicht ans Intervall, sondern an die Kombination aus Plan und Intervall. In dem Moment ist es eine andere Diskussion und das Enum bleibt nur die Default-Variante.
Solange die Retention rein technisch motiviert ist – „so viel macht ökonomisch Sinn" und nicht „so viel hat der Kunde gekauft" – bleibt das Enum die richtige Stelle.
Fazit
Eine kleine Methode am richtigen Ort spart dir einen ganzen Config-Layer und macht den Code an drei Stellen einfacher: im Cleanup, in den Tests, im Onboarding neuer Kolleg:innen. Kein revolutionäres Pattern, aber genau die Sorte Mikro-Entscheidung, die ein Codebase über die Jahre angenehm hält.