Мониторинг периодических задач Celery beat

Обновлено 3 ноября 2026

Celery beat — то, чем большинство проектов на Django и Python запускает периодические задачи: ночные отчёты, очистку, синхронизацию с внешними API. И потерять его незаметно очень легко. Beat — один процесс, который только отправляет задачи в брокер, а выполняют их воркеры. Если beat остановится, ничего не упадёт и в логах не будет ни одной ошибки. Задачи просто перестанут отправляться.

Что ломается

Небольшой помощник для пингов

Положите этот декоратор рядом с задачами. Он сообщает о старте, успехе или ошибке каждого запуска. Неудачный пинг никогда не ломает задачу:

# monitoring.py
import functools
import urllib.request

from celery.exceptions import Retry


def _ping(url, body=None):
    try:
        data = body.encode()[:100_000] if body else None
        urllib.request.urlopen(url, data=data, timeout=10)
    except Exception:
        pass  # мониторинг не должен ломать задачу


def heartcron(url):
    def decorator(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            _ping(url + "/start")
            try:
                result = fn(*args, **kwargs)
            except Retry:
                raise  # повтор — ещё не ошибка
            except Exception as e:
                _ping(url + "/fail", f"{type(e).__name__}: {e}")
                raise
            _ping(url)
            return result
        return wrapper
    return decorator

Оберните задачи

@app.task ставьте снаружи, чтобы Celery зарегистрировал уже обёрнутую функцию:

# tasks.py
from .celery import app
from .monitoring import heartcron


@app.task
@heartcron("https://ping.heartcron.com/<uuid>")
def nightly_report():
    build_and_send_report()

И расписание с явно заданным часовым поясом:

# celery.py
from celery.schedules import crontab

app.conf.timezone = "Europe/Moscow"
app.conf.beat_schedule = {
    "nightly-report": {
        "task": "app.tasks.nightly_report",
        "schedule": crontab(hour=3, minute=0),
    },
}

В Heartcron создайте проверку с cron-выражением 0 3 * * *, тем же часовым поясом и запасом времени чуть больше обычной длительности задачи. Как перевести crontab() из Celery в cron? Его аргументы идут в порядке «минута, час, день недели, день месяца, месяц», а поля cron — «минута, час, день месяца, месяц, день недели». Проверить результат поможет расшифровщик cron-выражений.

Как выглядит каждый исход

СитуацияЧто видит Heartcron
Задача выполнилась успешно/start, затем успех с длительностью
Задача выбросила исключение/fail с текстом ошибки, алерт сразу
Задача зависла/start без успеха, алерт после запаса времени
Лежит beat, брокер или все воркерыНичего не приходит, алерт после запаса времени
Два beat отправляют задачу дваждыДва запуска в журнале там, где ждёте один

Следите за самим beat

По задаче, которая выполняется раз в сутки, пропажу beat заметно только через сутки. Добавьте крошечную задачу раз в пять минут, которая только пингует. Она подтверждает, что работают и beat, и брокер, и хотя бы один воркер:

@app.task
@heartcron("https://ping.heartcron.com/<uuid>")
def heartbeat():
    pass

app.conf.beat_schedule["heartbeat"] = {
    "task": "app.tasks.heartbeat",
    "schedule": 300.0,
}

Её проверке задайте период 5 минут и запас 5–10 минут.

Много задач без множества адресов

Читаемый адрес из ключа пингов проекта и короткого имени избавляет от копирования UUID для каждой задачи. Задайте проверке короткое имя, например sync-stripe, в кабинете или командой heartcron checks create:

@heartcron("https://ping.heartcron.com/<ping-key>/sync-stripe")

Обычные cron-задачи того же проекта, например management-команды Django, можно обернуть без изменений в коде: heartcron run cleanup -- python manage.py clearsessions с открытой утилитой heartcron.

Другие руководства