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 SystemO que fazEquivalente em infra
Feature storeCache/storage de dados processados pro modeloRedis/cache layer
Model registryVersionamento de modelos treinadosContainer registry (ACR)
Model servingAPI que serve prediçõesApp server (deployment de API)
Training pipelineProcesso de treinar o modeloCI/CD pipeline
Inference pipelineFluxo de dados pra predição onlineRequest pipeline (middleware chain)
A/B testingComparar versões do modeloCanary deployment
Data driftDados de produção mudando vs treinamentoConfiguration drift
Feature engineeringTransformar dados brutos em inputs pro modeloETL / data transformation

A arquitetura de referência

Um sistema de ML em produção tem mais componentes do que parece:

Arquitetura de referência de um sistema de ML em produçãoDiagrama com três camadas: offline training, online serving e monitoring.OFFLINE (Training)Data Sources(SQL, Blob,Events)ETL/Feature Eng(Spark, DBX)Feature Store(Redis, SQL)Training(GPU)Model RegistryVersionamento e artifactsONLINE (Serving)RequestEntradaHTTP/APIFeatureRetrieval(feature store)Pre-processing(normalize,encode)Model Server(GPU/CPU)Post-proc(threshold,format)ResponseSaída finalMONITORINGLatênciaThroughputData DriftModel AccuracyA/B Results

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

AspectoReal-time (online)Batch (offline)
Latência< 200msMinutos/horas
TriggerRequest HTTPSchedule/evento
ScalingAuto-scale por RPSScale por volume de dados
CustoPay per request/uptimePay per compute time
ExemploFraude em transaçãoScore de crédito mensal
InfraContainer + GPU/CPU always-onSpark 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