O problema real
Um time desenha uma aplicação de IA em serverless (AWS Lambda, Google Cloud Functions). A lógica é simples: requisição chega, função invoca modelo, retorna resposta. Código fica lindo, DevOps fica fácil. Aí chega o dia da verdade: modelo quantizado em 4 bits tem 3,5 GB. Função executa, 30 segundos de cold start (buscando modelo do S3, descompactando), timeout vira. "Por quê é tão lento?" Porque serverless é otimizado pra funções leves, não pra llevar modelos de IA.
Container é o outro extremo: você gerencia tudo. Node, networking, storage, escala. Mas se vir tráfego irregular (10 requests à noite, 1000 ao meio-dia), você paga GPU 24/7 mesmo ociosa. Qual usar?
Cold start: o vilão do serverless
Serverless é estateless. Quando primeira função é invocada num horário, cloud provider instancia uma máquina virtual, baixa o container, executa. Tempo total: 5 a 30 segundos (warm start). Segunda função é mais rápida (3 a 5 segundos). Se esperar 15 minutos, próxima é fria de novo.
Modelo de 7B em quantização 4 bits = 3,5 GB. Primeira invocação:
1. Instanciar sandbox Lambda → 3 segundos
2. Download do S3 3.5GB → 15 segundos (fibra, sem compressão)
3. Decomprimir e carregar → 8 segundos
4. Inicializar framework → 4 segundos
5. TOTAL ANTES DE PROCESSAR → 30 segundos
6. Processar prompt → 200 ms
Cliente vê 30 segundos de latência. Timeout padrão do API Gateway é 30 segundos. Pronto, erro 504.
Solução: aumentar timeout? Máximo em Lambda é 15 minutos. Pode funcionar, mas você paga pelo tempo ocioso, e cada invocação é caro.
Serverless: quando ganha
Tráfego irregular é a situação ideal:
- Chat de suporte ao cliente: 50 mensagens/dia em horário comercial, 0 à noite
- Processamento de documentos: 5 uploads/dia (cada um é um PDF de 50 MB)
- Webhook de terceiros: impredizível, às vezes 0/hora, às vezes 100/hora
Nessas situações:
| Serverless | Container 24/7 |
|---|---|
| 0 reqs → US$ 0 | GPU rodando → US$ 73,60/dia |
| Escala automática | Espera por deploy |
| Paga por invocação | Paga por hora |
| Cold start < 1s (função simples) | Warm (s já está rodando) |
Exemplo numérico: webhook que processa 100 mensagens/dia:
Serverless:
- 100 invocações × US$ 0,0000002 (Lambda) = US$ 0,00002
- 1M free tier: primeira 1M/mês grátis
- Custo: US$ 0
Container on-demand 1h/dia (ligado só quando há trabalho):
- 1 GPU A100, 1h/dia = US$ 3,06
- Custo: US$ 91,80 / mês
Serverless ganha de goleada.
Container: quando ganha
Tráfego previsível e contínuo:
- Chatbot em produção: 10k requests/dia (24/7)
- Batch processing: modelo roda todo dia 2h da manhã
- Streaming: 1M eventos/dia vão pra modelo de classificação
Container ganha porque:
1. Cold start: 0 (container já tá quente)
2. Latência: 100-500ms constante
3. Batch: 10 requisições em 1 chamada = 100x mais throughput
Cenário: 10k requests/dia, 100ms latência média
Serverless + modelagem otimista:
- Warm invocações: 200ms latência
- Cold starts (10% das vezes): 30s latência
- 10% × 30s + 90% × 0.2s = 3,18s latência média
- Custo: 10k × US$ 0,0000002 = US$ 0,002 + storage de modelo = US$ 20/mês
- Total: US$ 20/mês, 3.18s latência média
Container (T4 GPU, US$ 0,35/h, batching):
- Latência: 120ms consistente
- Throughput: 8 requests/segundo em batches de 16 (GPU T4 quantizado)
- Custo: 24h × 30 dias × US$ 0,35 = US$ 252/mês
- Total: US$ 252/mês, 120ms latência
Se latência importa, container vence. Se custo importa e tráfego é bursty, serverless vence.
Limites técnicos do serverless que mata IA
- Timeout: máximo 15 minutos (Lambda). Se modelo leva 1 minuto pra inicializar + 14 minutos processando, você tá no limite. Nenhum espaço pra erro.
- Memória: máximo 10 GB (Lambda). Modelo de 7B requer ~15 GB com overhead. Não cabe.
- Efêmero:
/tmpé apagado entre invocações. Seu cache local some. Toda vez precisa recarregar. - GPU: serverless não oferece GPU nativamente. Precisa de workaround (Lambda com ECS integration).
Containers não têm esses problemas. GPU é nativa, timeout é o que você quiser, memória é contígua.
Arquitetura híbrida: o melhor dos dois mundos
Combine serverless (API simples, rápida) com container (processamento pesado):
┌──────────────────────────────────────────────┐
│ Cliente │
└──────────────┬───────────────────────────────┘
│
┌────────▼─────────┐
│ API Gateway + │
│ Lambda (HTTP) │ ← Serverless: rápido, escalável
│ Valida input │
│ Enfileira job │
└────────┬─────────┘
│
┌────────▼──────────────┐
│ SQS / Pub-Sub │ ← Fila: desacoplamento
│ (armazena job) │
└────────┬──────────────┘
│
┌──────────▼──────────────┐
│ ECS / Kubernetes │ ← Container: GPU, modelo
│ Worker Pool │ (1-10 workers, escala)
│ (processa em batch) │
└──────────┬──────────────┘
│
┌────────▼─────────────┐
│ S3 / DB │ ← Resultado persistido
│ (armazena output) │
└──────────────────────┘
Código do Lambda (Python):
import json
import boto3
import uuid
sqs = boto3.client('sqs')
QUEUE_URL = "https://sqs.sa-east-1.amazonaws.com/123/ml-jobs"
def lambda_handler(event, context):
# Recebe prompt de chat
body = json.loads(event['body'])
prompt = body['prompt']
user_id = body['user_id']
# Valida (rápido)
if len(prompt) > 10000:
return {'statusCode': 400, 'body': 'prompt muito longo'}
# Enfileira (rápido)
job_id = str(uuid.uuid4())
sqs.send_message(
QueueUrl=QUEUE_URL,
MessageBody=json.dumps({
'job_id': job_id,
'prompt': prompt,
'user_id': user_id
})
)
# Retorna imediatamente ao cliente
return {
'statusCode': 202,
'body': json.dumps({'job_id': job_id})
}
Worker (container, roda em GPU):
import json
import boto3
from transformers import pipeline
import threading
import time
sqs = boto3.client('sqs')
dynamodb = boto3.resource('dynamodb')
QUEUE_URL = "..."
results_table = dynamodb.Table('ml-results')
# Carrega modelo UMA VEZ (não a cada job)
nlp = pipeline('text-generation', model='meta-llama/Llama-2-7b-hf',
device=0, torch_dtype='float16')
def process_job(job):
job_id = job['job_id']
prompt = job['prompt']
user_id = job['user_id']
try:
# Processa (model já tá warm)
output = nlp(prompt, max_length=100, num_return_sequences=1)
result = output[0]['generated_text']
# Persiste resultado
results_table.put_item(Item={
'job_id': job_id,
'user_id': user_id,
'result': result,
'status': 'done'
})
except Exception as e:
results_table.put_item(Item={
'job_id': job_id,
'user_id': user_id,
'error': str(e),
'status': 'failed'
})
# Loop: pega jobs da fila, processa
while True:
response = sqs.receive_message(
QueueUrl=QUEUE_URL,
MaxNumberOfMessages=8, # Batch!
WaitTimeSeconds=20
)
if 'Messages' in response:
for message in response['Messages']:
job = json.loads(message['Body'])
process_job(job)
# Deleta da fila
sqs.delete_message(
QueueUrl=QUEUE_URL,
ReceiptHandle=message['ReceiptHandle']
)
else:
# Sem jobs, worker fica escutando (warm)
# Com escala automática, desliga em 5 minutos
pass
Latência: usuário vê resposta em 2 segundos (fila + primeira batch iniciada). Resultado tá pronto em 15 segundos (processamento no container).
Custo (10k requests/dia):
- Lambda: 10k × US$ 0,0000002 = ~US$ 2
- SQS: negligenciável
- Container (2 GPUs T4, 6h/dia rodando): 6 × 30 × US$ 0,70 = US$ 126
- Total: ~US$ 130/mês
Vs. container 24/7: 24 × 30 × US$ 0,70 × 2 = US$ 1.008/mês
Economiza US$ 880.
Armadilhas comuns
Serverless pura com big model: "Rodava localmente", não roda em Lambda. Blame do provider errado.
Container ocioso 99% do tempo: 1 request por dia mas máquina ligada 24/7. Use fila + auto-scale, ou serverless mesmo.
Fila sem DLQ (Dead Letter Queue): job falha 3x, desaparece da fila, ninguém sabe. Sempre configure DLQ pra jobs que falharam.
Quando NÃO usar serverless
Modelos grandes (GPT-3, 70B parameter). Timeout vai explodir.
Tráfego contínuo e previsível. Container é mais barato.
Lógica que requer estado compartilhado entre invocações (cache, conexão aberta). Serverless reset a cada invocação.
Quando NÃO usar container
Tráfego absolutamente irregular (5-10 reqs/dia aleatoriamente). Serverless é mais barato.
Experimentos e POC de curta duração. Não vale orquestração.
Função super simples (validação, transform, webhook). Lambda + serverless é 10x mais rápido de deployar.
Próximos passos
Implemente observabilidade de fila com Observabilidade em aplicações com LLM, rastreando tempo de latência end-to-end.
Gerencie sua infra com Terraform na prática: sua primeira infra versionada, criando filas, workers e auto-scale como código.
Crie worker pool containerizado em Kubernetes e orquestre com Kubernetes para quem vem do Docker Compose, escalando baseado em tamanho da fila.