Cron Magento 2 przestał działać – zalegało ponad 50 000 niezrealizowanych jobów w tabeli cron_schedule. Emaile transakcyjne nie wychodziły, indeksery nie aktualizowały się, reguły cenowe nie odświeżały. Sklep działał normalnie dla klientów ale „od środka” był zamrożony. Czas naprawy: 3 godziny.
Objawy
- Emaile potwierdzenia zamówień przestały dochodzić
- Ceny z reguł katalogowych nieaktualne
- W
var/log/cron.logbrak wpisów od kilku dni SELECT COUNT(*) FROM cron_schedule WHERE status = 'pending'zwraca 52 000- System cron entry dla Magento działa (crontab -l pokazuje wpisy)
Diagnoza
# Sprawdź status tabeli cron_schedule bin/magento cron:status # Sprawdź ile jobów zalega SELECT status, COUNT(*) as cnt FROM cron_schedule GROUP BY status ORDER BY cnt DESC; # Sprawdź ostatnie uruchomienie crona SELECT job_code, status, scheduled_at, executed_at, finished_at, messages FROM cron_schedule WHERE status != 'pending' ORDER BY finished_at DESC LIMIT 20; # Sprawdź czy proces crona PHP działa ps aux | grep "magento cron" # Sprawdź logi systemowego crona grep "magento" /var/log/syslog | tail -50
Przyczyna
Jeden z jobów crona (catalog_product_alert) wchodził w nieskończoną pętlę przy produkcie z uszkodzonymi danymi EAV. Proces PHP zajmował 100% CPU przez kilkadziesiąt minut, a następnie był killowany przez OOM killer. Magento nie oznaczało go jako „failed” – job zostawał w statusie „running” na zawsze. Kolejne uruchomienia crona widziały „running” job i nie uruchamiały nowych instancji.
Rozwiązanie
# Krok 1: Wyczyść zalegające joby bin/magento cron:unlock --all # lub ręcznie SQL:
-- Oznacz stuck "running" joby jako failed
UPDATE cron_schedule
SET status = 'error', messages = 'Manually unlocked - was stuck in running state'
WHERE status = 'running'
AND executed_at < DATE_SUB(NOW(), INTERVAL 2 HOUR);
-- Usuń zalegające pending joby (zostanie tylko kilka ostatnich)
DELETE FROM cron_schedule
WHERE status = 'pending'
AND scheduled_at < DATE_SUB(NOW(), INTERVAL 1 DAY);
-- Usuń stare zakonczone joby (czyść regularnie)
DELETE FROM cron_schedule
WHERE status IN ('success', 'error', 'missed')
AND finished_at < DATE_SUB(NOW(), INTERVAL 7 DAY);
# Krok 2: Znajdź problematyczny job grep -A 5 "catalog_product_alert" var/log/cron.log | tail -30 # Krok 3: Uruchom cron ręcznie i obserwuj bin/magento cron:run --group=default 2>&1 | tail -20 # Krok 4: Dodaj timeout do crona # app/etc/config.php lub przez di.xml: # Każdy job powinien mieć max_retry_count i timeout
<?php
// Własna implementacja crona z timeout
class SafeCronJob
{
public function execute(): void
{
// Ustaw limit czasu wykonania
set_time_limit(300); // 5 minut max
// Przetwarzaj w batchach z checkpoint
$lastId = $this->getCheckpoint();
$processed = 0;
while (true) {
$batch = $this->getBatch($lastId, 100);
if (empty($batch)) break;
foreach ($batch as $item) {
$this->processItem($item);
$lastId = $item->getId();
$processed++;
}
$this->saveCheckpoint($lastId);
// Pozwól innym procesom na CPU
if ($processed % 1000 === 0) {
sleep(1);
}
}
}
}
Prewencja
# Dodaj do crontaba regularne czyszczenie
# */5 * * * * /usr/bin/php /var/www/html/bin/magento cron:run --group=default
# 0 2 * * * /usr/bin/php /var/www/html/bin/magento cron:run --group=cleanup
# Monitoring: alert jeśli pending > 1000 lub running > 30 minut
# Skrypt sprawdzający:
PENDING=$(mysql -u magento -p magento -se "SELECT COUNT(*) FROM cron_schedule WHERE status='pending'")
if [ "$PENDING" -gt 1000 ]; then
echo "ALERT: Cron backlog: $PENDING pending jobs" | mail -s "Magento Cron Alert" admin@sklep.pl
fi
Wnioski
Tabela cron_schedule wymaga regularnego czyszczenia - bez tego rośnie do milionów rekordów i sama w sobie spowalnia crona. Jeden zawieszony job może zablokować wszystkie pozostałe. Monitoring liczby pending jobów to podstawa - próg alertu: powyżej 500 pending to sygnał do zbadania.
