Tarefas Periodicas (Celery Beat)
Visao Geral
A aplicacao usa Celery Beat com DatabaseScheduler para gerenciar tarefas periodicas. Isso significa que as tarefas sao armazenadas no banco (django_celery_beat_periodictask) e podem ser adicionadas/removidas via admin sem reiniciar containers.
Tarefas Ativas
1. responses.tasks.send_scheduled_surveys
| Atributo | Valor |
| Nome | Enviar pesquisas agendadas |
| Frequencia | A cada 5 minutos |
| Criada por | Migration responses/0005_create_periodic_tasks.py |
| Objetivo | Disparar pesquisas que foram agendadas via is_scheduled=True |
Logica: - Busca runs com is_scheduled=True, scheduled_at <= now(), status='pending', sent_at IS NULL - Para cada, envia o template via Meta Graph API - Atualiza sent_at = now() e is_scheduled = False - Logga sucesso/erro por run
2. responses.tasks.expire_stale_runs
| Atributo | Valor |
| Nome | Expirar pesquisas antigas |
| Frequencia | A cada 1 hora |
| Criada por | Migration responses/0007_create_expire_runs_task.py |
| Objetivo | Marcar como expired runs antigas (vide expiracao-pesquisas.md) |
Logica: Aplica 3 regras de expiracao (A, B, C) em massa.
3. celery.backend_cleanup (built-in do Celery)
| Atributo | Valor |
| Nome | celery.backend_cleanup |
| Frequencia | Padrao do Celery |
| Objetivo | Limpa resultados expirados do backend de resultados |
Operacao
Listar tarefas ativas
| Bash |
|---|
| docker compose exec backend python manage.py shell -c "
from django_celery_beat.models import PeriodicTask
for t in PeriodicTask.objects.all():
print(f' - {t.name} | task={t.task} | enabled={t.enabled} | interval={t.interval}')
"
|
Desabilitar uma tarefa temporariamente
| Bash |
|---|
| docker compose exec backend python manage.py shell -c "
from django_celery_beat.models import PeriodicTask
t = PeriodicTask.objects.get(name='Expirar pesquisas antigas')
t.enabled = False
t.save()
"
|
(Nao precisa reiniciar o Celery Beat -- ele monitora o banco.)
Executar manualmente uma tarefa
| Bash |
|---|
| docker compose exec backend python manage.py shell -c "
from responses.tasks import expire_stale_runs
result = expire_stale_runs.apply().get() # apply() = sincrono
print(result)
"
|
Para executar via worker (assincrono):
| Bash |
|---|
| docker compose exec backend python manage.py shell -c "
from responses.tasks import expire_stale_runs
async_result = expire_stale_runs.delay()
print(async_result.id)
"
|
Ver ultima execucao
| Bash |
|---|
| docker compose exec backend python manage.py shell -c "
from django_celery_beat.models import PeriodicTask
for t in PeriodicTask.objects.all():
print(f'{t.name}:')
print(f' last_run_at: {t.last_run_at}')
print(f' total_run_count: {t.total_run_count}')
"
|
Logs do Celery
Worker (executa as tarefas)
| Bash |
|---|
| docker compose logs celery_worker -f
docker compose logs celery_worker --tail 50
|
Beat (scheduler que dispara as tarefas)
| Bash |
|---|
| docker compose logs celery_beat -f
|
Restart do Celery
Em algumas situacoes (ex: trocou META_ACCESS_TOKEN no .env), pode ser necessario recriar os containers (nao apenas reiniciar):
| Bash |
|---|
| # ❌ NAO faz pegar novo .env
docker compose restart celery_worker celery_beat
# ✅ Recarrega .env (recria container)
docker compose up -d --force-recreate celery_worker celery_beat
|
Adicionar Nova Tarefa Periodica
1. Implementar a task em <app>/tasks.py
| Python |
|---|
| @shared_task(bind=True, max_retries=3)
def minha_nova_task(self):
try:
# logica
return {'status': 'success'}
except Exception as exc:
raise self.retry(exc=exc, countdown=60)
|
2. Criar migration para registrar no Celery Beat
| Python |
|---|
| # <app>/migrations/000X_create_my_task.py
from django.db import migrations
def create_task(apps, schema_editor):
PeriodicTask = apps.get_model('django_celery_beat', 'PeriodicTask')
IntervalSchedule = apps.get_model('django_celery_beat', 'IntervalSchedule')
schedule, _ = IntervalSchedule.objects.get_or_create(
every=30,
period='minutes',
)
PeriodicTask.objects.get_or_create(
name='Minha nova task',
defaults={
'task': 'myapp.tasks.minha_nova_task',
'interval': schedule,
'enabled': True,
'description': 'Descricao do que faz',
}
)
def reverse_task(apps, schema_editor):
PeriodicTask = apps.get_model('django_celery_beat', 'PeriodicTask')
PeriodicTask.objects.filter(name='Minha nova task').delete()
class Migration(migrations.Migration):
dependencies = [
('myapp', '000X-1_previous'),
('django_celery_beat', '__latest__'),
]
operations = [
migrations.RunPython(create_task, reverse_task),
]
|
3. Aplicar migration
| Bash |
|---|
| docker compose exec backend python manage.py migrate
|
4. Reiniciar Beat para garantir que pegou a nova tarefa
| Bash |
|---|
| docker compose restart celery_beat
|
Periodos Suportados (IntervalSchedule.period)
microseconds seconds minutes hours days
Para horarios especificos (ex: "todo dia as 03:00 UTC"), usar CrontabSchedule em vez de IntervalSchedule.
Boas Praticas
- Idempotencia: tarefas devem ser seguras para reexecutar (use filtros que excluem itens ja processados).
- Timeout: respeitar
CELERY_TASK_TIME_LIMIT=600 (10min) configurado. - Retry com backoff: usar
self.retry(exc=exc, countdown=60) em caso de erro temporario. - Logging: usar
logger.info() para sucesso, logger.error() para falhas. Evitar print(). - Bulk operations: usar
.update() em vez de iterar com .save() sempre que possivel. select_related() / prefetch_related() quando a task itera sobre QuerySets com FK.
Troubleshooting
Tarefa nao esta executando
Possiveis causas: 1. enabled=False na PeriodicTask 2. Celery Beat parado (docker compose ps celery_beat) 3. Worker parado (Beat dispara mas worker nao consome) 4. Redis sem conexao (verificar redis://redis:6379/2 no settings) 5. Bug na task fazendo retry infinito (verificar logs)
Tarefa executando mais que o esperado
Verificar total_run_count e last_run_at. Se intervalo muito curto, pode ser: 1. interval.every mal configurado 2. Dois containers de Beat rodando (NUNCA fazer isso)
Migration criando tarefa duplicada
Sempre use get_or_create (nao create) e teste rollback com migrate <app> 000X-1.