0005. Segregação de Workers Celery por Perfil de Carga
Date: 2026-07-31
Contexto e Problema
O sistema executa diversas tarefas assíncronas em segundo plano usando Celery e Redis:
- Processamento I/O-bound e chamadas de API externas rápidas (envio de e-mails, thumbnails leves, transcrição via API do Deepgram, resumos via API de LLMs).
- Processamento CPU-intensive de mídia local (conversão HLS com FFmpeg, extração de áudio).
- Downloads e streaming de arquivos grandes via
yt-dlp. - Tarefas de IA/ML pesadas com dependências de PyTorch (
Demucspara separação de áudio,Pyannotepara diarização ePiper/Argospara dublagem).
Anteriormente, todas as tarefas comuns rodavam em um único worker genérico. Sob alta carga (por exemplo, múltiplos uploads de vídeos longos), a fila ficava congestionada por tarefas de transcodificação de CPU, atrasando o envio de e-mails e respostas de APIs externas.
Decisão
Decidimos segregar a execução dos workers Celery em múltiplos perfis de carga e filas dedicadas:
Worker Light (
default,celery):- Destinado a tarefas rápidas e I/O-bound (E-mails, Notifications,
transcribe_media_taskvia API Deepgram,summarize_media_taskvia API LLM). - Baixa exigência de CPU/RAM, ideal para rodar em instâncias menores no Fly.io ou PaaS.
- Destinado a tarefas rápidas e I/O-bound (E-mails, Notifications,
Worker Heavy (
heavy):- Destinado a tarefas CPU-intensive de processamento local (
transcode_video_task,extract_audio_task). - Dimensionado com mais vCPUs (ex: VPS dedicada ou nó compute potente).
- Destinado a tarefas CPU-intensive de processamento local (
Worker Import (
media-import):- Destinado ao download de arquivos grandes e mídias via
yt-dlp(import_media_from_url_task). - Mantém downloads lentos isolados sem afetar nem o worker leve nem a transcodificação.
- Destinado ao download de arquivos grandes e mídias via
Workers de IA/ML (
stems,dubbing,diarization):- Mantidos em filas isoladas rodando com ambientes Python isolados (
uv run --isolated --extra ...).
- Mantidos em filas isoladas rodando com ambientes Python isolados (
Consequências
- Melhoria no Tempo de Resposta: Tarefas críticas e leves (como e-mails de confirmação e transcrição por API) não ficam bloqueadas por filas de renderização de vídeo.
- Eficiência de Custos: Permite escalar os containers no Fly.io / VPS de maneira independente (ex: multiplicar instâncias leveis baratas e manter apenas 1 instância forte para transcodificação).
- Manutenibilidade: Mapeamento explícito em
CELERY_TASK_ROUTESnosettings.pye facilidade de depuração com alvos específicos noMakefile(make celery-light,make celery-heavy).
Atualização (2026-08-02)
worker-light ainda misturava tarefas I/O-bound "não-urgentes" (transcrição, resumo, thumbnail, highlights) com tarefas críticas de latência baixa (e-mail/notificação: broadcast, webhook de access group, digest, lembrete/execução de exclusão de conta). Sob concorrência limitada, uma transcrição/highlight lenta podia segurar slot e atrasar notificação.
- Worker Critical (
critical):- Destinado a tarefas curtas cuja latência importa:
send_broadcast_campaign_task,process_access_group_webhook,send_notification_digests,process_deletion_reminders,execute_account_deletion,notify_space_members_of_post. - Isolado do
worker-lightsó por prioridade/latência, não por perfil de recurso (mesma imagem, mesmo tipo de carga I/O-bound).
- Destinado a tarefas curtas cuja latência importa:
Também identificado que docker-compose.prod.yml nunca chegou a implementar a segregação do Worker Import (item 3) nem previa os workers de IA/ML (item 4) — só worker-light/worker-heavy (com media-import indevidamente dentro de worker-heavy) existiam em produção. Corrigido: worker-import agora é serviço próprio em prod; worker-stems/worker-dubbing/worker-diarization foram adicionados como estrutura pronta atrás do Compose profile ml (docker compose --profile ml up -d), não sobem por padrão — a imagem app:latest não inclui os extras ML (torch/demucs/argostranslate/piper/pyannote), decisão de host/imagem dedicada ainda pendente.
Atualização (2026-08-02) parte 2 — reversão de worker-critical/worker-import como processos separados
Depósito é single-operator (baixo volume), não multi-tenant público. worker-light, worker-critical e worker-import (item 3 acima) rodavam como 3 máquinas Fly separadas 24/7 (nenhuma com auto_stop_machines), pagando por isolamento de processo entre filas que, como a própria seção anterior já registrava, "não é por perfil de recurso, mesma imagem, mesmo tipo de carga I/O-bound" — ou seja, a separação existia só por fila/prioridade, não por necessidade real de CPU/RAM/dependência isolada.
Revertido: as 3 filas (default, celery, critical, media-import) voltaram a ser consumidas por um único processo worker-main (-c 8, prefork). Concorrência alta o bastante pra um download yt-dlp ou um highlight lento ocupar só 1 dos 8 slots, sem segurar os demais — o mesmo isolamento lógico que a separação por processo dava, sem o custo de 2 máquinas extras sempre ligadas.
worker-heavy (CPU-bound, satura núcleo real com FFmpeg) e os workers de IA/ML (dependências pesadas isoladas, stems/dubbing/diarization) continuam separados — essas sim têm justificativa de perfil de recurso, não só de fila.
Gatilho pra separar de novo: Flower (make flower) mostrando a fila critical acumulando atrás de default/celery sob carga real — dado observado, não especulação.