Lab 95 — Enriquecimento em lote do acervo
O problema, e a empresa que o tem
A Cadência é a mesma equipe do L01, do L27 e do L89: agendamento gerenciado sem EC2 dedicada, e as três alavancas de custo de GenAI já medidas e aplicadas ao RAG e ao agente de suporte. O catálogo de produtos chegou a 2 milhões de itens, e boa parte deles nunca recebeu categoria, atributo estruturado nem palavra-chave de busca — foram importados de fornecedores diferentes, cada um com sua própria bagunça de metadado. Ninguém está esperando essa classificação em tempo real: é trabalho de fundo, que pode — e deve — rodar de madrugada.
Um estagiário propôs a solução mais direta: um laço que lê cada item do catálogo e chama o Bedrock de forma síncrona, um de cada vez, exatamente como o chat e o classificador de ticket do L89 já fazem. Ele testou com 50 itens e funcionou. Rodou contra os 2 milhões numa sexta à noite, esperando ter o catálogo pronto na segunda de manhã. Na segunda, o script tinha processado 340.000 itens — 17% do total — e tinha caído de vez, horas antes, com uma pilha de ThrottlingException acumuladas. Sem checkpoint nenhum, recomeçar do zero era a única opção: o fim de semana inteiro não tinha avançado sequer um sexto do trabalho, e ainda tinha custado o preço cheio de tempo real por cada uma das 340 mil chamadas que completaram.
O problema não é a chamada individual — cada uma das 340 mil respostas estava correta, a categoria certa, o atributo certo. O problema é usar a capacidade de tempo real, com o preço de tempo real, para um trabalho que não tem ninguém do outro lado esperando resposta em segundos. É a mesma lição do L89 — medir antes de decidir a forma de chamar o modelo — aplicada a um volume que nenhuma das três alavancas daquele laboratório resolve sozinha: cache não ajuda quando cada item é diferente do anterior, e roteamento por tarefa não muda o fato de que 2 milhões de chamadas síncronas, mesmo no modelo mais barato, ainda são 2 milhões de chamadas síncronas.
340 mil itens processados e nada para mostrar na segunda-feira
O laço síncrono não é apenas mais caro: ele não tem checkpoint. Quando a exceção de limite de taxa derrubou o script pela última vez, o programa não sabia dizer quais dos 340.000 itens já tinham resposta gravada e quais tinham falhado no meio — a única forma seguros de retomar era do zero. Dinheiro e tempo já gastos, sem nenhuma garantia de não duplicar o que já tinha sido pago.
O que este laboratório NÃO é
Não é sobre melhorar o prompt de classificação, nem sobre trocar de modelo para um mais preciso — a qualidade da classificação em si é a mesma medida no L89, aplicada aqui a um volume diferente. Também não é sobre construir um pipeline de MLOps com re-treino: é sobre a decisão de INFRAESTRUTURA entre chamar o Bedrock em tempo real, item a item, ou em lote, fatia a fatia — a mesma disciplina de medir antes de escolher a forma de chamar o modelo, agora aplicada a volume que não cabe em nenhuma das três alavancas do L89 sozinha.
O que você vai conseguir fazer
Objetivos verificáveis: cada um se prova com uma medição concreta na seção de implantação, não com a sensação de ter entendido.
- Explicar por que paralelizar chamadas síncronas ao Bedrock não escapa da cota de taxa da conta, e por que isso não é o mesmo problema que custo.
- Fatiar um acervo de 2 milhões de itens em manifestos do tamanho aceito por um job de lote do Bedrock.
- Orquestrar múltiplas fatias com Step Functions, disparadas por um agendador (reaproveitando o padrão do L27), com controle de progresso que sobrevive a uma falha no meio do lote.
- Medir o custo por item no modo tempo real e no modo batch, com o mesmo prompt e o mesmo modelo, e provar a diferença real.
- Auditar a qualidade do resultado em lote com Athena, contra uma amostra conhecida, antes de promover o resultado ao catálogo de produção.
- Decidir quando reprocessar o acervo inteiro e quando reprocessar só o delta afetado por uma mudança de taxonomia.
- Diagnosticar uma fatia que falhou ou que excedeu a janela esperada, sem reprocessar as fatias que já terminaram.
- Provar que o acervo de 2 milhões de itens termina dentro de uma janela de horas, com número medido, não estimado.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| Modelo de cobrança on-demand vs. batch | AIF-C01 | as 2 milhões de classificações saem do preço de tempo real e entram no preço de lote | o desconto do modo batch existe porque o provedor processa sem compromisso de latência individual — não porque o modelo mudou |
| Cota de taxa (throttling) por conta e por modelo | AIF-C01, DVA-C02 | o laço síncrono esbarra na cota mesmo paralelizando várias Lambdas ao mesmo tempo | a cota é da CONTA e do MODELO, na região — não da função individual; paralelizar não escapa dela |
| CreateModelInvocationJob e o teto de um job de lote | AIF-C01, MLS-C01 | 2 milhões de itens viram 40 fatias de 50 mil, cada fatia um job | todo job de lote tem um teto de registros e de tamanho de arquivo — confira o valor atual em Service Quotas antes de dimensionar a fatia |
| Orquestração assíncrona com retomada de falha | SOA-C02, DVA-C02 | Step Functions dispara só as fatias pendentes depois de uma falha, não o lote inteiro de novo | idempotência no nível da FATIA, não só do item — a mesma lição do L27, numa granularidade maior |
| Auditoria de qualidade de saída em lote antes de promover | AIF-C01 | Athena consulta o resultado contra uma amostra conhecida antes do resultado virar catálogo de produção | confiar cegamente em 2 milhões de classificações sem amostra auditada é o erro que a prova deste módulo evita |
Onde isto costuma ser cobrado errado
A pergunta clássica descreve uma fatura de Bedrock alta num job de processamento em massa e oferece "use instâncias maiores" ou "paralelize mais" como resposta. As duas erram o alvo: o gargalo não é capacidade de computação do lado do chamador, é a cota de taxa do modelo do lado do Bedrock e o preço de tempo real cobrado por chamada síncrona. A resposta certa muda a FORMA da chamada — de síncrona item a item para lote — não a quantidade de máquinas chamando.
Requisitos, e como cada um muda o desenho
A coluna da direita é a rastreabilidade: toda peça nova da arquitetura de produção aparece nela. Peça que não aparecesse teria sido retirada antes deste texto existir.
| Requisito | Valor declarado | O que ele decide no desenho |
|---|---|---|
| Custo por item reduzido pela metade, medido | sem valor absoluto — comprovado por medição antes e depois, mesmo prompt e mesmo modelo | obriga usar o modo batch do Bedrock para o volume grande, e medir tempo real vs. batch lado a lado antes de declarar a redução |
| Processamento roda numa janela de horas, não segundos | aceitável terminar em poucas noites, não em semanas | obriga orquestração assíncrona (Step Functions + agendador) em vez de chamada síncrona aguardando resposta |
| Nenhum dos 2 milhões de itens pode ficar sem classificação | cobertura de 100% do acervo declarado | obriga fatiamento com controle de progresso, para que uma falha parcial não perca fatias já concluídas |
| O lote não pode competir com o tráfego interativo do L89 | chat e ticket continuam com a mesma latência medida | obriga disparo fora do horário comercial, via agendador dedicado — reaproveita o padrão do L27, não um cron numa EC2 |
| Qualidade da classificação não pode degradar sem detecção | medida contra amostra golden, antes de promover à produção | obriga auditoria do Athena sobre o resultado do lote, com aprovação explícita antes de publicar no catálogo |
| Reprocessamento após mudança de taxonomia não pode custar como reclassificar tudo de novo | decisão por delta quando a mudança afeta um subconjunto conhecido | obriga o controle de progresso expressar "delta pendente", não só "tudo pendente ou tudo concluído" |
| Custo rastreável por execução do lote, não só a fatura consolidada de Bedrock | tag obrigatória em toda fatia e todo job | obriga tag por execução e painel segmentado por fatia, reaproveitando o padrão de Budgets por tag do L89 |
O requisito mais fácil de esquecer
"Terminar numa janela de horas" parece satisfeito só por usar o modo batch — e não é. Sem orquestração que dispare várias fatias em paralelo até o limite de concorrência da conta, 40 fatias sequenciais, uma depois da outra, também não cabem numa noite. A janela de horas é do JOB individual, não do lote inteiro; o lote inteiro depende de quantas fatias rodam ao mesmo tempo.
Arquitetura mínima: um item de cada vez, no preço de tempo real
Este é o desenho que o estagiário da Cadência implantou. É literalmente implantável, e nenhuma chamada individual está errada — é por isso que o defeito passa despercebido num teste pequeno. O laboratório começa nomeando exatamente o que falta: registro de progresso, e a forma certa de pagar por trabalho que ninguém está esperando.
- → lê o próximo item da lista de 2 milhões
- → chama de forma síncrona, um item por vez, espera a resposta
- → devolve a classificação, cobrada no preço de tempo real
- → grava o resultado antes de seguir para o próximo item
- Armazenamento
- Compute
- IA e machine learning
É o desenho que o estagiário da Cadência implantou. Ele é implantável de verdade e cada chamada individual está correta — o defeito não é nenhuma resposta errada, é que 2 milhões de chamadas síncronas, no preço de tempo real, não cabem em nenhuma janela razoável nem em nenhum orçamento razoável. Percorra os passos e note o que NÃO existe aqui: nenhum registro de progresso.
- A função lê um item por vez, sem paralelismo real. O laço é sequencial: item 1, resposta, grava, item 2. Não existe fatiamento, não existe lote — cada iteração é uma unidade de trabalho isolada, do tamanho de um item só.
- Cada item dispara uma chamada síncrona, no preço de tempo real. A mesma capacidade que atende uma pergunta de chat no L89 atende este item de catálogo — não existe uma "fila mais barata" para trabalho que pode esperar, porque o laço nunca declarou que este trabalho pode esperar.
- A função espera a resposta antes de seguir para o próximo item. Não há paralelismo dentro do laço: item 2 só começa depois de item 1 terminar. É a forma mais simples de escrever, e também a mais lenta possível para 2 milhões de itens.
- O resultado é gravado item a item, sem consolidação. Cada resposta vira um arquivo individual no bucket de saída — não existe um conceito de "fatia concluída" nem de progresso agregado para consultar depois.
- Paralelizar a função não escapa do limite de taxa do modelo. Disparar dez, cem ou mil Lambdas ao mesmo tempo não muda a cota de requisições por segundo que o Bedrock aplica por CONTA e por MODELO na região — é um teto compartilhado por todo o tráfego daquela conta, não um limite por função individual.
- Para 2 milhões de itens, o tempo total nunca cabe numa janela razoável. Mesmo no melhor caso, sustentando a cota de taxa sem nenhum erro de limite — o que a Cadência não conseguiu na prática —, o tempo total passa de dois dias corridos, sem parar. A seção de prova mede o número exato.
- Por que alguém tenta assim primeiro. Parece o caminho mais direto: um `for` chamando a mesma API que já funciona no chat. Funciona de verdade com 50 itens de teste — o defeito só aparece contra o volume real, e por isso passa despercebido até alguém rodar contra os 2 milhões.
O custo hipotético de terminar os 2 milhões assim
Rodar os 2 milhões de itens inteiramente no modo síncrono, mesmo sem nenhum throttling, custaria hipoteticamente cerca de R$ 27.600 — quase o dobro do modo batch — e levaria dias corridos sem parar, competindo o tempo todo com o chat e o ticket que também usam Bedrock. É a mesma disputa de capacidade que o L89 já tinha resolvido para tráfego interativo, reaberta aqui por um job que nem precisava disputar nada.
Arquitetura para produção
Cada peça nova abaixo rastreia a uma linha da tabela de requisitos. O registro de progresso resolve a falha parcial do estagiário; o agendador resolve a disputa com tráfego interativo; a auditoria do Athena resolve confiar cegamente em 2 milhões de respostas nunca revisadas.
- → dispara a máquina de estados de madrugada, fora do horário comercial
- → consulta e atualiza o status de cada fatia antes e depois de disparar
- → grava o manifesto da fatia pendente, no formato que o lote espera
- → o job lê o manifesto e os itens da fatia
- → dispara CreateModelInvocationJob e monitora o status até concluir
- → grava a classificação de cada item da fatia processada
- → consulta os resultados contra uma amostra golden, mede custo por item
- → duração e itens processados, por fatia
- → alarma se uma fatia não terminar dentro da janela esperada
- Integração de apps
- Banco de dados
- Armazenamento
- IA e machine learning
- Analytics
- Gestão e governança
A topologia muda inteira, não só o rótulo. O agendador dispara a orquestração de madrugada — reaproveitando o padrão do L27 — a máquina de estados consulta o progresso antes de decidir o que falta, cada fatia vira um job de lote no Bedrock, e o Athena audita o resultado antes de qualquer coisa virar catálogo de produção. Percorra os passos: cada peça nova ataca um requisito que a arquitetura mínima deixou exposto.
- O agendador dispara a máquina de estados de madrugada. Fora do horário comercial, sem depender de ninguém lembrar — o mesmo padrão de agendamento gerenciado do L27, agora disparando uma orquestração inteira em vez de uma função isolada.
- A máquina de estados decide o que falta, não reprocessa o que já terminou. Antes de disparar qualquer job novo, ela consulta o registro de progresso: só as fatias com status pendente ou falhou entram na próxima rodada — é a diferença estrutural em relação ao laço da arquitetura mínima, que não tinha essa pergunta para fazer.
- Cada fatia pendente vira um manifesto no bucket de entrada. 50 mil itens por manifesto, no formato de registro que o job de lote do Bedrock espera — não itens soltos, um arquivo por fatia.
- O job de lote lê a fatia e processa numa janela de horas, na metade do preço. A máquina de estados dispara `CreateModelInvocationJob` apontando para o manifesto da fatia, e monitora o status até ele terminar — sem esperar de forma síncrona, sem bloquear a próxima fatia.
- O resultado é gravado por fatia, não item a item. Cada job de lote concluído grava um arquivo de saída com a classificação de todos os 50 mil itens daquela fatia — consolidado, não disperso em 50 mil arquivos individuais.
- Athena mede o custo por item e audita a qualidade antes de promover. Uma consulta compara a saída da fatia contra uma amostra golden conhecida, e soma o custo do job dividido pelos itens processados — é o que impede uma fatia com erro sistemático de virar catálogo de produção sem ninguém perceber.
- O painel acompanha o progresso; o alarme cobre a fatia que trava. CloudWatch soma quantas das 40 fatias já terminaram e quanto custou até agora; o alarme dispara se uma fatia específica ultrapassar a duração esperada, sem esperar o lote inteiro estourar o prazo para alguém notar.
A diferença central não é "trocar chamada síncrona por chamada em lote": é que o trabalho passa a ser FATIADO e RASTREADO — cada fatia com seu próprio status, sua própria duração medida e seu próprio custo auditado — em vez de um laço cego que só sabe dizer "ainda não terminei" até cair.
O achado que sustenta o módulo inteiro
O modo batch não é só mais barato — ele muda o CONTRATO da chamada. Tempo real promete resposta em segundos e cobra por isso; lote promete resposta dentro de uma janela de horas e cobra menos, exatamente porque abre mão da promessa de segundos. A Cadência nunca precisou dessa promessa para classificar um catálogo que ninguém lê em tempo real.
Como funciona ponta a ponta: da fatia ao acervo classificado
O caminho mais fácil de entender errado é achar que "modo batch" significa uma chamada só para os 2 milhões de itens. Não é — o job de lote tem um teto de registros e de tamanho de arquivo por execução, e é esse teto, verificado em Service Quotas, que decide o tamanho da fatia.
// manifesto-fatia-014.jsonl -- uma linha por item, o formato que o job de
// lote LE do bucket de entrada. O campo recordId volta no resultado, e e
// ele que liga a resposta ao item original do catalogo.
// Confira o nome exato dos campos na referencia atual da API antes de
// gerar o manifesto em producao -- este e o formato documentado em
// meados de 2026, e schemas de API evoluem.
{"recordId": "CAD-0000481932", "modelInput": {"anthropic_version": "bedrock-2023-05-31", "max_tokens": 200, "messages": [{"role": "user", "content": "Classifique o item a seguir em categoria, atributo principal e ate 5 palavras-chave de busca. Titulo: Cabo USB-C 2m trancado, Descricao: cabo de recarga reforcado..."}]}}
{"recordId": "CAD-0000481933", "modelInput": {"anthropic_version": "bedrock-2023-05-31", "max_tokens": 200, "messages": [{"role": "user", "content": "Classifique o item a seguir em categoria, atributo principal e ate 5 palavras-chave de busca. Titulo: Suporte de parede para TV ate 55, Descricao: suporte articulado..."}]}}
Por que recordId importa mais do que parece
O job de lote não garante devolver os resultados na mesma ORDEM em que os itens foram enviados no manifesto — ele garante devolver o `recordId` de volta junto com cada resposta. Ligar o resultado ao item pela posição na lista, em vez de pelo `recordId`, é o tipo de suposição que passa despercebida no teste com poucos itens e produz classificação trocada silenciosamente em escala.
As decisões, e o que se perde em cada uma
📋 Classificar 2 milhões de itens de catálogo — categoria, atributo, palavra-chave de busca — sem nenhum consumidor esperando a resposta em tempo real, com equipe de duas pessoas e sem orçamento para treinar modelo próprio.
Nenhuma das alternativas resolve os três problemas ao mesmo tempo — custo, tempo e retomada de falha — sem adicionar complexidade que o volume não justifica. O modo batch resolve custo e tempo pela própria natureza da cobrança; a orquestração com controle de progresso resolve retomada de falha sem reprocessar o que já terminou; a auditoria do Athena substitui "confiar cegamente" por uma medição antes de qualquer coisa virar dado de produção.
Alt: Manter o laço síncrono, só que paralelizado em muitas Lambdas — não escapa da cota de taxa por conta e por modelo — o teto é compartilhado entre todas as Lambdas, então mais função não significa mais throughput além de um certo ponto, e o preço continua sendo o de tempo real
Alt: Provisioned throughput em vez de on-demand ou batch — reserva capacidade por um período mínimo de compromisso, pensada para volume alto e SUSTENTADO — a reclassificação da Cadência é um evento de poucas vezes por ano mais enriquecimento incremental pequeno, não um fluxo constante que justifique compromisso de capacidade
Alt: Treinar um classificador próprio (SageMaker) em vez de usar o Bedrock — resolveria custo por item no longo prazo, mas adiciona ciclo de vida de ML — rotulagem, treino, avaliação, re-treino quando a taxonomia muda — que a equipe de duas pessoas não tem capacidade de operar para um problema que o modo batch já resolve sem treinar nada
Alt: Rodar o lote inteiro dentro do horário comercial, sem agendador dedicado — tecnicamente possível, mas reabre a disputa de capacidade com o chat e o ticket que o L89 já tinha fechado — o job de lote não tem requisito de horário, então rodá-lo de madrugada não custa nada e evita o risco
| Decisão | Escolha | Alternativa considerada | Motivo | O que se perde |
|---|---|---|---|---|
| Tamanho da fatia | 50 mil itens por manifesto | uma fatia só com os 2 milhões | fica dentro do teto de registros por job (verificado em Service Quotas) e permite reprocessar uma fatia sem tocar nas outras 39 | mais peças para orquestrar — mitigado pela máquina de estados, que trata isso como repetição de um mesmo padrão |
| Controle de progresso | DynamoDB, uma linha por fatia | arquivo de checkpoint único em S3 | escrita condicional evita corrida entre duas execuções tentando atualizar a mesma fatia ao mesmo tempo | mais uma tabela pequena para operar, com custo desprezível sob demanda |
| Disparo do lote | EventBridge Scheduler, de madrugada | disparo manual quando alguém lembrar | reaproveita o padrão gerenciado do L27, sem depender de ninguém lembrar de rodar | nenhuma; é a mesma decisão do L27, aplicada a um novo alvo |
| Auditoria antes de promover | Athena contra amostra golden, aprovação explícita | promover automaticamente se o job terminar sem erro técnico | erro técnico e erro de CONTEÚDO são coisas diferentes — um job pode terminar limpo e ainda classificar uma fatia inteira errada | atraso de algumas horas entre o job terminar e o resultado virar catálogo — aceitável para um lote que já não é tempo real |
| Reprocessamento após mudança de taxonomia | por delta, quando o subconjunto afetado é conhecido | sempre reclassificar os 2 milhões de novo | evita pagar o custo do acervo inteiro por um ajuste que afeta uma fração dele | exige saber calcular o delta corretamente — decisão errada aqui deixa itens desatualizados sem ninguém perceber |
Construir: fatiar o catálogo e controlar o progresso
Os buckets e a tabela de controle são a parte declarável — entram em Terraform. O fatiamento em si, que lê o catálogo de origem e escreve os manifestos, é código: não há uma operação inversa nem estado a rastrear no provedor, o mesmo raciocínio do L89 para o disparo do job de lote de produtos.
# lote-acervo.tf -- o que E declaravel: buckets de entrada/saida e a
# tabela de controle de progresso por fatia. O fatiamento em si e codigo.
resource "aws_s3_bucket" "lote_entrada" {
bucket = "${var.projeto}-lote-acervo-entrada"
}
resource "aws_s3_bucket" "lote_saida" {
bucket = "${var.projeto}-lote-acervo-saida"
}
resource "aws_s3_bucket_lifecycle_configuration" "lote_entrada" {
bucket = aws_s3_bucket.lote_entrada.id
rule {
id = "expirar-manifesto-processado"
status = "Enabled"
# 14 dias bastam para investigar uma fatia com problema antes do
# manifesto de entrada sumir -- o resultado fica no bucket de saida,
# que tem regra propria e mais longa.
expiration { days = 14 }
}
}
# Uma linha por fatia: status, ARN do job quando existir, contagem de
# itens, custo estimado e timestamps. E o que falta no laco sincrono da
# arquitetura minima -- sem isso, uma falha no meio perde o progresso.
resource "aws_dynamodb_table" "controle_fatias" {
name = "${var.projeto}-controle-fatias-acervo"
billing_mode = "PAY_PER_REQUEST"
hash_key = "fatia_id"
attribute {
name = "fatia_id"
type = "S"
}
}
data "aws_iam_policy_document" "confianca_lambda" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
}
}
resource "aws_iam_role" "fatiar_catalogo" {
name = "${var.projeto}-fatiar-catalogo"
assume_role_policy = data.aws_iam_policy_document.confianca_lambda.json
}
data "aws_iam_policy_document" "fatiar_catalogo" {
statement {
effect = "Allow"
actions = ["s3:PutObject"]
resources = ["${aws_s3_bucket.lote_entrada.arn}/*"]
}
statement {
effect = "Allow"
actions = ["dynamodb:PutItem", "dynamodb:UpdateItem"]
resources = [aws_dynamodb_table.controle_fatias.arn]
}
}
resource "aws_iam_role_policy" "fatiar_catalogo" {
role = aws_iam_role.fatiar_catalogo.id
policy = data.aws_iam_policy_document.fatiar_catalogo.json
}
A função abaixo lê o catálogo de origem, escreve um manifesto por fatia de 50 mil itens, e inicializa o registro de progresso — a parte que a arquitetura mínima nunca teve.
// FatiarCatalogo.cs -- le o catalogo de origem, grava um manifesto por
// fatia de 50 mil itens, e inicializa o controle de progresso. Roda uma
// vez por execucao do lote inteiro, disparada pela maquina de estados.
public sealed class FatiarCatalogo
{
private const int TamanhoFatia = 50_000;
private readonly IAmazonS3 _s3;
private readonly IAmazonDynamoDB _ddb;
private readonly string _bucketEntrada;
private readonly string _tabelaControle;
public async Task<int> ExecutarAsync(CancellationToken ct)
{
var itens = await CarregarCatalogoAsync(ct); // 2.000.000 itens, origem: catalogo de producao
var totalFatias = (int)Math.Ceiling(itens.Count / (double)TamanhoFatia);
for (var i = 0; i < totalFatias; i++)
{
var fatiaId = $"fatia-{i:D3}";
var pagina = itens.Skip(i * TamanhoFatia).Take(TamanhoFatia);
// So grava manifesto e cria o registro se a fatia AINDA NAO existe
// no controle -- reexecutar este fatiamento nao duplica fatia
// nem reseta o progresso de uma que ja esta em andamento.
var jaExiste = await _ddb.GetItemAsync(new GetItemRequest
{
TableName = _tabelaControle,
Key = new() { ["fatia_id"] = new AttributeValue { S = fatiaId } },
}, ct);
if (jaExiste.Item.Count > 0) continue;
await GravarManifestoAsync(fatiaId, pagina, ct);
await _ddb.PutItemAsync(new PutItemRequest
{
TableName = _tabelaControle,
Item = new()
{
["fatia_id"] = new AttributeValue { S = fatiaId },
["status"] = new AttributeValue { S = "pendente" },
["itens"] = new AttributeValue { N = pagina.Count().ToString() },
["criada_em"] = new AttributeValue { S = DateTimeOffset.UtcNow.ToString("o") },
},
}, ct);
}
return totalFatias; // 40, para os 2 milhoes de itens atuais
}
}
Construir: a máquina de estados que dispara e retoma o lote
O EventBridge Scheduler dispara a máquina de estados — não uma função isolada, como no L27, porque aqui um único disparo precisa coordenar até 40 fatias, cada uma com seu próprio job de lote e seu próprio status.
# orquestracao-lote.tf -- agendador dispara a maquina de estados de
# madrugada; a maquina fatia, dispara e monitora, sem reprocessar fatia
# concluida.
resource "aws_scheduler_schedule" "disparar_lote_acervo" {
name = "${var.projeto}-disparar-lote-acervo"
group_name = "default"
flexible_time_window { mode = "OFF" }
# 2h da manha -- fora do horario comercial, folga suficiente para o
# lote terminar (medido em ~17h corridas) antes do proximo horario
# de pico de trafego interativo.
schedule_expression = "cron(0 2 * * ? *)"
schedule_expression_timezone = "America/Sao_Paulo"
target {
arn = aws_sfn_state_machine.lote_acervo.arn
role_arn = aws_iam_role.scheduler_invoca_sfn.arn
}
}
resource "aws_sfn_state_machine" "lote_acervo" {
name = "${var.projeto}-lote-acervo"
role_arn = aws_iam_role.execucao_sfn.arn
definition = jsonencode({
Comment = "Fatia o acervo pendente, dispara jobs de lote, monitora e retoma"
StartAt = "ListarFatiasPendentes"
States = {
ListarFatiasPendentes = {
Type = "Task"
Resource = aws_lambda_function.listar_pendentes.arn
Next = "ProcessarFatias"
}
# Map com concorrencia limitada -- ate 12 fatias ao mesmo tempo,
# dentro da cota de jobs de lote concorrentes da conta (confira o
# valor atual em Service Quotas antes de ajustar este numero).
ProcessarFatias = {
Type = "Map"
ItemsPath = "$.fatiasPendentes"
MaxConcurrency = 12
Iterator = {
StartAt = "DispararJobLote"
States = {
DispararJobLote = {
Type = "Task"
Resource = aws_lambda_function.disparar_job_lote.arn
Next = "AguardarJob"
}
AguardarJob = { Type = "Wait", Seconds = 900, Next = "VerificarStatus" }
VerificarStatus = {
Type = "Task"
Resource = aws_lambda_function.verificar_status_job.arn
Next = "JobConcluido"
}
JobConcluido = {
Type = "Choice"
Choices = [
{ Variable = "$.status", StringEquals = "concluida", Next = "Fim" },
{ Variable = "$.status", StringEquals = "falhou", Next = "Fim" },
]
Default = "AguardarJob"
}
Fim = { Type = "Succeed" }
}
}
End = true
}
}
})
}
// DispararJobLote.cs -- para UMA fatia, checa o controle de progresso e,
// se ainda nao esta em andamento nem concluida, cria o job de lote.
// Checar antes de disparar e o que evita pagar duas vezes pela mesma
// fatia se a maquina de estados reexecutar este passo.
public async Task<ResultadoDisparo> ExecutarAsync(string fatiaId, CancellationToken ct)
{
var registro = await _ddb.GetItemAsync(new GetItemRequest
{
TableName = _tabelaControle,
Key = new() { ["fatia_id"] = new AttributeValue { S = fatiaId } },
}, ct);
var status = registro.Item.GetValueOrDefault("status")?.S;
if (status is "em_andamento" or "concluida")
{
_logger.LogInformation("{Fatia} ja esta {Status}; nao disparando job novo", fatiaId, status);
return new ResultadoDisparo(fatiaId, status, JobArn: registro.Item.GetValueOrDefault("job_arn")?.S);
}
var job = await _bedrock.CreateModelInvocationJobAsync(new CreateModelInvocationJobRequest
{
JobName = $"enriquecimento-acervo-{fatiaId}-{DateTime.UtcNow:yyyyMMddHHmm}",
ModelId = "anthropic.claude-haiku-4-5-...", // classificacao curta, modelo leve tambem no lote
RoleArn = _roleArnBedrockBatch,
InputDataConfig = new() { S3InputDataConfig = new() { S3Uri = $"s3://{_bucketEntrada}/{fatiaId}.jsonl" } },
OutputDataConfig = new() { S3OutputDataConfig = new() { S3Uri = $"s3://{_bucketSaida}/{fatiaId}/" } },
}, ct);
// Escrita condicional: falha se outra execucao concorrente ja marcou
// esta fatia como em_andamento entre o GetItem acima e este PutItem --
// e a corrida que este desenho existe para evitar.
await _ddb.PutItemAsync(new PutItemRequest
{
TableName = _tabelaControle,
Item = new()
{
["fatia_id"] = new AttributeValue { S = fatiaId },
["status"] = new AttributeValue { S = "em_andamento" },
["job_arn"] = new AttributeValue { S = job.JobArn },
["disparada_em"] = new AttributeValue { S = DateTimeOffset.UtcNow.ToString("o") },
},
ConditionExpression = "attribute_not_exists(job_arn) OR #s <> :em_andamento",
ExpressionAttributeNames = new() { ["#s"] = "status" },
ExpressionAttributeValues = new() { [":em_andamento"] = new AttributeValue { S = "em_andamento" } },
}, ct);
return new ResultadoDisparo(fatiaId, "em_andamento", job.JobArn);
}
Disparar sem checar o progresso paga a fatia duas vezes
Um job de lote iniciado não tem cancelamento barato — a mesma lição do L89 para o job de produtos: itens já processados são cobrados de qualquer forma, mesmo que o job seja interrompido. Se a máquina de estados reexecutar o passo de disparo sem checar o controle de progresso primeiro, duas chamadas a CreateModelInvocationJob para a mesma fatia significam pagar duas vezes por 50 mil itens — e o erro só aparece na auditoria de custo por fatia, não num alarme técnico.
Construir: auditoria de qualidade no Athena
O bucket de saída ganha uma tabela do Glue por cima; o Athena consulta essa tabela contra uma amostra golden de itens já classificados manualmente, para medir taxa de acerto e custo por item antes de qualquer fatia virar catálogo de produção.
# auditoria-athena.tf -- tabela externa sobre o resultado do lote, e um
# workgroup dedicado para nao misturar o custo de consulta deste modulo
# com o de outras cargas de Athena da conta.
resource "aws_glue_catalog_database" "acervo" {
name = "${var.projeto}_acervo_classificado"
}
resource "aws_glue_catalog_table" "resultado_lote" {
name = "resultado_lote"
database_name = aws_glue_catalog_database.acervo.name
table_type = "EXTERNAL_TABLE"
storage_descriptor {
location = "s3://${aws_s3_bucket.lote_saida.bucket}/"
input_format = "org.apache.hadoop.mapred.TextInputFormat"
output_format = "org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat"
ser_de_info {
serialization_library = "org.openx.data.jsonserde.JsonSerDe"
}
columns {
name = "record_id"
type = "string"
}
columns {
name = "categoria"
type = "string"
}
columns {
name = "atributo"
type = "string"
}
columns {
name = "palavras_chave"
type = "array<string>"
}
}
}
resource "aws_athena_workgroup" "auditoria_acervo" {
name = "${var.projeto}-auditoria-acervo"
configuration {
result_configuration {
output_location = "s3://${aws_s3_bucket.lote_saida.bucket}/_resultados-athena/"
}
}
}
-- auditar_fatia.sql -- taxa de acerto contra a amostra golden e custo
-- por item, para UMA fatia. So depois deste numero aprovar e que a
-- fatia e promovida ao catalogo de producao.
WITH acerto AS (
SELECT
g.record_id,
g.categoria_correta = r.categoria AS categoria_bateu
FROM amostra_golden g
JOIN resultado_lote r ON r.record_id = g.record_id
WHERE g.fatia_id = 'fatia-014'
)
SELECT
COUNT(*) AS itens_na_amostra,
SUM(CASE WHEN categoria_bateu THEN 1 ELSE 0 END) AS acertos,
ROUND(100.0 * SUM(CASE WHEN categoria_bateu THEN 1 ELSE 0 END) / COUNT(*), 1) AS taxa_acerto_pct
FROM acerto;
-- Resultado esperado (medido na fatia-014, 500 itens de amostra golden):
-- itens_na_amostra=500, acertos=463, taxa_acerto_pct=92.6
A amostra golden não é o acervo inteiro, e não precisa ser
500 itens revisados manualmente por fatia bastam para estimar a taxa de acerto com margem aceitável para decidir promover ou reter — não é preciso revisar os 50 mil itens da fatia para confiar no número. É o mesmo raciocínio do golden set do L89 aplicado a classificação de catálogo em vez de classificação de ticket.
Provar: custo por item e a janela de horas do lote
É o mesmo tipo de disciplina do L89: medir com o MESMO prompt e o MESMO modelo nos dois modos, para que a diferença medida seja da FORMA de chamar, não de uma variável escondida. Os valores em reais são medição do cenário fictício da Cadência, não preço publicado pela AWS — confira o Pricing Calculator para o valor real na sua conta.
| Métrica | Modo tempo real (medido) | Modo batch (medido) |
|---|---|---|
| Custo por item | R$ 0,0138 | R$ 0,0069 (exatamente metade) |
| Custo projetado para os 2.000.000 de itens | R$ 27.600 | R$ 13.800 (economia de R$ 13.800) |
| Itens processados por chamada | 1, síncrono | até 50.000, num único job de lote |
| Tempo até o primeiro resultado | segundos | dentro da janela de horas do job |
| Tempo total para os 2.000.000 de itens | ~61,7 h corridas, no melhor caso sem nenhum throttling — mais de 2,5 dias sem parar | 16 h 40 min, em 40 fatias, até 12 concorrentes, dentro de 2 janelas de madrugada |
| Competição com tráfego interativo (L89) | sim — mesma capacidade do chat e do ticket | nenhuma — roda de madrugada, fora do horário comercial |
| Custo além do modelo | Dimensão | Cuidado |
|---|---|---|
| S3 (entrada e saída) | armazenamento e requisição | manifestos e resultados de 2 milhões de itens somam poucos GB — desprezível frente ao custo do modelo |
| Step Functions | por transição de estado | 40 fatias com Map e poucas transições cada uma ainda fica em centavos por execução do lote inteiro |
| DynamoDB (controle) | leitura e escrita sob demanda | 40 linhas atualizadas algumas vezes por execução — tráfego trivial |
| Athena | por byte escaneado na consulta | a tabela particionada por fatia evita escanear o acervo inteiro numa auditoria de uma fatia só |
O número que sustenta o módulo
R$ 13.800 de economia sobre R$ 27.600, com os mesmos 2 milhões de itens classificados — não menos itens, não modelo mais fraco. E o tempo total caiu de mais de 2,5 dias corridos (na hipótese otimista, sem nenhum throttling) para 16 h 40 min, distribuídos em duas madrugadas, sem competir um segundo com o chat ou o ticket que o L89 já protegia.
Quebrar de propósito: quatro falhas do trabalho de fundo
Trabalho de fundo tem um problema estrutural: ninguém está olhando quando ele roda. As quatro falhas abaixo aproveitam exatamente isso — todas produzem um catálogo que PARECE classificado, e três delas sobrevivem à conferência mais natural, que é abrir alguns itens e ver que estão certos.
| Falha injetada | Como injetar | Sintoma enganoso | Diagnóstico correto |
|---|---|---|---|
| Saída do lote com menos registros que a entrada | Incluir na fatia alguns itens cuja descrição estoure o limite de tokens de entrada, e juntar a saída à entrada por POSIÇÃO | A fatia conclui, a contagem de saída é quase igual à de entrada, e as amostras que alguém abrir estarão corretas — porque o desalinhamento começa no primeiro item que falhou e contamina só dali para a frente | Inferência em lote não garante saída um-para-um com a entrada: item rejeitado simplesmente não aparece. Juntar por posição desloca TODAS as classificações seguintes em um, e o produto errado herda a categoria do vizinho — 40 mil itens silenciosamente trocados. A junção tem de ser pelo identificador que o registro de entrada carrega, nunca pela ordem. É a falha mais grave deste laboratório e a mais difícil de ver |
| Retomada reprocessando fatia já concluída | Marcar uma fatia como em andamento, matar a execução, e disparar a retomada | O trabalho termina, o catálogo fica correto, e ninguém reclama de nada | O custo dobrou naquela fatia e o registro de progresso não sabe disso. O estado "em andamento" é ambíguo por natureza — pode significar rodando ou morto — e a retomada precisa distinguir os dois pelo job de lote real, não pelo que a tabela diz. Custo por item medido contra o esperado é o único sinal que denuncia, e é por isso que a prova deste módulo o calcula |
| Amostra golden que não representa o acervo | Medir a taxa de acerto contra 200 itens todos das três categorias mais comuns | A taxa de acerto vem alta e estável, execução após execução. O painel de qualidade autoriza publicar | Os itens que o modelo erra são justamente os da cauda — fornecedor com taxonomia estranha, que é a origem do problema deste laboratório. Uma amostra concentrada na cabeça da distribuição mede a parte fácil e chama o resultado de qualidade do todo. A amostra precisa ser estratificada por origem de fornecedor, e conter de propósito os casos que ninguém sabe classificar |
| Job de lote disparado no meio da tarde | Remover o agendamento noturno e disparar manualmente às 14h | O catálogo fica pronto mais cedo. O time comercial agradece | É o incidente do L98 sendo criado aqui. A cota de tokens por minuto é da conta, não do job: consumi-la em lote derruba a latência de quem responde em tempo real, e a relação de causa e efeito é invisível para os dois times. A janela noturna não era conveniência — era o único isolamento disponível antes de existir cota separada |
A primeira falha é a que justifica a auditoria
Junção posicional entre entrada e saída de inferência em lote é o defeito clássico deste tipo de pipeline, e ele não gera erro, não gera alarme e passa em qualquer amostragem que comece do início do arquivo. A defesa não é cuidado: é estrutural — todo registro de entrada carrega um identificador, e toda junção usa esse identificador. A auditoria do Athena que compara identificador de entrada com identificador de saída é o teste que pega.
A Cadência testou paralelizar o laço síncrono original, disparando 40 Lambdas ao mesmo tempo, cada uma classificando itens diferentes do catálogo via Bedrock em tempo real. O throughput agregado não melhorou proporcionalmente ao número de Lambdas, e o custo por item continuou o mesmo de antes. Por que paralelizar a chamada síncrona não resolve nem o tempo nem o custo do jeito que o modo batch resolve?
Segurança: o que muda quando o lote toca 2 milhões de itens de uma vez
Um erro de permissão na arquitetura mínima afeta um item por vez, e alguém percebe rápido. Um erro de permissão ou de conteúdo na arquitetura de produção afeta 50 mil itens de uma tacada — a auditoria antes de promover é, ela mesma, um controle de segurança de conteúdo, não só um controle de qualidade.
| Risco | Probabilidade | Impacto | Controle preventivo | Detecção | Resposta |
|---|---|---|---|---|---|
| Papel de execução do Bedrock batch lendo/escrevendo fora dos prefixos do projeto | baixa | alto | `Resource` restrito a `entrada/*` e `saida/*` dos buckets deste projeto, nunca o bucket inteiro da conta | IAM Access Analyzer sobre o uso real do papel | restringir o `Resource`; auditar objetos acessados fora do esperado |
| Fatia com erro sistemático promovida direto ao catálogo de produção | média | alto | aprovação explícita depois da auditoria do Athena — nunca promoção automática ao fim do job | taxa de acerto da amostra golden abaixo do limiar | reter a fatia, reprocessar os itens divergentes, investigar o prompt daquela fatia |
| Job de lote disparado em duplicidade para a mesma fatia | média | médio | escrita condicional no DynamoDB antes de chamar `CreateModelInvocationJob` | duas linhas de `job_arn` para a mesma `fatia_id` no histórico | cancelar o job duplicado se ainda não terminou; registrar o custo extra |
| Dado de item de catálogo com informação sensível de fornecedor exposta no prompt | baixa | médio | revisão do template de prompt para não incluir campo de custo ou contrato do fornecedor, só descrição pública do produto | amostragem manual do conteúdo dos manifestos antes do primeiro lote em produção | corrigir o template; reprocessar as fatias que usaram a versão anterior |
Promover sem auditoria publica erro em massa, não erro isolado
Promover a saída do lote direto para o catálogo de produção sem a aprovação do Athena arrisca publicar uma categoria sistematicamente errada para uma fatia inteira — 50 mil itens simultaneamente, não um erro isolado que um cliente reporta e alguém corrige na hora. É a razão de a promoção exigir aprovação explícita depois da auditoria, nunca automática ao fim do job.
Observabilidade: as perguntas que o painel tem de responder
| Pergunta | Métrica ou consulta | O que significa mudar | Limiar inicial |
|---|---|---|---|
| Quantas fatias já terminaram, e quantas faltam? | contagem por status no DynamoDB de controle | progresso do lote inteiro, sem precisar abrir o console do Bedrock fatia por fatia | informativo, sem alarme |
| Alguma fatia excedeu a janela esperada? | duração do job desde `disparada_em` até status terminal | job preso, ou fila do provedor mais lenta que o medido | > 6 h (folga sobre a média de ~4 h 10 min) |
| O custo por item está dentro do esperado? | custo do job dividido pelos itens da fatia, contra R$ 0,0069 medido | prompt maior que o normal, ou mudança de preço do modelo | desvio acima de 15% da média |
| A qualidade da classificação caiu? | taxa de acerto da amostra golden, por fatia, via Athena | prompt degradado, ou taxonomia mudou e o modelo não foi ajustado | abaixo de 90% (linha de base medida em 92,6%) |
| O lote inteiro terminou dentro da janela de negócio? | timestamp da última fatia concluída contra o prazo declarado | concorrência insuficiente, ou volume cresceu além do medido | além do horário comercial do dia seguinte ao disparo |
A métrica que engana nas primeiras fatias
Nos primeiros minutos depois do disparo, o painel mostra 0 fatias concluídas para todas as 12 fatias em andamento — não é sinal de que nada está funcionando, é o formato normal de um job que promete resposta em horas, não em minutos. Confirme pelo status `em_andamento` no DynamoDB antes de suspeitar de falha.
Escala: 50 mil, 500 mil, 2 milhões, e o pico de reclassificação total
| Volume | O que muda | O que passa a doer | O que fazer |
|---|---|---|---|
| 50 mil itens (enriquecimento incremental semanal) | 1 fatia só, termina numa única execução curta | nada — é o regime mais leve que este desenho atende | nada |
| 500 mil itens | 10 fatias, ainda cabem numa noite com a concorrência atual | nada crítico | nada |
| 2 milhões (linha de base deste laboratório) | 40 fatias, ~16 h 40 min, 2 madrugadas | já perto do limite de "poucas noites" — mais volume empurra para 3 ou mais | aumentar a concorrência máxima do Map, dentro da cota de jobs simultâneos da conta |
| Reclassificação total forçada por mudança grande de taxonomia, mais de uma vez no mês | custo e tempo dobram se cada mudança reclassificar o acervo inteiro de novo | orçamento do mês estoura sem o volume real de produtos ter mudado | usar o reprocessamento por delta do nível 5 da evolução, em vez do acervo inteiro |
| Falha regional do Bedrock durante o lote | jobs em andamento na região afetada falham | sem controle de progresso, perderia as fatias já concluídas também | o controle no DynamoDB permite retomar só as fatias pendentes quando a região voltar — não há failover automático de região neste laboratório |
O número que costuma decidir a questão de prova
Um job de lote tem um teto de registros por execução — verifique o valor atual em Service Quotas antes de dimensionar a fatia. Uma questão que pede "como processar um catálogo maior que esse teto" está testando exatamente o fatiamento com orquestração, não uma configuração de job maior que não existe.
Custo: o preço de classificar 2 milhões de itens, em três cenários
O estagiário gastou o preço de tempo real por 340 mil chamadas e entregou 17% do trabalho. O desenho deste laboratório muda as duas pontas: paga menos por chamada, e não paga duas vezes pelo que já foi feito. As dimensões abaixo são todas as que aparecem na fatura deste módulo.
| Cenário | O que roda | Onde o dinheiro vai | O que ninguém nota |
|---|---|---|---|
| Piloto — 50 mil itens, uma fatia | Uma execução, um job de lote, uma consulta de auditoria | Quase tudo em token. As outras dimensões somam centavos e não valem otimização nenhuma nesta escala | O custo por item medido aqui é o número que vai multiplicar por 40 na carga completa — é o único momento barato de descobrir que a descrição do fornecedor é maior do que se imaginava |
| Primeira carga — 2 milhões de itens, 40 fatias | 40 jobs de lote coordenados, registro de progresso ativo, auditoria por fatia | Token continua dominando, mas a auditoria deixa de ser desprezível: consultar a saída inteira a cada fatia varre o acumulado de novo, e o custo da auditoria cresce quadraticamente se a consulta não for particionada | Particionar a saída por fatia e consultar só a partição nova transforma esse crescimento quadrático em linear. É o mesmo aprendizado do L64, cobrando de novo num lugar novo |
| Regime — 80 mil itens novos por mês | Uma ou duas fatias por mês, no agendamento noturno | A conta cai a uma fração da carga inicial, e o piso passa a ser o que existe independentemente do volume: armazenamento das saídas históricas e a tabela de progresso | A saída histórica não tem prazo de validade declarado e cresce para sempre. Uma regra de ciclo de vida movendo saída antiga para armazenamento frio é centavos hoje e a diferença entre linear e acumulado em dois anos |
O número que decide, e o que ele não é
Os preços por 1.000 tokens, por minuto e por segundo mudam, e mudam por região e por modelo. Os números abaixo são a ORDEM DE GRANDEZA medida pela Cadência para dimensionar a decisão; confirme o valor vigente na página de preços do serviço antes de levar qualquer um deles a uma proposta. O número que decide a viabilidade aqui não é o custo total: é o custo POR ITEM comparado ao custo de um humano classificar o mesmo item. Foi essa razão que autorizou o projeto, e é ela que precisa ser recalculada quando o acervo ou o modelo mudarem.
Well-Architected nos seis pilares
| Pilar | Situação ao fim deste laboratório | Risco que fica | Melhoria | Prioridade |
|---|---|---|---|---|
| Excelência operacional | progresso por fatia visível no DynamoDB, sem depender de vasculhar log de uma função | sem runbook escrito para quando uma fatia falha repetidamente | runbook curto por status `falhou`, com o comando de retomada seletiva | média |
| Segurança | papel do Bedrock batch restrito aos prefixos do projeto; promoção exige aprovação explícita pós-auditoria | template de prompt ainda não tem revisão automatizada de campo sensível | checagem automática do manifesto antes do disparo, além da revisão manual inicial | média |
| Confiabilidade | controle de progresso permite retomar só as fatias pendentes depois de qualquer falha | sem failover automático de região se o Bedrock cair na região principal durante o lote | avaliar região secundária só se o SLA do lote exigir — hoje a janela de horas absorve um novo disparo no dia seguinte | baixa |
| Eficiência de performance | concorrência de fatias ajustável dentro da cota da conta, sem reescrever a orquestração | nenhum — 40 fatias com Map já usa o paralelismo disponível sem gargalo conhecido | nenhuma ação necessária agora | baixa |
| Otimização de custos | custo por item medido, metade do modo tempo real, com fatia como unidade de auditoria | reprocessamento total ainda é o padrão-fim, mesmo quando só uma fração da taxonomia mudou | implementar o reprocessamento por delta do nível 5 da evolução | alta |
| Sustentabilidade | modo batch permite ao provedor otimizar o uso de capacidade computacional, sem urgência de latência individual | nenhum específico deste módulo | nenhuma ação necessária agora | baixa |
Evolução em níveis: do item síncrono ao acervo tratado como dado
Cada nível responde a mesma pergunta: o que muda quando o volume ou a ambição cresce, e o que essa mudança troca.
Laço síncrono item a item, sem controle de progresso — o que o estagiário da Cadência implantou. Legítimo só para testar a integração com poucas dezenas de itens.Mesmo laço, com um checkpoint simples salvo a cada N itens num arquivo único, sem fatiamento real nem modo batch.Fatiamento em manifestos, Step Functions orquestrando o Bedrock batch por fatia, controle de progresso no DynamoDB, Athena auditando antes de promover.O mesmo pipeline enriquece não só o catálogo próprio, mas o de marketplaces parceiros, cada um com seu próprio bucket de entrada e sua própria janela de agendamento.Mudança de taxonomia dispara reprocessamento só do subconjunto afetado — o delta —, não do acervo inteiro de novo.O mesmo Bedrock batch passa a ser usado por vários times — catálogo, marketing, suporte —, cada um com cota e orçamento próprios, e chargeback por centro de custo. É o L98.A ordem não é opcional
Orquestrar fatias no nível 3 sem controle de progresso propaga o mesmo risco de duplicidade do laço original, só que em unidades de 50 mil itens em vez de 1. E decidir reprocessar por delta no nível 5 sem o histórico de auditoria do nível 3 é decisão sem dado — exatamente a armadilha que a seção de IA deste módulo evita ao tratar o próprio resultado do modelo como sinal, não como verdade absoluta.
Onde IA entra além da classificação, e onde não entra
O núcleo deste laboratório já É inteligência artificial: o Bedrock classifica cada item. A pergunta desta seção é diferente — onde MAIS IA agregaria, além do que já está construído, e onde forçá-la seria decoração.
Um lugar onde IA agrega valor real: decidir quais itens de uma fatia merecem revisão humana antes de confiar cegamente na classificação. Uma regra tradicional não resolve isso bem, porque "confiança" não é um campo que existe no catálogo — é um sinal que só o próprio processo de classificação pode produzir, por exemplo comparando a resposta do modelo contra a categoria anterior do item e sinalizando divergência grande como candidata a revisão, em vez de aceitar toda resposta nova como verdade.
| Pergunta | Resposta honesta para este módulo |
|---|---|
| Qual problema a IA adicional resolveria? | priorizar quais itens de uma fatia aprovada merecem revisão humana, em vez de tratar 50 mil itens como igualmente confiáveis |
| Por que uma regra tradicional não bastaria? | "confiança da classificação" não é um campo do catálogo — é um sinal que só comparar a resposta nova contra o histórico do item, ou contra a amostra golden da fatia, pode aproximar |
| De onde viriam os dados? | a divergência entre a classificação nova e a anterior do mesmo item, mais a taxa de acerto já medida da fatia pelo Athena |
| O que acontece quando erra? | item sinalizado errado como "precisa revisão" custa um clique a mais de um humano; item NÃO sinalizado que devia ter sido é o risco real, e é por isso que o limiar de divergência começa conservador |
| Por que isso não está construído neste laboratório? | a fila de revisão humana e o critério de priorização são um sistema à parte, com sua própria interface e seu próprio dono — este laboratório entrega o sinal (taxa de acerto por fatia), não a fila |
O uso de IA que parece atraente e é armadilha aqui
Pedir a um segundo modelo para "decidir se a classificação do primeiro está certa" sem nenhuma amostra golden para calibrar contra o quê troca um problema de qualidade por outro: agora são dois modelos que podem errar juntos, sem nenhum ponto de referência humano. A auditoria do Athena contra uma amostra golden — trabalho humano, medido — é o que ancora a confiança; um segundo modelo sem essa âncora só parece mais rigoroso.
Anti-padrões deste laboratório
| Anti-padrão | Por que alguém faz | Por que é problema | Sintoma em produção | Forma correta |
|---|---|---|---|---|
| Laço síncrono item a item chamando Bedrock em tempo real | é o código mais simples de escrever primeiro — funciona no teste com 50 itens, e só quebra contra os 2 milhões reais | caro (preço de tempo real) e lento demais para o volume, sem checkpoint | job travado ou com throttling acumulado, sem terminar dentro do prazo | Bedrock batch, fatiado e orquestrado por Step Functions |
| Paralelizar Lambdas para "resolver" o limite de taxa | parece a solução óbvia de escala — mais função, mais throughput | a cota é da conta e do modelo, não da função; todas competem pelo mesmo teto, e o preço continua sendo o de tempo real | `ThrottlingException` subindo proporcional ao número de Lambdas simultâneas, sem ganho real de velocidade | modo batch, que não tem essa disputa de taxa por item |
| Disparar o lote inteiro de novo a cada falha, sem checar progresso | parece mais simples do que rastrear o que já terminou | paga duas vezes pelas fatias já concluídas antes da falha | custo do lote maior que o projetado, sem relação com o volume real do acervo | controle de progresso por fatia, com retomada seletiva |
| Promover a saída do lote direto para produção, sem auditoria | o job terminou sem erro técnico, parece pronto | erro técnico e erro de conteúdo são coisas diferentes — o job pode terminar limpo e ainda classificar uma fatia inteira errada | categoria errada em massa, descoberta só quando um cliente reclama | Athena audita contra amostra golden antes da promoção |
| Reclassificar os 2 milhões de itens a cada ajuste pequeno de taxonomia | é mais simples do que calcular o delta afetado | paga o custo do acervo inteiro por uma mudança que afeta uma fração pequena dele | fatura do lote alta para ajustes que na prática tocam poucos milhares de itens | reprocessamento por delta — nível 5 da evolução |
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Correção |
|---|---|---|---|
| Job de lote termina com status `Failed` | manifesto mal formatado, ou papel de execução sem permissão de leitura no prefixo de entrada | consultar o job pela API e ler o campo de motivo do erro | validar o manifesto contra o formato esperado antes de disparar; conferir a policy do papel de execução |
| Fatia concluída aparece como pendente de novo | escrita no DynamoDB falhou depois de o job terminar, ou uma corrida entre duas execuções | comparar o timestamp de conclusão do job com o registro no DynamoDB | usar escrita condicional no controle de progresso, como no disparo do job |
| Custo por item medido acima do esperado numa fatia específica | itens daquela fatia com descrição mais longa que a média, gerando mais tokens de entrada | comparar tokens de entrada da fatia contra a média histórica | aceitar a variação e medir por fatia, ou normalizar o tamanho da descrição antes de montar o manifesto |
| Athena não encontra os dados da fatia mais recente | a tabela do Glue não tem a partição da fatia nova registrada | checar se a fatia aparece em `SHOW PARTITIONS` | adicionar a partição, ou automatizar a atualização de partição no fim do job |
| Duas execuções da máquina de estados disparam a mesma fatia ao mesmo tempo | o agendador disparou de novo antes de a execução anterior terminar | conferir execuções concorrentes no console do Step Functions | checar status no DynamoDB antes de iniciar, com escrita condicional impedindo a segunda |
A pergunta que resolve metade destes casos
O job falhou tecnicamente, ou terminou e a auditoria reprovou o conteúdo? A primeira dúvida se resolve olhando o status do job na API do Bedrock; a segunda, olhando a taxa de acerto no Athena. Confundir as duas faz alguém investigar permissão de IAM quando o problema era o prompt daquela fatia.
Limpeza: o que o destroy não leva
`terraform destroy` remove os buckets vazios, a tabela de controle, a máquina de estados, o agendador e o workgroup do Athena — mas não remove tudo que este laboratório cria enquanto roda.
#!/usr/bin/env bash
# limpeza.sh -- ordem que evita cobranca e falha de destroy por bucket
# com objeto.
set -euo pipefail
PROJETO=ffv-lab
# 1) Confirma que nenhum job de lote ainda esta em andamento -- destroy
# do papel IAM no meio de um job nao cancela o job, so pode falha-lo.
aws bedrock list-model-invocation-jobs --status-equals InProgress
# 2) Esvazia entrada, saida e resultados do Athena ANTES do destroy --
# bucket com objeto bloqueia a remocao e o destroy para no meio.
aws s3 rm "s3://${PROJETO}-lote-acervo-entrada" --recursive
aws s3 rm "s3://${PROJETO}-lote-acervo-saida" --recursive
terraform destroy
# 3) Confere que nada com o nome do projeto ficou de pe.
aws dynamodb describe-table --table-name "${PROJETO}-controle-fatias-acervo" 2>&1 | grep -i "not found" || true
aws resourcegroupstaggingapi get-resources \
--tag-filters Key=Projeto,Values=${PROJETO} \
--query "ResourceTagMappingList[].ResourceARN" --output table
| Recurso | O destroy remove? | O que fica, e por quê |
|---|---|---|
| Buckets de entrada e saída | sim, se vazios | bucket com manifesto ou resultado ainda gravado bloqueia a remoção — esvaziar antes é obrigatório |
| Job de lote em andamento | não é afetado pelo destroy do papel | o job continua até terminar sozinho; remover o papel IAM no meio pode falhar o job, não cancelá-lo |
| Tabela de controle no DynamoDB | sim | nada — modo sob demanda não cobra por capacidade parada |
| Máquina de estados e agendador | sim | nada — Step Functions e Scheduler cobram por execução e por invocação, não por existir |
| Workgroup e resultados de consulta do Athena | o workgroup sim; os arquivos de resultado, não automaticamente | cada consulta grava um arquivo de resultado no bucket de saída — some junto se o bucket for esvaziado antes |
| Tabela do Glue | sim | nada — catálogo do Glue não cobra por tabela definida, só por chamada de API em volume alto |
O job de lote não tem "cancelar" barato
Igual ao job de produtos do L89, um `CreateModelInvocationJob` em andamento não tem rollback limpo — ele processa até o fim ou até ser explicitamente parado, e itens já processados são cobrados de qualquer forma. Rodar `terraform destroy` sem checar jobs em andamento primeiro trata como reversível uma ação que não é.
Resumo: problema, peça e motivo
| Problema | Peça | Por que ela, e não outra |
|---|---|---|
| Laço síncrono item a item, caro e sem checkpoint | Bedrock em modo batch, fatiado em manifestos | metade do preço de tempo real, e cada fatia é uma unidade de trabalho auditável |
| 2 milhões de itens não cabem num job só | fatias de 50 mil, dentro do teto de registros por job | reprocessar uma fatia com problema não exige tocar nas outras 39 |
| Falha no meio do lote perde o progresso | controle de progresso por fatia no DynamoDB | retomada seletiva: só as fatias pendentes ou falhadas disparam de novo |
| Job de lote não pode competir com chat e ticket | agendador de madrugada, reaproveitando o padrão do L27 | custo zero de disputa de capacidade, porque o lote não tem requisito de horário |
| Job pode terminar limpo e ainda classificar errado | auditoria do Athena contra amostra golden, antes de promover | erro técnico e erro de conteúdo são checados por controles diferentes |
| Mudança pequena de taxonomia não deveria custar como reclassificar tudo | reprocessamento por delta — nível 5 da evolução | custo proporcional ao tamanho da mudança real, não ao acervo inteiro |
- Fatiamento do catálogo em manifestos de 50 mil itens, dentro do teto de registros por job de lote
- Step Functions com Map de concorrência limitada, disparando um job de lote por fatia pendente
- Controle de progresso no DynamoDB, com escrita condicional evitando disparo duplicado
- Agendador de madrugada (EventBridge Scheduler), reaproveitando o padrão do L27
- Auditoria no Athena contra amostra golden, com aprovação explícita antes de promover à produção
- Custo por item medido nos dois modos, com a diferença real de 50% provada, não estimada
Perguntas frequentes
❓ Por que não simplesmente disparar mais Lambdas em paralelo, em vez de usar o modo batch?
❓ O que é uma "fatia", e por que exatamente 50 mil itens?
❓ O modo batch do Bedrock aceita qualquer modelo?
❓ Por que auditar com Athena depois do lote, em vez de confiar direto no resultado?
❓ O que acontece se uma fatia falhar no meio do lote?
❓ Por que o job de lote não pode ser cancelado facilmente?
❓ Mudança de taxonomia sempre exige reclassificar os 2 milhões de itens de novo?
❓ Este laboratório substitui o EventBridge Scheduler do L27 por Step Functions?
Fixando
No meio de uma execução do lote, a fatia-014 termina com sucesso e o Step Functions marca no DynamoDB o status `concluída`. Trinta segundos depois, por uma falha transitória na própria máquina de estados, o passo de disparo é reexecutado para a fatia-014. O que o desenho deste laboratório faz nesse caso, e por quê?
A fatia-027 termina o job de lote sem nenhum erro técnico — status `Completed`, todos os 50 mil itens com resposta gravada. A auditoria do Athena, porém, mede taxa de acerto de 61% contra a amostra golden daquela fatia, bem abaixo da linha de base de 92,6%. O que esse cenário demonstra sobre a arquitetura de produção deste laboratório?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Conhecimentos anteriores necessários | L27 (agendamento gerenciado sem EC2) e L89 (custo e modo batch do Bedrock, cache, roteamento por tarefa) concluídos |
| Conhecimentos adquiridos | fatiamento de acervo grande dentro do teto de um job de lote; orquestração com Step Functions e controle de progresso idempotente por fatia; auditoria de qualidade de saída em lote com Athena antes de promover; medição de custo por item em tempo real vs. batch; reprocessamento total vs. por delta |
| Limitação que fica | sem failover automático de região se o Bedrock cair durante o lote; o controle de progresso permite retomar, mas só na mesma região, quando ela voltar |
| Próximo exemplo recomendado | L98 — plataforma de IA multi-time com cota e chargeback, que reaproveita o mesmo Bedrock batch entre vários times, cada um com cota e orçamento próprios |
| Também habilitado por este módulo | o padrão de fatiamento com controle de progresso e auditoria antes de promover é reaproveitável em qualquer job de lote grande sobre Bedrock, não só classificação de catálogo |
| Data da última validação técnica | 8 de agosto de 2026 |
Documentação oficial consultada: Process multiple prompts with batch inference — formato de entrada e saída do job de lote, e o desconto de preço sobre o modo on-demand, que é a fonte do achado central deste módulo; CreateModelInvocationJob na referência da API — parâmetros de entrada, saída e papel de execução; Bedrock runtime quotas — a existência de um teto de registros por job de lote e de uma cota de requisições por segundo em modo on-demand, cujos valores exatos mudam e devem ser confirmados em Service Quotas; e a documentação de aws_sfn_state_machine e aws_scheduler_schedule do provedor Terraform, já detalhadas no L27. Valores em reais e de duração aparecem como medição do cenário fictício da Cadência, não como preço ou tempo publicado pela AWS — confira o AWS Pricing Calculator e Service Quotas da sua própria conta antes de projetar resultado semelhante.
O que não foi verificado, e você deve conferir na sua conta
O nome exato dos campos do manifesto JSONL (`recordId`, `modelInput`) e o teto de registros por job variam por modelo e por versão da API — confirme na referência atual antes de gerar manifestos em produção. A disponibilidade do modo batch também varia por modelo e por região; nem todo modelo que atende chamada em tempo real tem modo batch habilitado, e isso deve ser checado no console antes de assumir a economia de 50% para um modelo específico.
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…