O time de ML treinou um modelo que funciona no notebook. Accuracy de 94%. Todo mundo comemora. Aí vem a parte menos glamourosa: colocar isso em produção.
“Dá pra colocar isso numa API com 99.9% de uptime, latência < 200ms e 10K requests por segundo?”
É aqui que system design de ML cai no colo de infra. A boa notícia é que a maior parte do problema parece familiar. Serving, rollout, observabilidade e capacity planning continuam sendo trabalho de sistema distribuído. O pedaço realmente diferente existe, mas é menor do que o hype sugere.
O mapa pro profissional de infra
| Conceito ML System | O que faz | Equivalente em infra |
|---|---|---|
| Feature store | Cache/storage de dados processados pro modelo | Redis/cache layer |
| Model registry | Versionamento de modelos treinados | Container registry (ACR) |
| Model serving | API que serve predições | App server (deployment de API) |
| Training pipeline | Processo de treinar o modelo | CI/CD pipeline |
| Inference pipeline | Fluxo de dados pra predição online | Request pipeline (middleware chain) |
| A/B testing | Comparar versões do modelo | Canary deployment |
| Data drift | Dados de produção mudando vs treinamento | Configuration drift |
| Feature engineering | Transformar dados brutos em inputs pro modelo | ETL / data transformation |
A arquitetura de referência
Um sistema de ML em produção tem mais componentes do que parece:
Feature store: o cache que ML ama
Uma feature é um dado processado que o modelo usa como input. Exemplo: pra um modelo de fraude, features podem ser “total de transações nas últimas 24h”, “média de valor por transação”, “número de países diferentes”.
O problema: calcular essas features em tempo real a cada request é caro. Feature store resolve isso separando o jogo em duas camadas: um storage offline pra treino e backfill, e um storage online de baixa latência pro request path.
Sem feature store:
Request → query 5 tabelas SQL → calcular agregações → normalizar → modelo
(latência: 500ms+ só pra montar o input)
Com feature store:
Request → lookup no Redis/cache → modelo
(latência: 5ms pra montar o input)
Na prática, muita arquitetura usa um par: Delta/Parquet/SQL no offline store e Redis no online store. O ponto importante é manter a mesma lógica de feature nos dois lados. Senão você ganha training-serving skew de brinde.
Na prática com Azure
# Azure ML managed feature store ou Redis como feature store
# Opção 1: Redis pra features online
az redis create \
--name ml-feature-store \
--resource-group rg-ml-prod \
--location eastus2 \
--sku Standard \
--vm-size c1
# Pipeline que pre-computa features e popula Redis
# Roda em schedule (a cada hora, por exemplo)
import os
import json
import redis
redis_client = redis.Redis(
host="ml-feature-store.redis.cache.windows.net",
port=6380,
ssl=True,
password=os.environ["REDIS_KEY"],
decode_responses=True
)
# Escrever features (batch job, roda periodicamente)
def update_user_features(user_id, features):
redis_client.set(f"features:user:{user_id}", json.dumps(features), ex=86400)
# Ler features (online, no request path)
def get_user_features(user_id):
data = redis_client.get(f"features:user:{user_id}")
return json.loads(data) if data else None
Model serving: como colocar o modelo atrás de uma API
Existem três padrões principais:
1. Model-as-a-Service (API externa)
Usar Azure OpenAI, GPT-4o, Claude via API. Você não hospeda o modelo.
- Prós: zero ops, escala automática, sempre atualizado
- Contras: latência de rede, custo por token, vendor lock-in, menos controle sobre runtime e tuning
- Quando: LLMs, modelos gerais, prototipação
2. Model-in-Container (self-hosted)
Empacotar o modelo num container e servir via API própria.
# Dockerfile pra serving com FastAPI
FROM python:3.11-slim
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY model/ /app/model/
COPY serve.py /app/
WORKDIR /app
CMD ["uvicorn", "serve:app", "--host", "0.0.0.0", "--port", "8000"]
# serve.py
from fastapi import FastAPI
import pickle
import numpy as np
app = FastAPI()
# Carregar modelo na inicialização (cold start)
with open("model/fraud_detector_v3.pkl", "rb") as f:
model = pickle.load(f)
@app.post("/predict")
async def predict(features: dict):
input_array = np.array([features["values"]])
prediction = model.predict_proba(input_array)[0]
return {"fraud_probability": float(prediction[1])}
@app.get("/health")
async def health():
return {"status": "healthy", "model_version": "v3"}
# Deploy no Azure Container Apps
az containerapp create \
--name ml-fraud-api \
--resource-group rg-ml-prod \
--environment ml-apps-env \
--image acr-ml.azurecr.io/fraud-model:v3 \
--ingress external \
--target-port 8000 \
--cpu 2 --memory 4Gi \
--min-replicas 2 \
--max-replicas 10 \
--scale-rule-name http-rule \
--scale-rule-type http \
--scale-rule-http-concurrency 50
- Prós: controle total, dados ficam no seu ambiente, custo previsível
- Contras: ops overhead, cold start, precisa gerenciar scaling
- Quando: modelos menores (sklearn, XGBoost), requisitos de compliance, latência ultra-baixa
3. GPU Inference Server (modelos grandes)
Pra LLMs open-source ou modelos grandes que precisam de GPU.
# endpoint.yml
$schema: https://azuremlschemas.azureedge.net/latest/managedOnlineEndpoint.schema.json
name: meu-llm-endpoint
auth_mode: key
az ml online-endpoint create \
--file endpoint.yml \
--resource-group rg-ml-prod \
--workspace-name ml-workspace
# llama-deployment.yml
$schema: https://azuremlschemas.azureedge.net/latest/managedOnlineDeployment.schema.json
name: llama-deployment
endpoint_name: meu-llm-endpoint
model: azureml://registries/azureml-meta/models/Meta-Llama-3-8B-Instruct/versions/2
instance_type: Standard_NC24ads_A100_v4
instance_count: 1
# Azure ML Online Endpoint com GPU
az ml online-deployment create \
--name llama-deployment \
--endpoint-name meu-llm-endpoint \
--file llama-deployment.yml \
--all-traffic \
--resource-group rg-ml-prod \
--workspace-name ml-workspace
- Prós: modelos open-source, customização total, sem custo por token
- Contras: GPU caro, cold start longo (minutos), ops complexa
- Quando: fine-tuned models, requisitos de privacidade extremos, volume alto que justifica o custo fixo
A/B testing: canary deployment pra modelos
Quando lança uma nova versão do modelo, você não vai all-in. Rota uma porcentagem do tráfego pra nova versão e compara métricas.
# Azure ML: traffic split entre versões
az ml online-endpoint update \
--name meu-endpoint \
--resource-group rg-ml-prod \
--workspace-name ml-workspace \
--traffic "model-v2=90 model-v3=10"
# Depois de validar que v3 é melhor:
az ml online-endpoint update \
--name meu-endpoint \
--resource-group rg-ml-prod \
--workspace-name ml-workspace \
--traffic "model-v3=100"
Métricas pra comparar durante A/B:
- Qualidade do modelo: accuracy, precision/recall, ou a métrica de negócio equivalente
- Latência: v3 é mais lento? Modelos maiores costumam ser
- Business metrics: taxa de conversão, falsos positivos em fraude, abandono, SLA
Se o ground truth chega atrasado, use proxies online durante o canary e confirme a qualidade real depois, quando os labels aparecerem.
Data drift: quando produção diverge do treinamento
O modelo foi treinado com dados de 2024. Estamos em 2026. O comportamento dos usuários mudou. Isso é data drift, e faz o modelo degradar silenciosamente.
Pensa em configuration drift: seu servidor era Ubuntu 22.04 no deploy, mas 6 meses depois tem pacotes diferentes, configs modificadas manualmente. Mesma ideia.
Detectando drift
from scipy.stats import ks_2samp
import numpy as np
def detect_drift(training_distribution, production_distribution, threshold=0.05):
"""Detecta drift usando Kolmogorov-Smirnov test."""
statistic, p_value = ks_2samp(training_distribution, production_distribution)
drifted = p_value < threshold
return {
"drifted": drifted,
"statistic": statistic,
"p_value": p_value
}
# Exemplo: feature "transaction_amount"
training_amounts = [50, 75, 100, 45, 200, ...] # distribuição no treinamento
production_amounts = [500, 750, 1000, 450, ...] # distribuição atual (inflação?)
result = detect_drift(training_amounts, production_amounts)
# {"drifted": True, "statistic": 0.82, "p_value": 0.0001}
# Alerta: feature mudou significativamente!
KS funciona bem pra features numéricas contínuas. Pra categóricas, eu costumo olhar chi-square, PSI ou Jensen-Shannon.
Monitorando em produção
# Trecho adicionado ao YAML existente do deployment
data_collector:
sampling_rate: 0.1
collections:
request:
enabled: true
response:
enabled: true
# Azure ML: atualizar o deployment com o YAML completo já contendo data_collector
az ml online-deployment update \
--name model-v3 \
--endpoint-name meu-endpoint \
--file model-v3.yml \
--resource-group rg-ml-prod \
--workspace-name ml-workspace
Batch vs Real-time inference
| Aspecto | Real-time (online) | Batch (offline) |
|---|---|---|
| Latência | < 200ms | Minutos/horas |
| Trigger | Request HTTP | Schedule/evento |
| Scaling | Auto-scale por RPS | Scale por volume de dados |
| Custo | Pay per request/uptime | Pay per compute time |
| Exemplo | Fraude em transação | Score de crédito mensal |
| Infra | Container + GPU/CPU always-on | Spark job, Azure ML pipeline |
Na prática, muitos sistemas usam ambos:
- Batch pra pre-computar scores e popular feature store
- Real-time pra scoring final com features fresh
Model registry: o container registry do ML
Assim como você versiona imagens Docker no ACR, modelos são versionados num registry.
# Registrar modelo no Azure ML
az ml model create \
--name fraud-detector \
--version 3 \
--path ./model_artifacts/ \
--resource-group rg-ml-prod \
--workspace-name ml-workspace \
--description "Fraud detection model v3. Trained on 2024Q4 data. AUC: 0.94"
# Listar versões
az ml model list \
--name fraud-detector \
--resource-group rg-ml-prod \
--workspace-name ml-workspace \
--output table
Cada versão tem metadata: quem treinou, com quais dados, quais métricas obteve, qual o hash dos artifacts. Rollback é simplesmente apontar o endpoint pra versão anterior.
O que levar pra segunda-feira
- ML em produção é 90% infra, 10% ML. Feature stores, APIs, monitoring, CI/CD. Tudo que você já sabe.
- Quando há features caras no request path, feature store vira o cache layer mais importante. Sem ele, a latência de inference sobe rápido.
- Model serving segue os mesmos patterns de qualquer API: health checks, autoscaling, blue-green deploys. A diferença é cold start mais longo e uso de GPU.
- A/B testing é canary deployment. Mesma lógica, métricas diferentes (accuracy vs latência).
- Data drift é silent killer. Monitore as distribuições de input, não só métricas de infra.
O próximo post entra em como AI Agents funcionam por dentro: loop, tools, custo e guardrails.
Leitura complementar
- Machine Learning System Design 101 (Neo Kim, System Design Newsletter)
- Azure ML endpoints documentation
- Designing Machine Learning Systems (Chip Huyen, O’Reilly)