FastAPI Background Tasks, Celery ou Arq: Como Escolher o Motor Assíncrono Certo para Seu Backend

Published on: 2025-12-21
Post image
pt fastapi background-tasks celery arq tarefas-assincronas python-async filas-de-tarefas processamento-em-background api-python arquitetura-backend

Aplicações de API costumam começar simples, com respostas rápidas e poucas dependências externas. Com o tempo, surgem necessidades como enviar e-mails, chamar webhooks, registrar eventos em sistemas analíticos e gerar documentos, atividades que podem deixar uma rota HTTP lenta e instável.

Nesse contexto, entram os trabalhos em segundo plano, também chamados de tarefas assíncronas: operações que não precisam terminar antes da resposta HTTP ser devolvida. A escolha entre FastAPI BackgroundTasks, Celery e Arq define o nível de confiabilidade, o esforço operacional e a forma como o sistema escala.

Por que tarefas em segundo plano são necessárias em APIs

Uma rota HTTP é dita síncrona quando executa todo o trabalho antes de responder, mesmo que parte desse trabalho seja lenta. Chamadas a serviços externos, envio de e-mails e geração de relatórios aumentam a latência e pioram métricas como P95, que representa o tempo em que 95% das requisições terminam. Além da lentidão, ocorre maior risco de timeouts e falhas intermitentes de dependências externas. Separar “responder ao usuário” de “executar trabalho demorado” reduz pressão sobre o servidor e melhora previsibilidade. O objetivo passa a ser devolver uma resposta rápida e delegar o restante a um mecanismo apropriado.

Essas tarefas podem ser classificadas como best-effort (aceitável perder ocasionalmente) ou críticas (não podem ser perdidas e exigem reprocessamento). Também podem variar em duração, desde poucos milissegundos até minutos, e em volume, de poucas execuções por dia a milhares por minuto. A ferramenta correta depende principalmente desses fatores. Em geral, quanto maior a criticidade e o volume, maior a necessidade de fila durável e workers dedicados.

FastAPI BackgroundTasks: execução no mesmo processo (in-process)

BackgroundTasks é um recurso embutido do FastAPI para registrar funções que serão executadas após o envio da resposta HTTP. “Mesmo processo” significa que a tarefa roda no mesmo worker que recebeu a requisição, sem broker (intermediário) e sem processos de worker separados. Isso reduz drasticamente a complexidade, pois não há Redis, RabbitMQ ou serviço adicional para administrar. Em contrapartida, a execução depende da saúde do processo do servidor e não fornece garantias fortes de entrega. É um mecanismo leve para pós-processamento simples, com baixa exigência de durabilidade.

A lógica típica é: a rota realiza o mínimo necessário para responder e agenda uma função para rodar depois. O agendamento ocorre durante a requisição, mas a execução só começa quando a resposta já foi enviada. Se o processo reiniciar, travar ou for encerrado, tarefas pendentes podem ser perdidas, pois não existe persistência em fila. Em cenários de carga alta, esse trabalho pode competir por CPU com as requisições, reduzindo a capacidade de resposta do serviço.

O exemplo a seguir mostra o padrão básico, com uma função síncrona simples sendo executada após a resposta. Ele é adequado para logging, métricas “fire-and-forget” e notificações pouco críticas.

from fastapi import FastAPI, BackgroundTasks

app = FastAPI()

def enviar_email_boas_vindas(email: str) -> None:
    # Operação simples e não crítica; roda após a resposta
    print(f"Enviando e-mail de boas-vindas para {email}")

@app.post("/signup")
async def signup(email: str, background_tasks: BackgroundTasks):
    # Persistência do usuário e validações essenciais ocorreriam aqui
    background_tasks.add_task(enviar_email_boas_vindas, email)
    return {"status": "ok"}

Limitações e riscos do BackgroundTasks em produção

A principal limitação é a ausência de durabilidade, isto é, não há armazenamento persistente do trabalho a ser feito. Também não existe um mecanismo nativo de retries (tentativas automáticas em caso de falha), nem controle avançado de tempo máximo de execução por tarefa. A execução está acoplada ao ciclo de vida do servidor web, o que torna tarefas longas especialmente perigosas. Se o serviço roda com múltiplos workers, as tarefas se espalham entre processos sem coordenação central.

Essas características tornam o BackgroundTasks adequado quando a perda eventual é aceitável e o impacto operacional deve ser mínimo. Em contrapartida, ele não substitui uma fila quando há necessidade de rastrear estado, reprocessar falhas e garantir entrega. A fronteira prática costuma aparecer quando e-mails, integrações de pagamento, conciliações ou webhooks precisam de garantia. Nesse ponto, uma fila com workers separados reduz acoplamento e melhora previsibilidade.

Celery: fila distribuída robusta e madura

Celery é um sistema de tarefas distribuídas que usa um broker (como Redis ou RabbitMQ) para enfileirar mensagens e workers separados para consumi-las. Broker é o componente que recebe tarefas da API e as armazena até que algum worker as execute. Muitas arquiteturas também usam um result backend, um armazenamento para estados e resultados, útil para auditoria e inspeção. Esse desenho traz durabilidade, paralelismo e isolamento, pois a API não precisa executar o trabalho pesado. O custo é um aumento significativo de infraestrutura e configuração.

O Celery oferece recursos avançados como retries, confirmações de processamento (acks), limites de tempo, agendamento periódico e composição de fluxos. Também possui ecossistema amplo, com padrões consolidados e ferramentas de observabilidade. Por outro lado, seu núcleo é historicamente orientado a funções síncronas, e o encaixe com pilhas assíncronas pode exigir cuidados. Em troca, torna-se uma escolha comum quando tarefas são críticas, numerosas e exigem governança operacional.

O exemplo a seguir mostra uma configuração mínima com Redis como broker e backend. A API apenas coloca a tarefa na fila, e o worker Celery executa em outro processo.

# celery_app.py
from celery import Celery

celery_app = Celery(
    "meuapp",
    broker="redis://localhost:6379/0",
    backend="redis://localhost:6379/1",
)

@celery_app.task
def enviar_email(email: str) -> None:
    # Lógica de envio de e-mail ocorreria aqui
    print(f"Enviando e-mail para {email}")
# app.py (FastAPI)
from fastapi import FastAPI
from celery_app import enviar_email

app = FastAPI()

@app.post("/signup")
async def signup(email: str):
    # Persistência do usuário ocorreria aqui
    enviar_email.delay(email)  # Enfileira a tarefa no broker
    return {"status": "queued"}

Quando o Celery compensa e quando vira excesso

O Celery compensa quando há exigência de confiabilidade, reprocessamento e controle operacional. Em sistemas com pagamentos, notificações importantes, integrações críticas e grande volume, a possibilidade de retries e inspeção de tarefas reduz incidentes e simplifica auditoria. A presença de múltiplos workers permite escalar o processamento independentemente da API. Esse desacoplamento costuma estabilizar latência e aumentar a resiliência contra dependências externas instáveis. A maturidade do ecossistema também favorece equipes que precisam de soluções previsíveis.

O excesso aparece quando o sistema é pequeno e o esforço operacional supera o ganho. Broker, backend, workers, configuração de serialização e ajustes finos (timeouts, heartbeats, prefetch) exigem monitoramento e manutenção. A complexidade pode ser desproporcional se as tarefas são simples, poucas e não críticas. Nesses casos, adicionar Celery cedo pode criar mais pontos de falha do que remover. Ainda assim, para workflows longos e críticos, ele tende a ser uma opção conservadora e estável.

Arq: workers assíncronos com Redis e asyncio

Arq é uma alternativa focada em asyncio, o mecanismo assíncrono padrão do Python. Ele utiliza Redis como fila e executa tarefas como funções assíncronas, integrando-se naturalmente com aplicações FastAPI e bibliotecas async. Esse alinhamento reduz atrito quando grande parte do código já é assíncrono, como chamadas HTTP com clientes async e acesso a bancos com drivers assíncronos. A proposta é oferecer uma fila durável e workers separados, com menor peso conceitual do que soluções mais abrangentes. Em troca, entrega menos “primitivas” prontas para orquestrações complexas.

Na arquitetura típica, a API coloca um job no Redis e os workers Arq puxam e executam. Redis funciona como armazenamento intermediário e permite sobreviver a reinícios da API, desde que o Redis permaneça disponível. O ecossistema é menor em comparação ao Celery, o que pode influenciar decisões em ambientes muito heterogêneos. Ainda assim, para stacks modernas baseadas em async Python, Arq costuma oferecer uma relação boa entre simplicidade e durabilidade.

O exemplo a seguir mostra um worker Arq com uma função assíncrona, além do endpoint FastAPI que enfileira o job. O nome do job corresponde ao nome da função registrada no worker.

# worker.py
from arq.connections import RedisSettings

async def enviar_email(ctx, email: str) -> None:
    # Chamada assíncrona para um provedor de e-mail ocorreria aqui
    print(f"Enviando e-mail (async) para {email}")

class WorkerSettings:
    functions = [enviar_email]
    redis_settings = RedisSettings(host="localhost", port=6379)
# app.py (FastAPI)
from fastapi import FastAPI
from arq import create_pool
from arq.connections import RedisSettings

app = FastAPI()

@app.on_event("startup")
async def startup() -> None:
    app.state.redis_pool = await create_pool(RedisSettings(host="localhost", port=6379))

@app.post("/signup")
async def signup(email: str):
    # Persistência do usuário ocorreria aqui
    redis = app.state.redis_pool
    await redis.enqueue_job("enviar_email", email)
    return {"status": "queued"}

Trade-offs práticos: durabilidade, retries, escala e operação

As diferenças mais importantes aparecem em quatro dimensões: durabilidade, capacidade de retentativa, escalabilidade e custo operacional. BackgroundTasks não fornece durabilidade e depende do processo da API, sendo melhor para tarefas rápidas e não críticas. Celery e Arq criam uma fila durável com workers separados, melhorando tolerância a falhas e escalabilidade horizontal. Celery tende a oferecer mais recursos nativos para workflows complexos, enquanto Arq tende a encaixar melhor em aplicações fortemente assíncronas. Em ambientes onde Redis já é parte central da stack, Arq pode reduzir o peso de adoção.

O controle de falhas também difere: retries e políticas de execução são centrais em Celery, enquanto Arq oferece um conjunto mais enxuto e orientado a async. Em tarefas que precisam de garantias fortes, a existência de mecanismos prontos para confirmação, reentrega e inspeção pesa bastante. Em tarefas simples, esses mecanismos podem ser desnecessários e custosos. Em todos os casos, a decisão correta é aquela em que o modo de falha esperado é aceitável para o tipo de tarefa.

A comparação a seguir resume o que cada opção tende a entregar em termos de características típicas. Ela mostra a direção geral das escolhas, sem transformar uma ferramenta em substituta universal da outra.

  • FastAPI BackgroundTasks: mínimo de infraestrutura, execução no mesmo processo, baixa durabilidade e poucos controles.
  • Celery: alta robustez, recursos avançados, maior complexidade de operação e integração historicamente síncrona.
  • Arq: fila durável com Redis, funções assíncronas nativas, menor ecossistema e menos orquestração pronta.

Cenários comuns e escolha coerente do “motor” assíncrono

Em um produto inicial com um único serviço FastAPI e pouco tráfego, tarefas como log, métricas simples e webhooks não críticos tendem a funcionar bem com BackgroundTasks. O custo de adicionar infraestrutura pode ser maior do que o benefício, e a perda eventual pode ser tolerável. Em aplicações de consumo com pagamentos, notificações e fluxos que não podem ser perdidos, Celery costuma ser mais adequado por oferecer maior controle e maturidade. Em serviços API-first que já usam asyncio extensivamente e adotam Redis, Arq costuma formar um meio-termo eficiente.

Também existe a possibilidade de coexistência: tarefas best-effort permanecem no BackgroundTasks e tarefas críticas vão para uma fila durável. Essa separação reduz custo e mantém clareza arquitetural, pois cada classe de tarefa recebe o nível de garantia necessário. O que define o corte é a criticidade do efeito colateral, como e-mails obrigatórios, conciliações financeiras ou integrações que exigem reprocessamento. A evolução tende a ocorrer quando o volume e o impacto de falhas ultrapassam o que “rodar no mesmo processo” suporta.

Conclusão

A escolha entre FastAPI BackgroundTasks, Celery e Arq depende menos de popularidade e mais de garantias e operação. BackgroundTasks atende bem quando a tarefa é pequena, rápida e tolera perda, mantendo a API simples e com poucas partes móveis. Celery se destaca quando durabilidade, retries, agendamento e workflows complexos são requisitos, aceitando-se o custo de infraestrutura e configuração. Arq ocupa um espaço forte em stacks modernas assíncronas com Redis, oferecendo durabilidade e simplicidade com uma integração mais natural ao asyncio.

Um sistema saudável tende a alinhar criticidade com o mecanismo de execução: tarefas não críticas permanecem leves, e tarefas críticas migram para filas e workers. Quando o “modo de falha” de uma ferramenta se torna inaceitável, a alternativa mais robusta deixa de ser luxo e passa a ser necessidade. Dessa forma, a decisão correta é a que mantém desempenho, previsibilidade e custo operacional equilibrados com o estágio e as exigências do serviço.