Capítulo 13: Tarefas em Segundo Plano (BackgroundTasks) no FastAPI
Especialização em Backend com Python & FastAPI • FastAPI & Python 3.12+ • Pydantic v2, SQLModel, Async/Await e Microsserviços
🗺️ Mapa Conceitual do Tópico
flowchart TD
A["Cliente HTTP (Mobile / Web)"] --> B["Endpoint FastAPI (Handler de Rota)"]
B --> C["Validação do Pedido e Persistência no Banco"]
C --> D["background_tasks.add_task(enviar_webhook, pedido_id)"]
D --> E["FastAPI gera Response HTTP (201 Created)"]
E --> F["Socket de Resposta Enviado ao Cliente"]
subgraph POS_RESPOSTA["Execução Pós-Resposta (Mesmo Processo)"]
F --> G["Loop de Background Tasks (Starlette)"]
G --> H["Executa Função Agendada (Sync em Threadpool ou Async)"]
H --> I{"Houve Exceção?"}
I -->|"Sim"| J["Log de Erro Estruturado / Alerta Sentry"]
I -->|"Não"| K["Conclusão Silenciosa"]
end
subgraph MATRIZ_DECISAO["Matriz de Decisão Arquitetural"]
L{"Exige Persistência ou Retries?"}
L -->|"Não (Best-effort < 3s)"| M["BackgroundTasks Nativas"]
L -->|"Sim (Crítico / Pesado)"| N["Fila Distribuída (Celery / Redis / SQS)"]
end
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style C fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style D fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style E fill:#e0f2f1,stroke:#009688,stroke-width:2px
style F fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
style G fill:#fce4ec,stroke:#e91e63,stroke-width:2px
style H fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
style J fill:#ffebee,stroke:#e53935,stroke-width:2px
style K fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style M fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style N fill:#fff3e0,stroke:#ff9800,stroke-width:2px
🏛️ 1. O que são BackgroundTasks da Starlette no FastAPI
No ciclo de vida de uma requisição HTTP, operações que dependem de terceiros — como envio de e-mails via provedores SMTP (SendGrid, Mailgun), despacho de webhooks ou registro de métricas analíticas — costumam adicionar entre 200ms e 2000ms de latência desnecessária. Fazer o usuário esperar esse tempo para receber uma resposta de sucesso prejudica a experiência e consome conexões concorrentes no servidor.
O FastAPI integra nativamente a classe BackgroundTasks do Starlette. Trata-se de uma fila em memória vinculada à resposta HTTP que adia a execução de funções para o momento imediatamente posterior ao envio dos bytes da resposta pela rede ao cliente.
Tabela Comparativa de Estratégias Assíncronas
| Abordagem | Onde Executa | Sobrevive a Crash / Restart? | Suporte a Retries / Dead-Letter | Caso de Uso Indicado |
|---|---|---|---|---|
BackgroundTasks Nativas |
Mesmo processo da API | ❌ Não (Volátil em RAM) | ❌ Manual via código | Disparo de e-mails rápidos (< 2s), auditoria, logs. |
threading.Thread Avulsa |
Thread desvinculada | ❌ Não (Vaza memória) | ❌ Não rastreável | ⛔ Anti-pattern: Não use em produção. |
| Celery + Redis / RabbitMQ | Processos Workers Dedicados | ✅ Sim (Persistido no Broker) | ✅ Retries exponenciais, DLQ | Tarefas pesadas (> 5s), geração de relatórios, vídeo. |
| Cloud Tasks / AWS SQS | Infraestrutura Serverless | ✅ Sim (Nuvem resiliente) | ✅ Dead-Letter Queue nativa | Arquiteturas orientadas a eventos e microsserviços. |
⚙️ 2. Injeção de Parâmetros e Cuidados com Sessões de Banco
Uma das armadilhas mais graves cometidas por desenvolvedores FastAPI é tentar repassar a sessão do banco de dados da requisição (AsyncSession injetada via Depends) diretamente como parâmetro para uma tarefa em background:
⚠️ Aviso de Falha Crítica: A sessão do banco injetada no endpoint é fechada pelo context manager (
finally: await session.close()) assim que a resposta HTTP é enviada. Quando a função em background tenta utilizar essa mesma sessão, ela falha comStatementError: Cannot operate on a closed session.
O Padrão Correto: Repassar Identificadores e Abrir Nova Sessão
import logging
from typing import Annotated
from fastapi import FastAPI, BackgroundTasks, Depends, status
from sqlmodel.ext.asyncio.session import AsyncSession
from sqlmodel import select
logger = logging.getLogger("worker_background")
app = FastAPI()
# 1. Tarefa de Background independente e resiliente
async def auditar_pedido_background(pedido_id: int) -> None:
"""Abre sua própria sessão de banco isolada do ciclo HTTP da requisição."""
try:
from core.database import async_session_maker
async with async_session_maker() as session:
stmt = select(Pedido).where(Pedido.id == pedido_id)
resultado = await session.exec(stmt)
pedido = resultado.first()
if pedido:
logger.info(f"Auditoria finalizada com sucesso para Pedido #{pedido.id}")
except Exception as exc:
# Erros em background NÃO afetam a resposta HTTP já entregue ao cliente
logger.exception(f"Falha irrecuperável na tarefa em background do Pedido #{pedido_id}: {exc}")
# 2. Endpoint que agenda a tarefa
@app.post("/pedidos/{pedido_id}/confirmar", status_code=status.HTTP_202_ACCEPTED)
async def confirmar_pedido(
pedido_id: int,
background_tasks: BackgroundTasks,
session: Annotated[AsyncSession, Depends(get_db_session)]
):
# Executa a regra de negócio e confirma no banco
stmt = select(Pedido).where(Pedido.id == pedido_id)
resultado = await session.exec(stmt)
pedido = resultado.first()
pedido.status = "CONFIRMADO"
session.add(pedido)
await session.commit()
# Agenda a tarefa passando apenas tipos primitivos (ID do pedido)
background_tasks.add_task(auditar_pedido_background, pedido_id=pedido.id)
return {"status": "ACEITO", "pedido_id": pedido.id}
🔍 3. Limitações Estruturais e Limiares de Migração
Para garantir estabilidade em arquiteturas de microsserviços, considere as seguintes diretrizes para decidir quando BackgroundTasks não são mais suficientes:
- Ausência de Garantia de Entrega (At-Most-Once): Se o container Docker for reciclado (devido a deploy, autoscaler do Kubernetes ou reinício por erro de memória OOM), todas as tarefas agendadas em RAM pendentes de execução são perdidas para sempre.
- Competição por CPU com o Servidor HTTP: Processamentos que exigem cálculo matemático pesado ou manipulação de imagens reduzem a capacidade do Uvicorn de processar requisições de outros usuários, mesmo rodando após o envio da resposta.
- Falta de Visibilidade Operacional: Não existe painel nativo para verificar quantas tarefas estão na fila, taxa de erro ou tempo médio de execução.
Regra Prática: Se o fracasso da tarefa custar dinheiro para o negócio (como emissão de NF-e ou débito em cartão) ou durar mais de 3 segundos, migre obrigatoriamente para uma fila de mensagens dedicada (Celery, RQ ou RabbitMQ).