Die cron_schedule-Tabelle sauber halten und aufräumen
AI generated
InnoDB
SQL
MySQL · Magento · Cron · Wartung
Die cron_schedule-Tabelle sauber halten und aufräumen
von history_cleanup_every bis zum manuellen Cleanup

cron_schedule protokolliert jeden geplanten und ausgeführten Cron-Job in Magento und kann bei falscher Konfiguration innerhalb weniger Wochen Millionen Zeilen ansammeln. Wer die Statusübergänge der Tabelle versteht, history_cleanup_every richtig einstellt und bei Bedarf manuell nachhilft, verhindert langsame Cron-Läufe und Locking-Probleme in der Produktion.

15 Min. Lesezeit cron_schedule · history_cleanup_every · Cron-Gruppen Magento 2.4.x · MySQL 8 / MariaDB 10.6

1. Wie cron_schedule funktioniert

Die Tabelle cron_schedule ist das Herzstück der Magento-Cron-Verarbeitung. Für jeden konfigurierten Cron-Job legt Magento im Voraus Zeilen mit dem Status pending an, jeweils mit job_code, scheduled_at und initial leerem executed_at. Sobald der Cron-Runner (bin/magento cron:run) einen fälligen Job findet, wechselt der Status zu running, nach Abschluss zu success oder bei einem Fehler zu error oder missed, falls das Zeitfenster verpasst wurde.

Diese Statusübergänge sind bewusst so gestaltet, dass cron_schedule gleichzeitig als Warteschlange und als Ausführungsprotokoll dient. Der Vorteil ist Nachvollziehbarkeit, jeder Job-Lauf lässt sich im Nachhinein über scheduled_at, executed_at und finished_at auswerten. Der Nachteil ist, dass ohne aktives Aufräumen jede einzelne dieser Zeilen dauerhaft in der Tabelle verbleibt, selbst nach erfolgreichem Abschluss des Jobs.

Bei einem typischen Magento-Shop mit mehreren Dutzend registrierten Cron-Jobs, die im Minutentakt geplant werden, erzeugt allein die Erzeugung neuer pending-Zeilen pro Tag mehrere Tausend neue Einträge in cron_schedule. Ohne Bereinigung ist ein Wachstum auf mehrere Millionen Zeilen innerhalb weniger Monate keine Ausnahme, sondern die Regel.


DESCRIBE cron_schedule;
-- +--------------+------------------+------+-----+---------+----------------+
-- | Field        | Type             | Null | Key | Default | Extra          |
-- +--------------+------------------+------+-----+---------+----------------+
-- | schedule_id  | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
-- | job_code     | varchar(255)     | NO   | MUL | NULL    |                |
-- | status       | varchar(7)       | NO   | MUL | pending |                |
-- | messages     | text             | YES  |     | NULL    |                |
-- | created_at   | timestamp        | NO   |     | CURRENT |                |
-- | scheduled_at | varchar(30)      | NO   | MUL | NULL    |                |
-- | executed_at  | varchar(30)      | YES  |     | NULL    |                |
-- | finished_at  | varchar(30)      | YES  |     | NULL    |                |
-- +--------------+------------------+------+-----+---------+----------------+

-- Current status distribution
SELECT status, COUNT(*) AS count FROM cron_schedule GROUP BY status;

2. Warum die Tabelle bloatet

Der Hauptgrund für Bloat in cron_schedule ist simpel: Magento erzeugt kontinuierlich neue Zeilen, aber die Standardkonfiguration löscht alte Zeilen nur mit einer bestimmten Verzögerung und nur, wenn der Bereinigungsjob selbst zuverlässig läuft. Fällt der Cron-Runner über längere Zeit aus, etwa weil ein Server-Wartungsfenster den Cron deaktiviert hat oder ein PHP-Fehler den Prozess crasht, sammeln sich pending-Einträge unkontrolliert an, ohne dass jemals eine Bereinigung stattfindet.

Ein zweiter, oft übersehener Grund ist eine zu aggressive schedule_ahead_for-Konfiguration oder Cron-Jobs, die mit sehr kurzen Intervallen (jede Minute oder häufiger) laufen. Jeder dieser Jobs erzeugt eigene Zeilen in cron_schedule, und bei Custom-Modulen, die eigene, sehr häufige Cron-Jobs registrieren, kann die Zeilenanzahl schneller wachsen, als der Standard-Cleanup nachkommt. Ein dritter Grund ist eine falsch konfigurierte oder deaktivierte history_cleanup_every-Einstellung, wodurch abgeschlossene Jobs zwar den Status success erhalten, aber nie tatsächlich gelöscht werden.

3. history_cleanup_every und verwandte Konfiguration

Magento steuert die Bereinigung von cron_schedule über mehrere Parameter in der crontab.xml-Gruppenkonfiguration beziehungsweise über die Admin-Einstellung unter System > Cron. Zentral sind history_cleanup_every (wie oft die Bereinigung läuft, in Minuten), history_success_lifetime (wie lange erfolgreiche Jobs aufbewahrt werden, in Minuten) und history_failure_lifetime (wie lange fehlgeschlagene Jobs aufbewahrt werden). Der Standardwert für history_success_lifetime liegt bei 2880 Minuten (2 Tage), was für die meisten Shops ausreichend ist, aber bei sehr hoher Job-Frequenz zu knapp bemessen sein kann.

Diese Werte lassen sich pro Cron-Gruppe (default, index, consumers) individuell in app/etc/env.php unter dem Schlüssel cron_configuration oder direkt über den Admin-Bereich anpassen, geprüft zum Beispiel mit bin/magento config:show cron_schedule/default/schedule_ahead_for. Eine häufige Fehlkonfiguration ist, dass Entwickler history_cleanup_every beim Debuggen auf einen sehr hohen Wert setzen, um Job-Historie länger einzusehen, und vergessen, den Wert danach zurückzusetzen. Das Ergebnis ist eine cron_schedule-Tabelle, die still und unbemerkt über Wochen wächst.


-- Verify how far back finished job history actually reaches
SELECT job_code, MIN(scheduled_at) AS oldest, MAX(scheduled_at) AS newest, COUNT(*) AS total
FROM cron_schedule
WHERE status = 'success'
GROUP BY job_code
ORDER BY total DESC
LIMIT 10;

4. Manuelle Cleanup-Queries für alte Einträge

Wenn cron_schedule bereits mehrere Millionen Zeilen erreicht hat, reicht das Zurücksetzen der Konfiguration allein nicht aus, weil der reguläre Cleanup-Job selbst nur begrenzte Batches pro Lauf verarbeitet und bei einer stark aufgeblähten Tabelle Wochen bräuchte, um den Rückstau abzuarbeiten. In diesem Fall ist ein manueller, einmaliger Cleanup per SQL der pragmatische Weg, wichtig ist dabei, in kontrollierten Batches zu löschen, um lange Locks auf einer produktiv genutzten Tabelle zu vermeiden.

Vor jedem manuellen Cleanup sollte der Cron-Prozess kurz pausiert werden (bin/magento cron:run darf während der Löschoperation nicht parallel schreiben), und es empfiehlt sich, zunächst nur wenige Tausend Zeilen pro DELETE-Anweisung zu entfernen und den Effekt auf die Systemlast zu beobachten, bevor größere Batches folgen.


-- Manual batched cleanup of old finished cron_schedule entries
-- Run repeatedly until affected rows reach 0, in small batches to avoid long locks
DELETE FROM cron_schedule
WHERE status IN ('success', 'missed')
  AND scheduled_at < DATE_SUB(NOW(), INTERVAL 30 DAY)
LIMIT 5000;

-- Separately clean up long-stuck error entries
DELETE FROM cron_schedule
WHERE status = 'error'
  AND scheduled_at < DATE_SUB(NOW(), INTERVAL 60 DAY)
LIMIT 5000;

-- Reclaim disk space after a large cleanup (run during a maintenance window)
OPTIMIZE TABLE cron_schedule;

5. Symptome einer aufgeblähten Tabelle

Eine aufgeblähte cron_schedule-Tabelle zeigt sich zunächst indirekt: Cron-Läufe dauern spürbar länger, weil bin/magento cron:run bei jedem Aufruf eine Abfrage über alle pending-Zeilen ausführt, um fällige Jobs zu identifizieren. Ist die Tabelle nicht sauber indiziert oder enthält Millionen historischer Zeilen, verlangsamt sich diese Abfrage messbar, was in Summe zu verpassten (missed) Job-Ausführungen führt, weil der nächste Cron-Zyklus bereits beginnt, bevor der vorherige abgeschlossen ist.

Ein zweites Symptom ist Locking bei parallelen Zugriffen: Wenn mehrere Cron-Gruppen gleichzeitig gegen dieselbe stark gewachsene cron_schedule-Tabelle schreiben, steigt die Wahrscheinlichkeit für Lock-Wartezeiten und im Extremfall Deadlocks. In der Praxis äußert sich das oft als scheinbar zufällige Fehler in var/log/cron.log, die auf den ersten Blick nichts mit der Tabellengröße zu tun haben, aber bei genauerer Analyse auf Lock-Timeouts zurückzuführen sind.

6. Analyse: welche Jobs die meisten Einträge erzeugen

Bevor man cron_schedule pauschal bereinigt, lohnt sich eine kurze Analyse, welche job_code-Werte den größten Anteil an der Tabellengröße ausmachen. Häufig sind es nicht die Standard-Magento-Jobs, sondern Custom-Module oder Drittanbieter-Erweiterungen mit sehr kurzen Intervallen, die überproportional viele Zeilen erzeugen. Diese Analyse liefert oft die eigentliche Ursache, nicht nur ein Symptom, das man mit Löschen kaschiert.

Wird ein einzelner Job als Hauptverursacher identifiziert, lohnt sich die Prüfung, ob das konfigurierte Intervall tatsächlich notwendig ist. Ein Synchronisations-Job, der alle 60 Sekunden läuft, obwohl die zugrunde liegende Datenquelle sich nur alle 15 Minuten ändert, erzeugt unnötigen Overhead in cron_schedule und sollte auf ein realistisches Intervall gestreckt werden, statt die Symptome durch immer aggressiveres Löschen zu bekämpfen.


-- Identify the top job_code contributors to cron_schedule row count
SELECT job_code, COUNT(*) AS row_count,
       ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM cron_schedule), 1) AS pct_of_total
FROM cron_schedule
GROUP BY job_code
ORDER BY row_count DESC
LIMIT 15;

7. Best-Practice-Konfiguration je Cron-Gruppe

Magento trennt Cron-Jobs standardmäßig in mehrere Gruppen: default für allgemeine Wartungsjobs, index für Reindex-Läufe und consumers für Message-Queue-Consumer. Jede Gruppe kann eine eigene history_cleanup_every-, history_success_lifetime- und history_failure_lifetime-Konfiguration haben, was gezieltes Tuning erlaubt: Die index-Gruppe mit potenziell sehr vielen kurzlebigen Jobs kann eine kürzere Aufbewahrungszeit vertragen als die default-Gruppe, in der Bestellstatus-Übergänge oder E-Mail-Versand-Jobs liegen, deren Historie für Support-Anfragen länger nützlich ist.

Eine bewährte Ausgangskonfiguration reduziert history_success_lifetime auf 1440 Minuten (24 Stunden) für hochfrequente Gruppen und belässt es bei 2880 Minuten (48 Stunden) für die default-Gruppe, kombiniert mit einem history_cleanup_every von 10 Minuten statt der Standardeinstellung, um Rückstände gar nicht erst entstehen zu lassen. Diese Werte sind Ausgangspunkte, keine Universallösung, die tatsächliche Job-Frequenz jedes Shops sollte die endgültige Konfiguration bestimmen.

8. Monitoring-Skript und Alerting

Ein einfaches Monitoring-Skript, das täglich per Cron läuft, prüft die Gesamtzeilenanzahl von cron_schedule sowie die Anzahl der pending-Einträge, die älter als eine erwartbare Zeitspanne sind. Überschreitet die Gesamtzeilenanzahl einen definierten Schwellenwert, oder gibt es überdurchschnittlich viele pending-Einträge, die eigentlich längst hätten ausgeführt werden sollen, deutet das auf einen hängenden oder inaktiven Cron-Runner hin, der sofortige Aufmerksamkeit braucht.

Ergänzend liefert eine Auswertung der error- und missed-Status über Zeit wertvolle Hinweise auf strukturelle Probleme, etwa wenn ein bestimmter Job systematisch fehlschlägt. Ein solches Monitoring lässt sich leicht in bestehende Alerting-Systeme integrieren, indem das Skript bei Überschreitung der Schwellenwerte eine E-Mail oder eine Nachricht an einen Monitoring-Kanal sendet.


#!/usr/bin/env bash
# cron-schedule-monitor.sh: alert on cron_schedule bloat or stuck runner
set -euo pipefail

THRESHOLD_ROWS=200000
THRESHOLD_STALE_PENDING=500

TOTAL_ROWS=$(mysql -N magento -e "SELECT COUNT(*) FROM cron_schedule")
STALE_PENDING=$(mysql -N magento -e "
  SELECT COUNT(*) FROM cron_schedule
  WHERE status = 'pending' AND scheduled_at < DATE_SUB(NOW(), INTERVAL 30 MINUTE)
")

if (( TOTAL_ROWS > THRESHOLD_ROWS )); then
  echo "[ALERT] cron_schedule has ${TOTAL_ROWS} rows, exceeds threshold" >&2
fi

if (( STALE_PENDING > THRESHOLD_STALE_PENDING )); then
  echo "[ALERT] ${STALE_PENDING} stale pending jobs, cron runner may be stuck" >&2
fi

9. Vorbeugen: Indizes und Wartungsfenster

Magento legt auf cron_schedule standardmäßig Indizes auf job_code, status und scheduled_at an, die für die üblichen Abfragen ausreichen, solange die Tabelle nicht durch Bloat aus dem Ruder läuft. Bei sehr hoher Job-Frequenz kann ein zusätzlicher zusammengesetzter Index auf status und scheduled_at die Performance der Abfrage verbessern, mit der der Cron-Runner fällige Jobs sucht, weil MySQL dann direkt über beide Filterkriterien scannen kann statt nur über eines.

Vorbeugend hilft außerdem ein festes Wartungsfenster, in dem regelmäßig geprüft wird, ob history_cleanup_every noch aktiv und korrekt konfiguriert ist, besonders nach größeren Deployments oder Modul-Updates, die neue Cron-Jobs registrieren könnten. Ein Shop, der diese Prüfung als festen Bestandteil seines Deployment-Prozesses etabliert, verhindert, dass cron_schedule überhaupt erst in einen kritischen Zustand gerät.

Vergleich: Cleanup-Strategien für cron_schedule

Strategie Einsatz Risiko Wirkung
history_cleanup_every Laufender Regelbetrieb Gering Kontinuierlich, verhindert Bloat
Manuelles Batch-DELETE Bereits stark aufgeblähte Tabelle Mittel bei falscher Batch-Größe Sofortige Entlastung
Job-Intervall reduzieren Einzelner Job als Hauptverursacher Gering Behebt Ursache statt Symptom
OPTIMIZE TABLE Nach großem Cleanup Sperrt Tabelle während Lauf Speicherplatz freigeben

Mironsoft

Magento-Cron-Konfiguration und Datenbankwartung

cron_schedule aus dem Ruder gelaufen?

Wir analysieren eure Cron-Jobs, identifizieren die Hauptverursacher, richten eine saubere history_cleanup_every-Konfiguration ein und bauen Monitoring gegen hängende Cron-Runner auf.

Cron-Audit

Job-Frequenz und Hauptverursacher der Tabellengröße identifizieren

Cleanup-Setup

history_cleanup_every je Cron-Gruppe sauber konfigurieren

Monitoring

Alerting gegen hängende oder deaktivierte Cron-Runner

10. Zusammenfassung

Die cron_schedule-Tabelle dient gleichzeitig als Warteschlange und als Ausführungsprotokoll für alle Magento-Cron-Jobs, was ohne aktives Aufräumen zu unkontrolliertem Wachstum führt. Die Standardkonfiguration über history_cleanup_every, history_success_lifetime und history_failure_lifetime reicht für die meisten Shops, sollte aber pro Cron-Gruppe an die tatsächliche Job-Frequenz angepasst werden, besonders bei hochfrequenten Custom-Jobs.

Ist die Tabelle bereits stark aufgebläht, hilft nur ein manueller, batched Cleanup per SQL, gefolgt von OPTIMIZE TABLE zur Speicherplatzfreigabe. Symptome wie langsame Cron-Läufe, verpasste Jobs und Deadlocks lassen sich mit einer Analyse der job_code-Verteilung auf die eigentliche Ursache zurückführen, statt nur das Symptom mit immer aggressiverem Löschen zu bekämpfen. Ein einfaches Monitoring-Skript, das Tabellengröße und veraltete pending-Einträge prüft, verhindert, dass sich das Problem unbemerkt wiederholt.

cron_schedule aufräumen: Das Wichtigste auf einen Blick

Konfiguration

history_cleanup_every, history_success_lifetime und history_failure_lifetime je Cron-Gruppe prüfen.

Manueller Cleanup

Batched DELETE mit LIMIT, gefolgt von OPTIMIZE TABLE zur Speicherplatzfreigabe.

Ursachenanalyse

job_code-Verteilung prüfen, oft ist ein Custom-Job mit zu kurzem Intervall die eigentliche Ursache.

Monitoring

Tägliches Skript gegen Tabellenwachstum und veraltete pending-Einträge einrichten.

11. FAQ: cron_schedule aufräumen

1Warum wächst cron_schedule so schnell?
Vorab angelegte Zeilen pro Job, gelöscht nur über history_cleanup_every. Fällt der Cleanup aus, wächst die Tabelle unkontrolliert.
2Was macht history_cleanup_every?
Definiert die Häufigkeit der Bereinigung in Minuten, zusammen mit den lifetime-Parametern für die Aufbewahrungsdauer.
3Manuelles SQL-Cleanup sicher?
Ja, in kleinen Batches mit LIMIT und ohne parallelen Cron-Schreibzugriff während der Löschung.
4Welche Symptome deuten auf Bloat hin?
Langsame Cron-Läufe, verpasste Jobs, Lock-Wartezeiten und scheinbar zufällige Fehler im Cron-Log.
5Hauptverursacher finden?
GROUP BY job_code sortiert nach Zeilenanzahl, oft Custom-Module mit zu kurzem Intervall.
6Gleiche Lifetime für alle Gruppen?
Nein, hochfrequente Gruppen wie index vertragen kürzere Aufbewahrung als die default-Gruppe.
7Wozu OPTIMIZE TABLE?
Defragmentiert die Tabelle und gibt Speicherplatz frei, sollte im Wartungsfenster laufen.
8Kann Bloat Deadlocks verursachen?
Ja, besonders bei parallelen Cron-Gruppen. Zusammengesetzte Indizes reduzieren das Risiko.
9Wie dauerhaft überwachen?
Tägliches Skript, das Zeilenanzahl und veraltete pending-Einträge prüft und bei Überschreitung alarmiert.
10Wo wird history_cleanup_every konfiguriert?
Admin unter System > Cron oder direkt in app/etc/env.php unter cron_configuration.