Migration from django-celery-beat¶
Goatbeat replaces celery beat and its database scheduler. Migration copies your periodic tasks,
then you switch which process schedules them.
1. Install and migrate¶
pip install "goatbeat[django]" "psycopg[binary]"
Add "goatbeat.django" to INSTALLED_APPS and run python manage.py migrate. Keep
django_celery_beat installed until the end: the importer reads its tables.
2. Preview the import¶
python manage.py goatbeat import_celery_beat --dry-run
Every PeriodicTask is reported as would import (with notes) or skipped (with the reason).
Run it again without --dry-run to write. The import is idempotent: names that already exist in
Goatbeat are skipped.
3. What is converted¶
| django-celery-beat | Goatbeat |
|---|---|
IntervalSchedule(every, period) |
every= interval, anchored on start_time if set |
CrontabSchedule |
cron= with the crontab's timezone |
PeriodicTask.task, args, kwargs |
task path and JSON arguments |
queue, priority |
routing, checked against your Django Tasks backend |
enabled |
enabled (see below) |
SolarSchedule, ClockedSchedule |
skipped: no equivalent |
one_off, expires, expire_seconds, exchange, routing_key, headers |
dropped, reported per task |
start_time on a crontab |
dropped, reported (Goatbeat runs from the next match) |
Rows are never re-synced: a task edited in django-celery-beat after the import keeps its imported
version. Skipped rows include solar and clocked schedules, crontabs restricting both day of month
and day of week (celery requires both to match, cron either), and syntax cron does not accept
(wrap-around ranges such as fri-mon, full day names). A queue or priority your backend refuses
(celery allows priorities up to 255, Django Tasks -100 to 100) is imported disabled with a note.
Interval schedules without start_time are anchored on the Unix epoch, not on the last run: an
hourly task runs at minute 0, a weekly one on Thursdays at 00:00 UTC. Set start_time in
django-celery-beat before importing if that matters.
Imported schedules are store schedules, editable in the admin. Their misfire policy is
run_once, the closest match to celery beat, which also runs a missed task once.
4. Port the tasks¶
Celery task names are not Django tasks. A periodic task whose path is not a
django.tasks.Task is imported disabled, with a note. Port the function to @task under
the same import path (the admin refuses non-Django paths), then enable each schedule.
To keep running the Celery tasks themselves, use goatbeat-celery with the standalone command
instead of Django Tasks:
app = GoatbeatApp(registry=beat, submitter=CelerySubmitter(celery_app))
5. Switch over¶
- Stop
celery beat. - Start
python manage.py goatbeat scheduler(one or more, on any hosts). - Watch
goatbeat statusand the admin's execution history.
Operational differences¶
- Concurrency. celery beat must run exactly once; running two duplicates every task. Goatbeat is safe with any number of schedulers.
- Restarts. celery beat may run a task again or skip it depending on
last_run_at. Goatbeat claims each occurrence once and applies an explicit misfire policy. - Change detection. celery beat polls
PeriodicTasks.last_update; Goatbeat reads due schedules through an index and wakes onLISTEN/NOTIFY. - History. Every occurrence leaves an execution row with its status, error and retry count.