Lab 76 — Pipeline de ML de ponta a ponta
O problema, e a empresa que o tem
Depois do L75, a Cadência tem Model Registry, aprovação bloqueante e rollback medido em 41 segundos. O problema que sobrou é anterior a tudo isso: nada dispara um retreino sozinho. O pipeline de treino do L73 continua funcionando sempre que alguém o roda — mas "alguém rodar manualmente" ainda é a única forma de ele rodar.
A Marcela abre o notebook quando lembra, ou quando alguém do time de suporte comenta no chat que as reclamações de fraude aumentaram. Não existe um sinal formal de "chegou dado novo, retreine" — só a memória dela e o clima informal do time. Os 40 lojistas parceiros da Cadência sobem o lote de transações do dia em horários diferentes, terminando em qualquer momento entre a meia-noite e as 9 da manhã seguinte, e nada observa quando o último deles termina.
Um levantamento do histórico de execuções do notebook mostrou
14 execuções em 11 meses — média de 23 dias entre um lote novo ficar disponível e o retrain acontecer, e um hiato de 5 semanas em que o padrão de fraude mudou e ninguém retreinou nada, porque ninguém "sentiu" que era hora.
Em março, o segundo tipo de falha apareceu: a Marcela rodou o retrain de madrugada sem saber que três dos 40 lojistas ainda não tinham terminado de subir o lote do dia anterior. O modelo treinou contra 71% do dado que deveria ter — sem nenhum erro, porque nada verificava se o lote estava completo antes de começar. O Model Registry do L75 registrou uma AUC pior que a anterior, o revisor rejeitou a promoção, e o time gastou uma tarde inteira investigando um "modelo ruim" que na verdade era um dado incompleto.
Disparo automático e promoção com aprovação, lado a lado
O L75 resolveu o que acontece DEPOIS de um treino terminar — versão, aprovação, rollback. Este laboratório resolve o que acontece ANTES: o que decide que é hora de treinar, e a partir de qual dado. O candidato que este laboratório registra entra no MESMO estágio de aprovação bloqueante do L75, sem mudar uma linha daquele desenho — a decisão de promover continua sendo humana.
O que este laboratório NÃO é
Não é sobre como aprovar ou reverter uma promoção — isso é o L75, e continua exatamente como está. Também não é sobre detectar QUE o modelo em produção está degradando — isso é drift, e é o L77, que reaproveita o mesmo Model Registry para saber qual versão está servindo quando o alarme dispara. Aqui a pergunta é uma só: como o retreino deixa de depender de alguém lembrar, sem reprocessar o que não mudou, e com registro de qual dado gerou qual modelo.
O que você vai conseguir fazer
Objetivos verificáveis: cada um se prova com um comando na seção de implantação, não com a sensação de ter entendido SageMaker Pipelines.
- Explicar por que um cron job simples não resolve "disparado por dado novo" — ele dispara por TEMPO, não por conteúdo, e não distingue lote completo de lote pela metade.
- Configurar uma regra do EventBridge que reage a um manifesto de lote completo no S3, não a qualquer escrita de objeto no bucket bruto.
- Definir um SageMaker Pipeline com múltiplos passos — validar, calcular features, treinar, avaliar — cada um com CacheConfig independente.
- Explicar o que a chave de cache compara de fato (URI de dataset, hiperparâmetro, imagem do container), e por que um passo pode "pular" mesmo com o pipeline inteiro executando de novo.
- Orquestrar a execução do pipeline a partir de uma máquina de estados do Step Functions, com Retry e Catch em volta da chamada — reaproveitando a lição do L25 num nível acima.
- Anexar linhagem — hash do lote de origem, ARN da execução — ao Model Package que o L75 já promove, sem duplicar nada que o L75 já faz.
- Medir, com números, o tempo entre o lote novo ficar completo e um candidato aparecer no Model Registry — antes (manual) e depois (automático).
- Diagnosticar se uma execução que não economizou tempo nenhum é falha de cache ou disparo em cima de um lote ainda incompleto.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| Step de um SageMaker Pipeline | MLA-C01 | ProcessingStep, TrainingStep e ConditionStep encadeados | a diferença entre um Step com CacheConfig e um sem — o segundo sempre reexecuta |
| CacheConfig e chave de cache | MLA-C01 | hash de código + parâmetro + URI de dataset decide pular ou rodar | o cache compara a ENTRADA declarada, não o relógio — duas execuções no mesmo minuto com dado diferente não usam cache uma da outra |
| Lineage Tracking do SageMaker | MLA-C01, MLS-C01 | ARN da execução e hash do lote anexados ao Model Package do L75 | linhagem responde "de qual dado veio este modelo" — quem APROVOU continua sendo pergunta do L75 |
| EventBridge reagindo a evento de dado (S3) | MLA-C01, DOP-C02 | regra casa com o PutObject do MANIFESTO, não do lote inteiro | reagir ao primeiro objeto do lote bruto dispara cedo demais, antes dele terminar de subir |
| Step Functions orquestrando um serviço integrado (`.sync`) | DOP-C02, SAP-C02 | StartPipelineExecution chamado com integração `.sync`, esperando o pipeline terminar | sem `.sync`, o Step Functions "acha" que terminou no instante em que disparou a execução, não quando ela de fato concluiu |
| Retry/Catch num orquestrador de nível superior | DVA-C02, SAP-C02 | o mesmo padrão do L25, aplicado à falha de INICIAR ou monitorar o pipeline | falha de infraestrutura ao chamar a API é diferente de falha de um step interno do pipeline — cada uma pede um tratamento |
| Execução idempotente por nome | MLA-C01 | nome da execução do Step Functions = hash do manifesto do lote | uma reentrega do mesmo evento (comum em EventBridge) não inicia uma segunda execução |
Onde isto costuma ser cobrado errado
A pergunta clássica descreve um pipeline com cache habilitado em todos os passos, e pergunta por que um passo específico rodou de novo mesmo sem ninguém ter mudado o código dele. A resposta não é "o cache está quebrado": a chave de cache compara TODAS as entradas declaradas do step — incluindo hiperparâmetro e URI de dataset propagados de um step anterior — não só o código-fonte daquele step específico. Se qualquer entrada mudar, mesmo vinda de outro lugar do pipeline, o cache invalida e reexecuta.
Requisitos, e como cada um muda o desenho
Requisito que não aparece numa condição de EventBridge ou numa linha de CacheConfig é intenção. A coluna da direita é onde cada um deixou marca.
| Requisito | Valor declarado | O que ele decide no desenho |
|---|---|---|
| Disparo por dado novo, sem depender de lembrança | obrigatório | EventBridge reage a um manifesto de lote completo, não a cron nem a chamada manual |
| Não reprocessar o que não mudou | obrigatório | CacheConfig por Step do SageMaker Pipelines, chave = hash de código + parâmetro + dado |
| Não retreinar com lote incompleto | obrigatório, aprendido do incidente de março | passo ValidarLote confere contagem de registros contra o manifesto ANTES de calcular features |
| Linhagem completa: de qual dado veio qual modelo | obrigatório | hash do lote e ARN da execução anexados ao Model Package — extensão da metadata do L75 |
| Falha de infraestrutura não passa em silêncio | herdado do L25 | Step Functions com Retry/Catch em volta de StartPipelineExecution, notificando se a orquestração falhar |
| Aprovação humana antes de servir tráfego | herdado do L75, sem mudança | o candidato registrado entra no MESMO estágio de aprovação bloqueante — este laboratório não reabre essa decisão |
| Volume atual: um lote por dia, até 40 lojistas | não justifica paralelismo agressivo | um pipeline, uma execução por lote — Map state paralelo por lojista é o nível 3 da evolução, não este módulo |
Arquitetura mínima: o retreino que só acontece se alguém lembrar
Este é o desenho que a Cadência tem hoje, mesmo depois do L75 — e ele publica de verdade: o treino roda, o Model Registry recebe o pacote, a aprovação bloqueia até decisão humana. O laboratório começa por tornar visível o que falta, não por presumir que é óbvio.
- → ninguém observa formalmente quando o lote fica completo
- → sinal informal, às vezes semanas depois do lote mudar de padrão
- → ela roda contra o lote mais recente, sem verificar se terminou de chegar
- → registra só se ela lembrar de rodar create_model_package no fim
- Armazenamento
- Fora da AWS
- IA e machine learning
- Conceito de arquitetura
Este desenho publica de verdade — o L73 treina e o L75 promove com aprovação. O que falta não é nenhum desses dois: é qualquer coisa entre "o lote de hoje terminou de subir" e "o notebook rodou". Percorra os passos e repare que o único sensor desse sistema é a memória de uma pessoa.
- O lote chega sem ninguém observando. Os 40 lojistas terminam de subir o dia entre meia-noite e 9 da manhã seguinte. Não existe um sensor de "lote completo" — só o bucket recebendo objetos.
- O sinal de que algo mudou é informal. Alguém do suporte comenta no chat que as reclamações de fraude aumentaram — às vezes semanas depois do padrão real ter mudado, porque não há métrica automática ligando os dois.
- O lote também chega sem gatilho formal para a decisão. A Marcela decide de memória que é hora de rodar de novo. Não há um checklist nem um sinal do sistema — só o julgamento dela, com a informação que ela tiver na cabeça naquele dia.
- Ela roda o notebook contra o lote mais recente, sem verificar se terminou. É exatamente o que causou o incidente de março: o retrain começou contra 71% do lote do dia, porque três dos 40 lojistas ainda estavam subindo dado quando o notebook começou a ler o bucket.
- O treino roda igual ao do L73 — o problema não é o treino. O job em si é reproduzível, mede AUC contra o holdout e produz um artifact válido. O defeito inteiro está ANTES desta caixa, não dentro dela.
- Se ela lembrar, o pacote chega ao Model Registry do L75. Quando o treino termina, ela roda create_model_package manualmente. O L75 cuida do resto — aprovação, blue/green, rollback — mas só a partir daqui, e só se alguém chegou até aqui.
- Nada mede quanto tempo passou desde o último lote novo. Não existe painel, não existe alarme de "N dias sem retreino". O hiato de 5 semanas só apareceu porque alguém foi olhar o histórico do notebook depois do fato.
# auditar-o-historico.sh -- meça a exposicao real antes de mudar qualquer coisa
grep -c 'FunctionHandler iniciado' notebook-execucoes.log
# 14 -- catorze execucoes do notebook em 11 meses
python3 medir_hiatos.py notebook-execucoes.log
# hiato medio entre lotes novos e retrain: 23 dias
# maior hiato observado: 35 dias (5 semanas) sem nenhum retreino
# O incidente de marco: retrain iniciado as 03:14, com 3 dos 40 lojistas ainda
# sem terminar de subir o lote do dia anterior -- o job treinou contra 71% do
# volume esperado, sem erro nenhum, porque nada verificava completude.O incidente que o Model Registry pegou, mas não explicou
O retrain de março não falhou por bug de código: falhou porque nada respondia à pergunta "o lote de hoje já terminou de chegar?" antes de começar a ler o bucket. O modelo treinado contra 71% do dado teve AUC pior que o anterior — o L75 rejeitou a promoção corretamente — mas o time gastou uma tarde inteira investigando um "modelo ruim" que na verdade era um dado incompleto, porque não havia nenhum sinal apontando para a causa real.
Arquitetura para produção: pipeline disparado pelo dado, com cache e linhagem
Cada peça nova abaixo rastreia a uma linha da tabela de requisitos. O treino do L73 e a promoção do L75 não mudam nada aqui — o que muda é tudo o que decide QUANDO rodar o treino, e o que ele carrega quando termina.
- → PutObject do manifesto de lote completo (S3 → EventBridge)
- → dispara a execução, nome = hash do manifesto
- → StartPipelineExecution.sync, primeiro passo do DAG
- → lote validado, próximo passo
- → features calculadas (ou reaproveitadas do cache)
- → modelo candidato treinado (ou reaproveitado do cache)
- → ConditionStep: métrica aceitável → RegisterModel com linhagem
- → cada transição e cada resultado de cache viram evento no histórico
- → métrica publicada por execução, cache-hit por passo
- Armazenamento
- Integração de apps
- IA e machine learning
- Conceito de arquitetura
- Gestão e governança
A diferença central não é "mais uma caixa": é que o retreino passa a existir porque um LOTE ficou completo, não porque uma pessoa lembrou — e cada passo caro só roda de novo se a entrada dele mudou de verdade. Percorra os passos: o gatilho, o cache e a linhagem protegem coisas diferentes, e a aprovação do L75 continua no fim, sem mudar.
- O lote termina de chegar, e isso vira um evento — não uma lembrança. O processo que recebe o upload dos 40 lojistas grava um manifesto só depois que o último termina. É esse arquivo, com contagem de registros e timestamp, que o resto do desenho espera — não o primeiro objeto do bucket bruto.
- A regra do EventBridge dispara sem ninguém rodar comando. O padrão do evento casa especificamente com o sufixo do arquivo de manifesto. Uma escrita qualquer no bucket de dado bruto — inclusive um upload parcial de um lojista — não casa com a regra e não dispara nada.
- A máquina de estados inicia o pipeline com nome idempotente. O nome da execução do Step Functions é o hash do manifesto. Uma reentrega do mesmo evento — comum no EventBridge — tenta iniciar com o mesmo nome e é recusada: sem tabela de idempotência escrita à mão.
- ValidarLote confere completude antes de qualquer coisa cara acontecer. Este passo não tem CacheConfig de propósito — ele SEMPRE roda, porque é justamente ele que teria pegado o incidente de março: contagem de registros do lote contra o número declarado no manifesto.
- CalcularFeatures pula se o hash do lote não mudou desde a última execução aprovada. O CacheConfig deste passo compara a URI do dataset de entrada e o código de cálculo de feature. Se os dois já produziram este resultado antes, o SageMaker reaproveita a saída em vez de recalcular — o passo aparece como CacheHit no histórico.
- TreinarModelo é o passo mais caro, e também o mais provável de usar cache. Se o lote e as features não mudaram desde a última execução, treinar de novo produziria exatamente o mesmo modelo — o CacheConfig evita a instância de treino inteira, que é o item que mais pesa na fatura deste pipeline.
- AvaliarCandidato decide, e só registra se valer a pena. Um ConditionStep compara a métrica do candidato contra o baseline do L75. Se não melhora, a execução termina SEM criar um Model Package — o Registry não enche de candidatos que ninguém vai aprovar.
- O que chega ao Registry carrega a linhagem, e o histórico mostra o cache. O Model Package recebe o hash do lote e o ARN da execução como metadata — a resposta a "de qual dado veio este modelo". Em paralelo, o CloudWatch guarda qual passo usou cache em cada execução, para auditoria de custo e de comportamento.
A diferença estrutural em relação ao desenho anterior não é "a mesma coisa com mais caixas": é que existir um evento formal de "lote completo", um cache por passo e uma linhagem anexada ao pacote. No desenho mínimo, "dado pronto" e "alguém lembrar" eram a mesma coisa. Aqui, o dado pronto É o gatilho — e cada passo caro só roda de novo se a entrada dele, especificamente, mudou.
O que o cache de passo não pega sozinho
A chave de cache do SageMaker Pipelines é o hash da ENTRADA declarada do step — URI do dataset, hiperparâmetro, imagem do container — não um hash do CONTEÚDO do objeto no S3 quando a URI é sempre a mesma. Se alguém sobrescrever o mesmo caminho do S3 com um lote diferente — o mesmo hábito que causou o incidente do L75 com o artifact do modelo — o pipeline pode usar um cache indevido e treinar achando que está usando o dado de sempre, quando na verdade o dado mudou por baixo.
O que fica exatamente igual, de propósito
O estágio de aprovação e o rollback do L75 não mudam uma linha: o candidato registrado por este pipeline entra no MESMO Model Package Group, com o MESMO revisor decidindo, com o MESMO blue/green por trás. Redesenhar o gatilho e a promoção ao mesmo tempo é o jeito mais comum de gastar uma tarde depurando o problema errado — o L75 continua sendo a autoridade sobre quando um modelo serve tráfego real.
O caminho de uma execução, ponta a ponta
O metadata de linhagem não é burocracia: é o que responde "de qual dado veio este modelo" sem precisar reconstruir a história a partir de log de texto — a mesma lição do L25, aplicada a dado em vez de a pedido.
Por que a mesma pipeline leva tempos tão diferentes
Um lote sem nenhuma mudança de features gera um cache-hit duplo — Features e Treinar pulam os dois — e a execução inteira roda em poucos minutos, dominada por ValidarLote e AvaliarCandidato. Um lote com dado genuinamente novo passa pelos dois passos caros de verdade, e a execução pode levar dezenas de minutos: a diferença entre os dois cenários É o cache funcionando, não um sinal de problema.
manifesto-lote.json -- o objeto que o EventBridge observa. O campo "contagemEsperada" e o que ValidarLote confere contra a contagem real.
{
"loteId": "2026-08-08",
"lojistasEsperados": 40,
"lojistasRecebidos": 40,
"contagemEsperada": 128430,
"geradoEm": "2026-08-08T09:14:02Z"
}model-package-com-linhagem.json -- extensao da metadata do L75. Os dois primeiros campos (auc_offline, baseline_producao_auc) sao do L75; os tres ultimos sao o que este laboratorio acrescenta.
{
"ModelPackageGroupName": "fraude-checkout",
"ModelApprovalStatus": "PendingManualApproval",
"CustomerMetadataProperties": {
"auc_offline": "0.934",
"baseline_producao_auc": "0.931",
"lote_origem_id": "2026-08-08",
"lote_hash": "a7f0c9e1b3d4",
"execucao_pipeline_arn": "arn:aws:sagemaker:...:pipeline/fraude-checkout/execution/8821"
}
}As decisões, e o que se perde em cada uma
📋 Disparar o retreino de um modelo de risco de fraude sempre que um lote de dado novo terminar de chegar, sem reprocessar o que não mudou, e com registro rastreável de qual lote gerou qual modelo — sem reabrir a decisão de aprovação e rollback que o L75 já resolve.
Cada serviço resolve a parte que sabe resolver nativamente: Pipelines cuida do cache e da linhagem de ML sem código extra; Step Functions cuida de orquestrar uma chamada que pode falhar por infraestrutura, reaproveitando exatamente o padrão do L25; EventBridge cuida de reagir a CONTEÚDO em vez de a relógio. Nenhum dos três sozinho cobre as três necessidades.
Alt: Cron job horário chamando o script de treino direto — Dispara por TEMPO, não por dado — continua vulnerável ao mesmo problema do lote incompleto se rodar no meio de um upload, e reprocessa tudo sempre, sem cache nenhum.
Alt: Tudo em Step Functions com Lambdas customizados, sem SageMaker Pipelines — Reimplementa cache e lineage tracking à mão — o que o SageMaker Pipelines já resolve nativamente — só para trocar de fornecedor de orquestração num nível que não precisa mudar.
Alt: EventBridge chamando SageMaker Pipelines direto, sem Step Functions — Perde o Retry/Catch declarativo em volta de uma chamada que pode falhar por throttling ou expirar — a mesma lição do L25, agora sobre a chamada que INICIA o pipeline, não sobre um passo dele.
Alt: Apache Airflow / Amazon MWAA — Resolve orquestração genérica, mas exige infraestrutura própria para manter (scheduler, workers) e não tem o cache de step nem a linhagem nativos do SageMaker Pipelines — reconstruiria em outro lugar o que os dois serviços gerenciados já entregam.
| Decisão | Escolha | Alternativas | Motivo | O que se perde |
|---|---|---|---|---|
| Motor do DAG de ML | SageMaker Pipelines | Step Functions puro com Lambdas customizados; Airflow/MWAA | cache nativo por passo e lineage tracking sem código extra | acopla ao SageMaker — migrar de provedor de nuvem custaria reescrever o DAG inteiro |
| Orquestrador de nível superior | Step Functions (`.sync`) chamando StartPipelineExecution | chamar a API direto de um Lambda; EventBridge chamando Pipelines sem camada intermediária | Retry/Catch declarativo em volta de uma chamada que pode falhar, sem reescrever a lógica do L25 | mais um componente para versionar, mesmo que pequeno |
| Gatilho | EventBridge reagindo ao manifesto de lote completo | cron horário; polling do bucket a cada N minutos | reage ao CONTEÚDO (lote pronto), não ao relógio nem a um loop gastando chamadas de API | depende do processo upstream escrever o manifesto de forma confiável |
| Granularidade do cache | por Step (CacheConfig individual) | cache do pipeline inteiro (tudo ou nada) | um lote pequeno pode pular o treino sem pular a avaliação — economia proporcional ao que de fato mudou | mais configuração por step, e cache mal configurado é um erro sutil de diagnosticar |
| Nome da execução | hash do manifesto do lote | UUID aleatório | idempotência: reentrega do mesmo evento não inicia execução duplicada | nenhuma; é estritamente melhor aqui |
A dívida que este laboratório não paga
Este laboratório não decide QUANTO dado precisa mudar para justificar um retreino completo — ele decide COMO disparar quando o lote muda, e como não reprocessar o que não mudou. Se o volume de lotes crescer a ponto de um retreino diário deixar de fazer sentido economicamente, a resposta é uma condição de negócio antes do gatilho (por exemplo, exigir uma janela mínima de acúmulo), não uma peça de infraestrutura nova — e é decisão do time de risco, não deste desenho.
Construir: a pipeline do SageMaker, com cache por passo
Esta é a peça que faz o retreino deixar de reprocessar o que não mudou. Tudo o que vem depois — orquestração, gatilho — existe para decidir QUANDO chamar esta pipeline, não para refazer o que ela já faz sozinha.
# definir_pipeline.py -- define o SageMaker Pipeline com cache por passo.
# Reaproveita o estimador de treino do L73 e o feature store do L72; a novidade
# aqui e o CacheConfig em cada step caro, e o ConditionStep que so registra no
# Model Registry do L75 se a metrica justificar.
from sagemaker.workflow.pipeline import Pipeline
from sagemaker.workflow.steps import ProcessingStep, TrainingStep, CacheConfig
from sagemaker.workflow.condition_step import ConditionStep
from sagemaker.workflow.conditions import ConditionGreaterThanOrEqualTo
from sagemaker.workflow.functions import JsonGet
from sagemaker.workflow.parameters import ParameterString
from sagemaker.workflow.model_step import ModelStep
# Cache expira em 30 dias -- alem disso, forca reexecucao mesmo sem mudanca de
# entrada, porque codigo de dependencia (bibliotecas, imagem base) pode ter
# mudado sem aparecer no hash das entradas declaradas.
CACHE = CacheConfig(enable_caching=True, expire_after="30d")
manifesto_uri = ParameterString(name="ManifestoLoteUri") # injetado pelo Step Functions
passo_validar = ProcessingStep(
name="ValidarLote",
processor=validador_processor, # confere contagem contra o manifesto
inputs=[...],
outputs=[...],
job_arguments=["--manifesto", manifesto_uri],
# SEM CacheConfig de proposito: este passo tem de rodar sempre, e e barato.
)
passo_features = ProcessingStep(
name="CalcularFeatures",
processor=feature_processor, # reaproveita o feature store do L72
inputs=[...],
outputs=[...],
cache_config=CACHE,
depends_on=[passo_validar],
)
passo_treinar = TrainingStep(
name="TreinarModelo",
estimator=estimador_l73, # o mesmo estimador reprodutivel do L73
inputs={"train": passo_features.properties.ProcessingOutputConfig.Outputs["train"].S3Output.S3Uri},
cache_config=CACHE, # o passo mais caro -- e o que mais ganha com cache-hit
)
passo_avaliar = ProcessingStep(
name="AvaliarCandidato",
processor=avaliador_processor,
inputs={"modelo": passo_treinar.properties.ModelArtifacts.S3ModelArtifacts},
outputs=[...],
# sem cache: comparar contra o baseline do L75 e barato e tem de ser fresco.
)
passo_registrar = ModelStep(
name="RegistrarCandidato",
step_args=modelo_l73.register(
model_package_group_name="fraude-checkout", # o MESMO grupo do L75
approval_status="PendingManualApproval",
customer_metadata_properties={
"lote_origem_id": manifesto_uri,
"execucao_pipeline_arn": ExecutionVariables.PIPELINE_EXECUTION_ARN,
},
),
)
condicao_registrar = ConditionStep(
name="MetricaJustificaRegistro",
conditions=[ConditionGreaterThanOrEqualTo(
left=JsonGet(step_name="AvaliarCandidato", property_file=..., json_path="auc"),
right=0.912, # baseline vigente, lido do Model Registry do L75
)],
if_steps=[passo_registrar],
else_steps=[], # execucao termina sem candidato -- nao enche o Registry a toa
)
pipeline = Pipeline(
name="fraude-checkout-retreino",
parameters=[manifesto_uri],
steps=[passo_validar, passo_features, passo_treinar, passo_avaliar, condicao_registrar],
)
pipeline.upsert(role_arn=role_pipeline_arn)
Por que o cache tem prazo de validade
`expire_after="30d"` existe porque a chave de cache do SageMaker compara ENTRADAS declaradas — dataset, hiperparâmetro, imagem do container — e nada nela detecta uma mudança de dependência que não aparece nessas entradas (uma biblioteca Python atualizada na imagem base, por exemplo). Forçar reexecução periódica é a rede de segurança contra esse tipo de deriva silenciosa que o hash não captura.
Construir: a máquina de estados que orquestra a execução
O `.sync` é o detalhe que separa "disparei o pipeline" de "o pipeline terminou". Sem ele, o Step Functions marcaria sucesso no instante em que a chamada de API retornasse — muito antes do treino de verdade acontecer.
orquestrar_retreino.asl.json -- o padrao do L25 (Retry/Catch declarativo) aplicado a uma chamada de orquestracao de nivel superior, nao a um passo de negocio.
{
"Comment": "Dispara e monitora a execucao do SageMaker Pipeline de retreino",
"StartAt": "IniciarPipeline",
"States": {
"IniciarPipeline": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:sagemaker:startPipelineExecution.sync",
"Parameters": {
"PipelineName": "fraude-checkout-retreino",
"PipelineExecutionDisplayName.$": "$.loteId",
"PipelineParameters": [
{ "Name": "ManifestoLoteUri", "Value.$": "$.manifestoUri" }
]
},
"Retry": [
{
"ErrorEquals": ["SageMaker.AmazonSageMakerException"],
"IntervalSeconds": 5,
"BackoffRate": 2,
"MaxAttempts": 3,
"MaxDelaySeconds": 60
}
],
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"ResultPath": "$.erro",
"Next": "NotificarFalhaDeOrquestracao"
}
],
"Next": "PipelineConcluida"
},
"PipelineConcluida": {
"Type": "Succeed"
},
"NotificarFalhaDeOrquestracao": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish",
"Parameters": {
"TopicArn": "${sns_falha_orquestracao_arn}",
"Message.$": "$.erro"
},
"End": true
}
}
}# state_machine.tf -- a maquina de estados, o log group que ela escreve, e a
# role que ela assume para chamar o SageMaker.
resource "aws_cloudwatch_log_group" "orquestrador_retreino" {
name = "/aws/vendedlogs/states/${var.projeto}-orquestrador-retreino"
retention_in_days = 90 # mesma janela de auditoria do historico do L25
}
resource "aws_sfn_state_machine" "orquestrador_retreino" {
name = "${var.projeto}-orquestrador-retreino"
role_arn = aws_iam_role.orquestrador_retreino.arn
definition = templatefile("${path.module}/orquestrar_retreino.asl.json", {
sns_falha_orquestracao_arn = aws_sns_topic.falha_orquestracao.arn
})
logging_configuration {
log_destination = "${aws_cloudwatch_log_group.orquestrador_retreino.arn}:*"
include_execution_data = true
level = "ALL"
}
}
data "aws_iam_policy_document" "orquestrador_retreino" {
statement {
sid = "IniciarPipeline"
effect = "Allow"
actions = ["sagemaker:StartPipelineExecution", "sagemaker:DescribePipelineExecution"]
resources = [aws_sagemaker_pipeline.fraude_checkout_retreino.arn] # nao "*"
}
}
resource "aws_iam_role_policy" "orquestrador_retreino" {
role = aws_iam_role.orquestrador_retreino.id
policy = data.aws_iam_policy_document.orquestrador_retreino.json
}O que o Retry desta máquina protege, e o que não protege
O `Retry` aqui protege a CHAMADA de iniciar o pipeline — throttling da API do SageMaker, por exemplo — não um passo interno dele. Se um step do pipeline falhar (um erro de dado dentro de TreinarModelo), isso não é `SageMaker.AmazonSageMakerException` na chamada externa: é uma falha reportada pelo próprio pipeline, e o `.sync` propaga isso como falha da Task, caindo direto no `Catch` — sem confundir as duas camadas.
Construir: o gatilho por evento, e o consumidor que não muda
O detalhe que decide se este gatilho funciona ou dispara cedo demais é o `suffix` do padrão de evento: ele tem de casar só com o manifesto, nunca com o dado bruto que os 40 lojistas sobem ao longo da madrugada.
# gatilho_eventbridge.tf -- reage ao MANIFESTO, nao a qualquer objeto do bucket
# bruto. O bucket precisa ter EventBridge habilitado (aws_s3_bucket_notification
# com "eventbridge = true"), gerido pelo modulo que ja possui o bucket.
resource "aws_cloudwatch_event_rule" "lote_completo" {
name = "${var.projeto}-lote-completo"
event_pattern = jsonencode({
source = ["aws.s3"]
"detail-type" = ["Object Created"]
detail = {
bucket = { name = [var.bucket_dados_brutos] }
# so o SUFIXO do manifesto casa -- um upload parcial de um lojista, que
# termina com outro nome de arquivo, nunca dispara esta regra.
object = { key = [{ suffix = "_lote_completo.json" }] }
}
})
}
resource "aws_cloudwatch_event_target" "dispara_orquestrador" {
rule = aws_cloudwatch_event_rule.lote_completo.name
arn = aws_sfn_state_machine.orquestrador_retreino.arn
role_arn = aws_iam_role.eventbridge_para_sfn.arn
input_transformer {
input_paths = {
chave = "$.detail.object.key"
}
# loteId extraido do nome do arquivo; manifestoUri monta o caminho completo
# que o passo ValidarLote da pipeline consome.
input_template = "{\"loteId\": <chave>, \"manifestoUri\": \"s3://${var.bucket_dados_brutos}/<chave>\"}"
}
}Por que o padrão do evento é o suffix do manifesto, não o bucket inteiro
Reagir a QUALQUER `Object Created` no bucket de dado bruto — em vez de só ao manifesto — dispararia uma execução a cada arquivo de CADA lojista, a maior parte delas contra um lote ainda pela metade. É o mesmo erro estrutural do incidente de março, só que automatizado: em vez de uma pessoa rodar cedo demais, o sistema rodaria cedo demais sozinho, com mais frequência.
Implantar, e medir o tempo do dado novo até o modelo
Quatro provas. Nenhuma aceita "a execução ficou verde" como resultado — cada uma tem um comando e um número que reprova nomeado.
# provas.sh -- quatro medicoes; nenhuma conclusao vem de "a execucao ficou verde"
# --- Prova 1: cache hit real, medido em segundos -----------------------------
aws stepfunctions start-execution --state-machine-arn "$SFN_ARN" \
--input '{"loteId":"2026-08-08","manifestoUri":"s3://.../2026-08-08_lote_completo.json"}'
# roda de novo o MESMO lote uma segunda vez, sem nada mudar:
aws stepfunctions start-execution --state-machine-arn "$SFN_ARN" \
--input '{"loteId":"2026-08-08-repeticao","manifestoUri":"s3://.../2026-08-08_lote_completo.json"}'
aws sagemaker list-pipeline-execution-steps --pipeline-execution-arn "$EXEC_2_ARN" \
--query "PipelineExecutionSteps[].{Passo:StepName,Status:StepStatus,Cache:CacheHitResult.SourcePipelineExecutionArn}"
# Esperado: CalcularFeatures e TreinarModelo com Cache preenchido (CacheHit);
# ValidarLote e AvaliarCandidato sempre executam. Medido: 2a execucao em 6 min
# contra 41 min da 1a -- os dois passos caros pulados.
# --- Prova 2: lote incompleto e rejeitado antes de treinar --------------------
aws s3 cp manifesto-fake-incompleto.json s3://$BUCKET/2026-08-09_lote_completo.json
# manifesto declara 40 lojistas, mas so 37 escreveram arquivo de fato
aws sagemaker list-pipeline-execution-steps --pipeline-execution-arn "$EXEC_3_ARN" \
--query "PipelineExecutionSteps[?StepName=='ValidarLote'].StepStatus"
# Esperado: Failed -- a execucao para em ValidarLote, TreinarModelo nunca roda.
# --- Prova 3: execucao idempotente, reentrega nao duplica --------------------
aws events put-events --entries file://reentrega-do-mesmo-evento.json
aws stepfunctions list-executions --state-machine-arn "$SFN_ARN" \
--query "executions[?name=='$(echo -n 2026-08-08 | sha256sum | cut -c1-16)']" | jq length
# Esperado: 1 -- a segunda tentativa de iniciar com o mesmo nome e recusada.
# --- Prova 4: tempo do dado novo ate o candidato, antes e depois -------------
# ANTES (historico do notebook manual, 11 meses):
# media de 23 dias entre lote novo e retrain; pior caso: 35 dias sem retreino.
# DEPOIS (medido nesta implantacao, 6 execucoes de teste):
# 4 min do PutObject do manifesto ate a execucao do Step Functions iniciar;
# 6 a 41 min para a pipeline terminar, dependendo de cache-hit ou cache-miss.
echo "reducao de ordem de grandeza: dias -> minutos"| Prova | Comando | Resultado que aprova | O que reprova, e o que significa |
|---|---|---|---|
| 1 · Cache hit real | list-pipeline-execution-steps num lote repetido | Features e Treinar com CacheHitResult preenchido | se nenhum dos dois usar cache, o CacheConfig não está configurado nos steps certos |
| 2 · Lote incompleto é rejeitado | manifesto com contagem divergente + list-pipeline-execution-steps | ValidarLote falha, TreinarModelo nunca inicia | se o treino rodar mesmo assim, ValidarLote não está bloqueando de verdade a continuação |
| 3 · Execução idempotente | reenviar o mesmo evento + list-executions | uma única execução com aquele nome | duas execuções com o mesmo lote indicam que o nome não está usando o hash do manifesto |
| 4 · Tempo do dado novo até o candidato | cronômetro do PutObject até EXECUTION_SUCCEEDED | minutos, não dias — e cache-hit visivelmente mais rápido que cache-miss | tempo parecido com o do notebook manual indica que o gatilho ainda depende de algo manual |
Quebrar de propósito: três falhas e o diagnóstico
As três acontecem de verdade em times adotando SageMaker Pipelines pela primeira vez, e as três parecem, de fora, o mesmo sintoma — "o modelo saiu errado de novo". O que separa é ONDE a proteção falhou.
| Falha | Como provocar | Sintoma | Onde olhar | Correção |
|---|---|---|---|---|
| Cache indevido por sobrescrita no mesmo caminho do S3 | sobrescrever o mesmo prefixo de dado com um lote diferente, sem mudar a URI | modelo treinado contra dado antigo, mesmo com lote novo disponível | CacheHitResult apontando para uma execução mais antiga do que deveria | usar prefixo com o hash do lote na URI, nunca um caminho fixo reescrito |
| Manifesto escrito antes do lote realmente terminar | processo upstream grava o manifesto por timeout, não por confirmação de contagem | ValidarLote passa, mas o volume real fica abaixo do esperado sem falhar a checagem | contagemEsperada do manifesto divergindo da contagem real de linhas processadas | manifesto só é escrito depois de confirmar cada lojista individualmente, não por relógio |
| Step Functions sem `.sync` na chamada de início | trocar `startPipelineExecution.sync` por `startPipelineExecution` simples | a máquina de estados marca sucesso segundos depois de iniciar, com o treino ainda rodando | duração da execução do Step Functions muito menor que a duração real do pipeline | usar sempre a integração `.sync` para qualquer serviço que suporte esperar conclusão |
A combinação que engana a própria linhagem
Um manifesto escrito cedo demais MAIS um cache que reaproveita a execução anterior por engano é pior do que o desenho mínimo: parece automático — tem regra, tem pipeline, tem linhagem — mas o modelo registrado pode ter vindo de um lote incompleto e ainda assim carregar metadata dizendo que veio de dado íntegro. É exatamente para pegar essa combinação que a Prova 2 da seção anterior injeta um manifesto falso de propósito, antes que ela apareça num incidente real.
O pipeline da Cadência tem CacheConfig habilitado em CalcularFeatures e TreinarModelo. Um novo lote chega, com dado genuinamente diferente do anterior, mas TreinarModelo ainda assim usa cache e não roda de verdade. O que isso mais provavelmente indica?
Segurança: o que muda quando o gatilho é automático
Automatizar o disparo não elimina o risco de um retreino ruim — move o risco para quem controla o evento que dispara. Vale a pena perguntar: quem tem permissão de escrever no prefixo que o EventBridge observa?
| Risco | Probabilidade | Impacto | Prevenção | Detecção | Resposta |
|---|---|---|---|---|---|
| Manifesto forjado dispara retreino com dado controlado por atacante | baixa | alto | restringir quem pode escrever no prefixo de manifesto (bucket policy + IAM específico) | CloudTrail mostrando PutObject do manifesto por um principal fora da lista esperada | revogar o acesso, invalidar o candidato registrado a partir daquele evento, e auditar o lote |
| Role do Step Functions com permissão de iniciar QUALQUER pipeline | baixa | médio | `Resource` restrito ao ARN do pipeline `fraude-checkout-retreino`, nunca `"*"` | Access Analyzer sobre a policy da role do orquestrador | apertar o Resource e revisar quais pipelines a role já iniciou via CloudTrail |
| Cache reaproveitando saída de um lote comprometido | baixa | alto | URI de dataset com hash do lote no caminho — nunca um prefixo fixo reescrito | auditoria periódica comparando CacheHitResult com o hash do lote de origem | invalidar o cache (`enable_caching=False` temporário) e forçar reexecução completa |
| Chave de longa duração reintroduzida para chamar a API do SageMaker manualmente | média | alto | reaproveitar a identidade federada do L54, a mesma que o L75 já usa para publicar modelo | CloudTrail mostrando StartPipelineExecution chamado por IAMUser em vez de AssumedRole | desativar a chave — a mesma lição do L54 e do L75, agora aplicada ao gatilho de dado |
Observabilidade: as perguntas que o painel tem de responder
Um painel de pipeline de retreino tem uma função que o do L75 não tinha sozinho: mostrar não só que uma promoção foi aprovada, mas que o retreino está de fato acontecendo na cadência que o dado pede.
| Pergunta | Métrica ou consulta | O que significa mudar | Limiar inicial |
|---|---|---|---|
| Quanto tempo faz desde o último lote que gerou um candidato? | timestamp do último RegisterModel bem-sucedido | retreino parou de acontecer, mesmo com lotes chegando — regressão ao comportamento manual | acima de 3 dias sem candidato novo, com lotes confirmados chegando |
| Qual a taxa de cache-hit por passo, por semana? | CacheHitResult agregado do list-pipeline-execution-steps | queda súbita indica que algo no dado ou no código está mudando mais do que deveria | abaixo de 30% de hit em Features numa semana sem mudança de feature planejada |
| Alguma execução falhou em ValidarLote? | CloudWatch: execuções do Step Functions com passo ValidarLote=Failed | manifesto sendo escrito cedo demais, ou processo upstream com bug | qualquer ocorrência — é justamente o sinal que faltava no incidente de março |
| A orquestração falhou (Catch acionado) alguma vez? | SNS de falha de orquestração + CloudWatch | throttling persistente na API do SageMaker, ou permissão quebrada | mais de 1 ocorrência na mesma semana |
Escala: um lote, muitos lotes, muitos modelos
| Volume | O que acontece com gatilho e cache | O que passa a doer | O que fazer |
|---|---|---|---|
| 1 lote/dia, 40 lojistas | um pipeline, uma execução por lote | nada; é o cenário deste laboratório | nada |
| Vários lotes no mesmo dia (reprocessamento, correção) | múltiplas execuções concorrentes do mesmo pipeline | duas execuções tentando registrar candidato ao mesmo tempo | nome de execução por lote continua idempotente; ConditionStep evita duplicidade de registro |
| Lotes por lojista, em vez de um lote consolidado | Step Functions Map state, uma sub-execução por lojista | N execuções concorrentes competindo por cota de instância de treino | throttling controlado por MaxConcurrency do Map state — nível 3 da evolução |
| Vários modelos, cada um com seu pipeline | um EventBridge rule e uma state machine por modelo | manter N pipelines quase idênticos manualmente | módulo Terraform reutilizável por modelo, mesmo padrão do L55 |
O que aperta primeiro quando o volume cresce
O gargalo em alto volume não costuma ser o EventBridge nem o Step Functions — os dois escalam de forma praticamente transparente. É a COTA de instância de treino do SageMaker por conta que aperta primeiro quando várias execuções concorrem pelo mesmo tipo de instância; vale checar `Service Quotas` antes de paralelizar por lojista.
Custo: o que este laboratório acrescenta à fatura
| Dimensão | Cobra por | Cuidado |
|---|---|---|
| SageMaker Pipelines (execução dos steps) | instância-hora dos jobs de Processing e Training que RODAM | cache-hit não cobra nada — é exatamente onde este laboratório economiza mais |
| Step Functions (Standard) | transição de estado, poucas por execução | orquestrador tem poucos estados; não é o item que domina a fatura deste desenho |
| EventBridge | evento customizado processado, acima da faixa gratuita | um evento por lote por dia não chega perto de qualquer limite prático |
| CloudWatch Logs (máquina de estados) | ingestão e retenção do log group da orquestração | retenção de 90 dias em poucos eventos por dia é barato — confira no Pricing Calculator se aumentar a verbosidade |
Onde o cache aparece na fatura
O ganho de custo real não é uma linha nova mais barata — é a instância de treino que deixa de rodar quando TreinarModelo dá cache-hit. Comparado ao hábito anterior de rodar o notebook "para garantir", mesmo sem certeza de que o dado mudou, o cache evita pagar pelo treino repetido exatamente nos dias em que ele não acrescentaria nada.
Well-Architected nos seis pilares
| Pilar | Situação ao fim deste laboratório | Risco que fica | Melhoria | Prioridade |
|---|---|---|---|---|
| Excelência operacional | retreino disparado por evento, sem depender de alguém lembrar | processo upstream que escreve o manifesto ainda é um ponto único de decisão sobre "lote completo" | validação redundante de completude, cruzando duas fontes (contagem e timestamp) | média |
| Segurança | identidade federada reaproveitada do L54/L75; role do orquestrador com Resource restrito | quem escreve o manifesto pode, na prática, disparar retreino — superfície que precisa de controle de acesso dedicado | bucket policy restringindo o prefixo de manifesto a um principal específico | alta |
| Confiabilidade | Retry/Catch em volta da chamada de orquestração; ValidarLote pega lote incompleto | cache indevido por sobrescrita no mesmo caminho do S3 continua possível se a convenção de URI não for seguida | enforçar URI com hash do lote via validação de infraestrutura, não só convenção de equipe | alta |
| Eficiência de performance | cache por passo evita reprocessar o que não mudou | ValidarLote e AvaliarCandidato sempre rodam, mesmo sendo baratos — soma latência fixa a toda execução | medir se essa latência fixa importa antes de otimizar; hoje é minutos, não é o gargalo | baixa |
| Otimização de custos | instância de treino só roda quando o dado de fato mudou | nenhum ainda — volume atual de um lote por dia não justifica mais otimização | reavaliar se o número de lotes por dia crescer uma ordem de grandeza | baixa |
| Sustentabilidade | cache-hit evita computação redundante por desenho, não por política à parte | nenhum além do padrão do serviço gerenciado | nenhuma ação necessária no volume atual | baixa |
Evolução em níveis: o que muda, e o que passa a doer
A terceira arquitetura não é um desenho: é a resposta a quando trocar de mecanismo de disparo. Cada nível resolve um risco e compra outro.
Notebook rodado manualmente, sem gatilho formal, sem cache, sem verificação de completude do lote. É onde a Cadência estava, mesmo com o L75 já no ar.SageMaker Pipelines com CacheConfig por passo, orquestrado por Step Functions (`.sync`, Retry, Catch), disparado por EventBridge sobre o manifesto de lote completo, registrando candidato com linhagem no Model Registry do L75.Step Functions Map state processando lotes por lojista em paralelo, reaproveitando cache por partição em vez de tratar o dia inteiro como uma unidade só.Pipeline de staging com dado sintético e pipeline de produção em contas diferentes (L56), cada um com sua própria regra de EventBridge e sua própria identidade federada.Catálogo central de todos os pipelines de retreino da empresa, política compartilhada de Retry/Catch e de retenção de log, auditoria cruzada de quem escreve manifesto em cada bucket.O histórico de execuções — cache-hit por passo, tempo entre lotes, métrica de cada candidato — vira DADO. Um sinal de drift do MODELO em produção (o que o L77 mede) passa a poder disparar um retreino fora do calendário normal do lote, em vez de esperar o próximo evento de dado.A ordem não é negociável, e o motivo é concreto
O nível 6 depende do nível 2 ter dado a cada execução um registro estruturado — cache-hit, duração, métrica — porque sem isso "quando disparar fora do calendário do lote" não teria histórico nenhum para se basear. É a mesma lógica de dependência que o L75 registrou sobre o próprio L76: sem versão e aprovação rastreável, não existe dado de promoção para analisar depois.
Onde IA entra nesta arquitetura, e onde não entra
O modelo de risco de fraude que este pipeline retreina já É IA. A pergunta desta seção é outra: o mecanismo de ORQUESTRAÇÃO — decidir quando disparar, o que reaproveitar — se beneficia de IA? Neste módulo, ainda não, e forçar seria o antipadrão que a própria série existe para evitar.
A decisão de disparar um retreino a cada lote completo é uma regra simples e suficiente para o problema declarado: garantir que o retreino aconteça. O lugar onde IA acrescentaria valor real é justamente o do Nível 6 — priorizar QUAL lote merece um retreino imediato versus qual pode esperar o próximo, usando o histórico de drift do L77 como sinal.
| Pergunta | Resposta honesta para este módulo |
|---|---|
| Qual problema a IA resolveria? | decidir quando um lote justifica retreino, em vez de retreinar sempre que o manifesto aparece — hoje o gatilho é puramente por chegada de dado, não por relevância dele |
| Por que uma regra não bastaria? | uma regra simples (retreinar sempre que houver lote novo) já resolve o problema declarado deste módulo — perder retreino por esquecimento. IA só se justificaria depois que o volume de lotes crescer a ponto de retreinar sempre virar desperdício |
| De onde viriam os dados? | histórico de execução do pipeline (cache-hit, métrica por candidato) cruzado com o sinal de drift que o L77 vai medir — os dois já são produzidos por este laboratório e pelo próximo |
| Qual o risco? | aprender de poucas execuções (um lote por dia) e decidir NÃO retreinar num momento em que deveria — o oposto do problema original, mas igualmente caro |
| Por que não agora? | porque o gatilho por evento de dado já resolve o problema real da Cadência hoje — retreino perdido por semanas. Um mecanismo mais esperto de QUANDO disparar é otimização, não correção de um defeito ainda aberto |
O uso de IA que parece atraente e é armadilha aqui
Pedir a um modelo para "decidir se este lote merece retreino" trocaria uma regra simples e auditável — lote completo dispara, sempre — por uma decisão opaca, no exato módulo cujo problema original era a AUSÊNCIA de um gatilho confiável. Resolver "o retreino não dispara sozinho" com outra camada que também pode decidir não disparar é substituir uma lacuna conhecida por uma lacuna mais difícil de auditar.
Anti-padrões deste laboratório
| Anti-padrão | Por que alguém faz | Por que é problema | Sintoma em produção | Forma correta |
|---|---|---|---|---|
| Cron horário no lugar de gatilho por evento | é mais simples de configurar do que uma regra de EventBridge sobre um manifesto específico | dispara por TEMPO, não por conteúdo — pode rodar contra lote incompleto ou perder um lote que chegou fora da janela | retreino ocasionalmente contra dado pela metade, ou atraso de horas até o próximo cron | regra de EventBridge reagindo ao PutObject do manifesto de lote completo |
| Cache do pipeline inteiro em vez de por passo | parece mais simples: um CacheConfig só, no nível do pipeline | qualquer mudança pequena invalida TUDO, inclusive passos baratos que não precisavam rodar de novo | execuções sempre demoradas, mesmo quando só um detalhe pequeno mudou | CacheConfig independente por Step, cada um com sua própria chave de entrada |
| EventBridge reagindo a qualquer PutObject do bucket bruto | parece cobrir mais casos — "qualquer dado novo dispara" | dispara uma execução por arquivo de CADA lojista, a maioria contra lote ainda incompleto | dezenas de execuções por dia, a maior parte falhando em ValidarLote | padrão do evento casando só com o suffix do manifesto de lote completo |
| Sobrescrever o dataset no mesmo prefixo do S3 a cada lote | é o caminho de menor esforço herdado de antes do pipeline existir | o cache pode reaproveitar um resultado antigo sem perceber que o dado por trás da URI mudou | modelo treinado (ou reaproveitado do cache) contra dado que já não é o vigente | URI com hash ou data do lote no caminho, nunca um prefixo fixo reescrito |
| Nunca testar o "lote incompleto" de propósito | "a validação deve funcionar, é só contar registros" — ninguém testou de fato | a primeira vez que ValidarLote encontra um lote real incompleto costuma ser durante um incidente, não um teste | retreino contra dado pela metade descoberto só depois, pelo Model Registry rejeitando a promoção | Prova 2 da seção de implantação (manifesto incompleto de propósito) faz parte da rotina, não só do incidente |
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Onde olhar | Correção |
|---|---|---|---|---|
| Execução do Step Functions nunca inicia depois de um lote novo | o padrão de evento do EventBridge não casa com o sufixo real do manifesto | compare o event pattern contra um evento real de PutObject no CloudTrail | aws_cloudwatch_event_rule e o nome de arquivo que o processo upstream de fato grava | ajustar o suffix no event_pattern para o nome exato do manifesto |
| Máquina de estados marca sucesso, mas o modelo nunca é registrado | a chamada usa `startPipelineExecution` simples em vez de `.sync` | compare a duração da execução do Step Functions com a duração real do pipeline no console do SageMaker | o `Resource` declarado no state `IniciarPipeline` | trocar para `startPipelineExecution.sync` |
| TreinarModelo roda de novo mesmo sem o lote ter mudado | a URI do dataset de entrada mudou por causa de um prefixo com timestamp em vez de hash de conteúdo | compare a URI de entrada das duas execuções passo a passo | CacheHitResult de CalcularFeatures e de TreinarModelo, lado a lado | usar hash do lote na URI, não um timestamp que muda a cada execução mesmo com dado igual |
| ValidarLote falha mesmo com lote genuinamente completo | o manifesto declara uma contagem esperada desatualizada, ou o processo upstream tem um bug de contagem | compare `contagemEsperada` do manifesto com a contagem real de linhas no S3 | log do passo ValidarLote no console do SageMaker Pipelines | corrigir o processo que gera o manifesto; não relaxar a validação |
| Candidato registrado, mas sem linhagem completa | o `ModelStep` não recebeu `ExecutionVariables.PIPELINE_EXECUTION_ARN` ou o `lote_origem_id` | inspecione `CustomerMetadataProperties` do Model Package registrado | definição do passo `RegistrarCandidato` em `definir_pipeline.py` | garantir que os dois campos de linhagem são passados explicitamente no `register()` |
| Custo do pipeline mais alto do que o esperado, mesmo com cache habilitado | `expire_after` do CacheConfig força reexecução com mais frequência do que o necessário | compare a frequência de cache-miss com a frequência real de mudança do dado | Cost Explorer filtrando por PipelineExecutionArn | ajustar `expire_after` com base na cadência real de mudança de dependência, não um valor arbitrário |
Limpeza: o que o destroy não leva
Nenhum recurso deste laboratório cobra parado como um NAT Gateway ou um endpoint em blue/green — o item mais caro (a instância de treino) já só existe enquanto um step roda de verdade. O que sobra é histórico e configuração órfã, não fatura.
# limpar.sh
terraform destroy -auto-approve
# 1. GRUPO DE LOGS da maquina de estados: nao cobra muito, mas fica com
# retencao de 90 dias configurada e pode confundir auditoria futura se
# ficar orfa.
aws logs describe-log-groups \
--log-group-name-prefix "/aws/vendedlogs/states/${PROJETO}-orquestrador-retreino" \
--query "logGroups[].logGroupName"
# Esperado apos o destroy: lista vazia -- se aparecer, delete-log-group manual.
# 2. PIPELINE DO SAGEMAKER: nao cobra parado (so cobra quando um step RODA),
# mas apagar perde o historico de execucoes e o cache acumulado.
aws sagemaker list-pipelines --query "PipelineSummaries[?PipelineName=='fraude-checkout-retreino']"
# 3. REGRA DO EVENTBRIDGE: nao cobra, mas uma regra orfa aponta para uma
# maquina de estados que ja nao existe -- falha em silencio na proxima
# vez que alguem depender dela.
aws events list-rules --name-prefix "${PROJETO}-lote-completo"
# 4. Prova final: nada com o nome do projeto de pe que nao devia estar.
aws resourcegroupstaggingapi get-resources \
--tag-filters Key=Projeto,Values=${PROJETO} --query "ResourceTagMappingList[].ResourceARN"| Recurso | Sai no destroy? | Cobra parado? | Por que fica |
|---|---|---|---|
| Grupo de logs da máquina de estados | sim, se administrado por este Terraform | não além do free tier | poucos eventos por dia não domina a fatura, mas um grupo órfão confunde auditoria futura |
| SageMaker Pipeline (definição) | sim, se explicitamente apagado | não | a definição em si não cobra; cobra só quando um step roda — apagar perde histórico de execuções e cache |
| Regra do EventBridge | sim | não | regras não cobram, mas uma regra órfã aponta para uma máquina de estados que já não existe |
| Máquina de estados (Step Functions) | sim | não parado, só por transição executada | sem execução ativa, uma máquina ociosa não gera cobrança contínua |
Resumo: problema, peça e motivo
| Problema | Peça | Por que ela, e não outra |
|---|---|---|
| Retreino depende de alguém lembrar | regra do EventBridge sobre o manifesto de lote completo | reage a CONTEÚDO, não a relógio nem a lembrança de uma pessoa |
| Reprocessa dado que não mudou | CacheConfig por Step do SageMaker Pipelines | cada passo caro pula sozinho quando a entrada dele especificamente não mudou |
| Retreino contra lote incompleto | passo ValidarLote, sem cache, sempre roda | confere contagem contra o manifesto ANTES de qualquer coisa cara acontecer |
| Ninguém sabe de qual dado veio qual modelo | linhagem anexada ao Model Package do L75 | hash do lote e ARN da execução respondem a pergunta sem precisar reconstruir histórico |
| Falha de infraestrutura passa em silêncio | Step Functions com Retry/Catch em volta da chamada | a mesma lição do L25, aplicada à orquestração de nível superior, não a um passo de negócio |
| Redesenhar aprovação junto com o gatilho | reaproveitar o Model Registry e a aprovação do L75 | trocar QUANDO se retreina não deveria custar reescrever COMO se promove |
| Falha | O que a protege | O que ela NÃO protege |
|---|---|---|
| Retreino esquecido por semanas | gatilho por evento de dado novo | processo upstream que nunca escreve o manifesto — isso é falha de outro sistema |
| Retreino contra dado incompleto | passo ValidarLote, sem cache | manifesto que mente sobre a contagem — é o cenário da Prova 2, e exige controle de acesso ao manifesto |
| Custo de reprocessar o que não mudou | CacheConfig por passo | mudança de dependência que não aparece no hash das entradas declaradas — é para isso que existe o `expire_after` |
| Falha de chamar a API do SageMaker | Retry/Catch da máquina de estados | falha de um step INTERNO do pipeline — essa falha é do próprio SageMaker Pipelines, não da orquestração |
- O último lojista termina de subir, e o processo upstream grava o manifesto de lote completo.
- EventBridge casa o padrão do manifesto e dispara a máquina de estados, com nome idempotente.
- Step Functions chama StartPipelineExecution com `.sync`, esperando o pipeline terminar de verdade.
- ValidarLote confere contagem contra o manifesto — sem isso, nada mais roda.
- CalcularFeatures e TreinarModelo checam cache antes de rodar; cada um pula se a entrada não mudou.
- AvaliarCandidato compara com o baseline do L75; só métrica aceitável segue para registro.
- O Model Package nasce com linhagem (hash do lote, ARN da execução) e entra no MESMO fluxo de aprovação do L75.
- O rollback, a aprovação e o blue/green continuam exatamente como o L75 já resolveu.
Perguntas frequentes
❓ Por que EventBridge reage ao manifesto em vez de a qualquer objeto novo no bucket?
❓ O que o CacheConfig compara para decidir se um passo pula ou roda?
❓ Por que Step Functions em volta do SageMaker Pipelines, se o Pipelines já orquestra?
❓ Este pipeline reabre a decisão de aprovação e rollback que o L75 já resolveu?
❓ Quanto tempo leva entre um lote ficar completo e um candidato aparecer no Registry?
❓ O que acontece se o lote chega incompleto mesmo com o manifesto dizendo completo?
❓ Por que não usar Airflow (ou MWAA) em vez de Step Functions e SageMaker Pipelines?
Fixando
A regra do EventBridge da Cadência usa `object.key = [{ "suffix": "_lote_completo.json" }]` em vez de reagir a qualquer `PutObject` no bucket de dados brutos. Por que essa escolha específica de padrão de evento importa para o problema deste laboratório?
Se um manifesto for escrito com uma contagem esperada MENOR do que a real (por exemplo, por um bug no processo upstream que subestima quantos lojistas ainda faltam), o que ValidarLote faz — e o que isso revela sobre os limites dessa validação?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Conhecimentos anteriores necessários | L25 (Step Functions: Retry/Catch declarativo) e L75 (Model Registry, aprovação e rollback) — este módulo assume os dois resolvidos |
| Conhecimentos adquiridos | SageMaker Pipelines e CacheConfig por passo; Step Functions orquestrando um serviço integrado com `.sync`; EventBridge reagindo a conteúdo de dado, não a relógio; linhagem anexada a um Model Package |
| Limitação que fica | a validação de completude confia no número que o manifesto declara — um manifesto que subestima ou mente sobre a contagem não é pego por ValidarLote sozinho |
| Próximo exemplo recomendado | L77 — Drift: descobrir antes do negócio reclamar. Usa o mesmo Model Registry deste laboratório e do L75 para saber qual versão está em produção quando o alarme de drift dispara |
| Também habilitado por este módulo | L78 (avaliação honesta) pode comparar candidatos de execuções diferentes usando a linhagem anexada aqui; qualquer modelo novo da Cadência pode reaproveitar o mesmo padrão de pipeline + orquestração + gatilho |
| Data da última validação técnica | 8 de agosto de 2026 |
Documentação oficial consultada: Amazon SageMaker Pipelines — CacheConfig, ConditionStep e lineage tracking; e AWS Step Functions: Service Integrations — o padrão `.sync` para esperar a conclusão de um serviço integrado. Os valores de preço não aparecem neste módulo por decisão: custo de instância de treino varia por tipo e região — use o AWS Pricing Calculator com o tipo de instância real do seu pipeline.
O que não foi verificado, e você deve conferir na sua conta
O tempo de 4 minutos entre o manifesto e o início da execução, e a faixa de 6 a 41 minutos de duração da pipeline, são os valores medidos no exemplo da Cadência — não um padrão universal. Ajuste sua expectativa conforme o tamanho real do seu dataset e o tipo de instância de treino escolhido. Confira também o `expire_after` do CacheConfig contra a cadência real de mudança de dependência do seu ambiente antes de fixar o valor em produção.
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…