Pular para o conteúdo
fernando.moretes.com
BlogEstudosCursosE-booksOpen SourcePodcasts
Loading…
Fernando Azevedo

Arquiteto de TI Especialista

Arquitetura, AWS, IA em produção, sistemas financeiros e FinOps, escritos a partir do que foi operado, com os números.

  • LinkedIn
  • GitHub
  • E-mail

Conteúdo

  • Blog
  • Estudos de arquitetura
  • Cursos
  • E-books
  • Open Source
  • Podcasts
  • Assuntos
  • Trilhas

Ferramentas

  • Moretes Architect MCP
  • Well-Architected self-check
  • Arquitetura deste site
  • Números públicos
  • Histórico de estudo

Sobre

  • Comunidade
  • IA Builder
  • Perfil
  • Currículo (CV)
  • Trabalhe comigo
  • Media kit
  • RSS do blog
  • RSS dos estudos
  • Artigos narrados (podcast)
  • llms.txt
  • Termos, privacidade e uso de IA

(c) 2026 Fernando Francisco Azevedo

MCP para Arquitetos: o Moretes Architect na prática/Números que defendem a decisão: capacidade, latência, filas e custo
Módulo 2 · Trabalhar· Aula 05/10

Números que defendem a decisão: capacidade, latência, filas e custo

Sete ferramentas de cálculo do MCP aplicadas à plataforma de pedidos do marketplace: concorrência, orçamento de latência, consumidores de fila, orçamento de erro, custo mensal, custo por pedido e crescimento de armazenamento. Todo número sai de uma entrada que você pode auditar.

13 min de leitura

Escolha como aprender

combine como quiser

Dica: o vídeo é um resumo visual rápido; o áudio e o texto trazem a aula completa. Terminar o áudio ou o vídeo, ou passar no quiz, já conta a aula como concluída.

Assista a esta aula
Narrado por Fernando AzevedoMP4PDF
Ouça esta aula
0:0019:44
Velocidade
Baixar

Nas aulas anteriores você conectou o servidor e aprendeu a ler o que uma ferramenta devolve: o campo kind, as limitations, as lacunas explícitas. Nesta aula o MCP faz contas sobre a plataforma de pedidos do marketplace: 300 pedidos por minuto no pico, SLO de 99,9%, orçamento de USD 1.500 por mês. A regra que atravessa as sete ferramentas é uma só: cada número que sai tem uma entrada que você trouxe, e quando a entrada é um preço, você traz a fonte e a data, porque a ferramenta se recusa a chutar.

Adjetivo não passa em comitê

Em dez anos operando sistema de pagamento eu nunca vi "escala bem" convencer um diretor financeiro. O que convence é "3 execuções simultâneas no pico, 143 dólares por mês com 20% de contingência, preço consultado em 20 de outubro". A diferença entre as duas frases não é estilo: a segunda pode ser conferida por alguém que não confia em mim.

As sete ferramentas desta aula existem para produzir a segunda frase. Nenhuma delas chama modelo nenhum. capacity_plan, latency_budget, queue_sizing, slo_budget e finops_unit_economics devolvem kind: "calculation": aritmética sobre o que você digitou. cost_model devolve kind: "scenario-not-quote" e storage_growth devolve kind: "projection-not-quote". O nome do kind já é a primeira linha de defesa: ninguém vai apresentar "cenário, não cotação" como se fosse cotação.

A segunda linha de defesa está no esquema de entrada. No cost_model, cada item exige source (uma URL válida) e priceDate (uma data ISO). Sem os dois campos a chamada falha na validação, antes de qualquer conta. A descrição da ferramenta no servidor diz literalmente: does not guess AWS prices or credit coverage. O storage_growth repete a regra nas limitações: prices are caller-supplied; no provider rates are embedded. Quando o agente que você usa tenta preencher o preço de cabeça, a ferramenta devolve erro, não número.

Isso muda a sua rotina: antes de chamar, você abre a página de preço, anota URL e data. Parece burocracia. É o que faz o número sobreviver a seis meses, quando alguém perguntar de onde ele veio.

capacity_plan e slo_budget: os dois números que abrem a conversa

O marketplace recebe 300 pedidos por minuto no pico, ou seja, 5 por segundo. Cada pedido ocupa o serviço por cerca de 400 ms e carrega 4 KB. Com headroom: 1.5, o capacity_plan devolve:

  • averageConcurrency: 2, que é a lei de Little: concorrência = taxa × tempo de serviço = 5 × 0,4 s.
  • plannedConcurrency: 3, o teto arredondado de 2 × 1,5.
  • dailyRequests: 432000 e dailyPayloadGB: 1.728, se a taxa de pico durasse o dia inteiro.

A intuição da lei de Little é a que vale a aula: em regime estável, o que está "dentro" do sistema é o quanto entra multiplicado pelo tempo que cada um fica. Não precisa de simulação para saber que 5 por segundo a 400 ms ocupa 2 vagas. O headroom é a sua margem para variância e para o que a fórmula não vê; o padrão é 1,3 e aqui subi para 1,5 porque pagamento tem cauda longa. Cada 0,1 a mais é 10% de capacidade paga o mês inteiro, então a margem é uma decisão, não um reflexo.

A primeira limitação que a ferramenta devolve é a que mais pega: assumes sustained rate all day; separate burst and average inputs. Os 432 mil pedidos por dia são o pico esticado por 24 horas. Para o custo diário, chame de novo com a média.

O slo_budget responde a pergunta que todo gestor faz quando ouve 99,9%: quanto tempo isso dá? Com availabilityPercent: 99.9, periodDays: 30 e usedDowntimeMinutes: 12, o resultado é allowedDowntimeMinutes: 43.2 (30 × 1440 × 0,001), remainingMinutes: 31.2 e exhausted: false. O JSON cru traz 43.19999999999524, artefato de ponto flutuante; leia 43,2. A limitação diz o resto: é SLO por tempo; disponibilidade por requisição exige contagem de eventos bons e totais, que esta ferramenta não tem.

latency_budget: 1460 dos 2000 ms já estão comprometidos

O checkout tem alvo de 2 segundos no p95. Mapeei sete saltos com a estimativa de cada um: TLS + gateway (60 ms), validação de token (40 ms, cacheable: true), consulta de estoque (120 ms) e score de fraude (350 ms) no mesmo parallelGroup: "pre", autorização do pagamento (900 ms), gravar pedido (80 ms) e publicar evento (30 ms).

A ferramenta soma os saltos seriais (60 + 40 + 900 + 80 + 30 = 1110 ms) e, para cada grupo paralelo, conta só o caminho crítico: parallelGroups.pre.criticalPathMs: 350, porque estoque e fraude rodam juntos e quem manda é o mais lento. Total: estimatedTotalMs: 1460, remainingMs: 540, utilizationPercent: 73, withinBudget: true.

O que vale ler em seguida é a lista hops, ordenada do maior para o menor. A cota igualitária é 2000 / 7 ≈ 286 ms por salto; payment authorization leva 61,6% do total e fraud score 24%, ambos com overEqualShare: true. Os outros cinco juntos somam menos que a fraude. Se alguém pedir para "otimizar o checkout", a resposta é: só dois saltos importam, e um deles é de terceiro.

Repare no que a ferramenta não sugeriu. A validação de token está marcada como cacheável, mas a sugestão de cache só aparece quando o salto é cacheável e está acima da cota; 40 ms não justifica. A única sugestão foi more than six serial hops; consider aggregating calls or an async path, porque são sete saltos. É aritmética honesta: ela não inventa otimização para parecer útil.

As limitações são as duas que mais derrubam orçamento de latência. Percentis não somam: a soma de sete p95 superestima o p95 fim a fim, então 1460 é um teto conservador, não uma previsão. E fila, retry e cold start não entram a menos que você os modele como saltos. Quer contar o retry do pagamento? Adicione um salto chamado "retry" com a estimativa dele.

latency_budget: argumentos e resultado reais (docs/examples/latency_budget.json), encurtados
json
// arguments
{
  "targetMs": 2000,
  "percentile": "p95",
  "hops": [
    { "name": "TLS + gateway", "estimatedMs": 60 },
    { "name": "auth token validation", "estimatedMs": 40, "cacheable": true },
    { "name": "inventory check", "estimatedMs": 120, "parallelGroup": "pre" },
    { "name": "fraud score", "estimatedMs": 350, "parallelGroup": "pre" },
    { "name": "payment authorization", "estimatedMs": 900 },
    { "name": "write order", "estimatedMs": 80 },
    { "name": "publish event", "estimatedMs": 30 }
  ]
}

// result
{
  "kind": "calculation",
  "targetMs": 2000,
  "estimatedTotalMs": 1460,
  "remainingMs": 540,
  "withinBudget": true,
  "utilizationPercent": 73,
  "hops": [
    { "name": "payment authorization", "estimatedMs": 900, "sharePercent": 61.6, "overEqualShare": true },
    { "name": "fraud score", "estimatedMs": 350, "parallelGroup": "pre", "sharePercent": 24, "overEqualShare": true },
    { "name": "inventory check", "estimatedMs": 120, "parallelGroup": "pre", "sharePercent": 8.2, "overEqualShare": false }
    // ... 4 more hops, all below the equal share
  ],
  "parallelGroups": { "pre": { "criticalPathMs": 350 } },
  "suggestions": [
    "More than six serial hops; consider aggregating calls or an async path"
  ],
  "limitations": [
    "Percentiles do not add linearly; summing p95 values overestimates the end-to-end p95 and is used here as a conservative budget",
    "Queueing, retries and cold starts are excluded unless modeled as hops"
  ]
}

queue_sizing: a fila que nunca esvazia

O evento de pedido vai para uma fila e 4 consumidores processam cada mensagem em 800 ms. Chegam 5 por segundo e, depois de um incidente, sobraram 2000 mensagens acumuladas. Quero 60% de utilização. O queue_sizing devolve primeiro as fórmulas que vai usar, e isso não é enfeite: é para você refazer a conta à mão.

  • offeredLoadErlangs: 4: lei de Little outra vez, 5 chegadas/s × 0,8 s de serviço.
  • totalCapacity: 4: 4 consumidores × maxConcurrencyPerConsumer: 1 (padrão).
  • utilizationPercent: 100, stable: false, waitProbability: 1, meanInSystem: null, backlogDrainSeconds: null.

Carga oferecida igual à capacidade é o pior lugar possível: a fila só cresce. Os avisos dizem isso com todas as letras: unstable: arrivals exceed capacity; backlog grows without bound e backlog never drains at current capacity. Em produção isso aparece como ApproximateAgeOfOldestMessage subindo em linha reta.

A resposta útil está em consumersForTargetUtilization: 7: teto de 4 / 0,6 = 6,67. Com 7 consumidores a utilização cai para 4 / 7 ≈ 57% e a fila passa a esvaziar. A própria fórmula que a ferramenta imprime dá o tempo: drainSeconds = backlog / (capacity/serviceTime − arrivals) = 2000 / (7 / 0,8 − 5) = 2000 / 3,75 ≈ 533 s, uns 9 minutos. Chame de novo com consumers: 7 e confira; a ferramenta é determinística, rodar duas vezes é parte do método.

visibilityTimeoutHintSeconds: 30 é o maior entre 6 × tempo de serviço (4,8 s) e 30 s: se o consumidor travar, a mensagem volta à fila sem ficar invisível por tempo demais, e sem voltar antes de o processamento legítimo terminar.

A hipótese está declarada nas limitações: chegadas Poisson e serviço exponencial, o modelo M/M/c, com P(wait) por Erlang C. Tráfego em rajada espera mais do que a fórmula diz; batch receive, limites do broker, retries e falhas ficam de fora. Se a sua fila recebe lotes às 9h em ponto, trate o resultado como piso.

queue_sizing: argumentos e resultado reais (docs/examples/queue_sizing.json), encurtados
json
// arguments
{
  "arrivalsPerSecond": 5,
  "serviceTimeMs": 800,
  "consumers": 4,
  "backlogMessages": 2000,
  "targetUtilizationPercent": 60
}

// result
{
  "kind": "calculation",
  "formulas": [
    "offeredLoad = arrivals × serviceTime (Little's law)",
    "utilization = offeredLoad / (consumers × concurrency)",
    "P(wait) = Erlang C(servers, offeredLoad)",
    "drainSeconds = backlog / (capacity/serviceTime − arrivals)"
  ],
  "offeredLoadErlangs": 4,
  "totalCapacity": 4,
  "utilizationPercent": 100,
  "stable": false,
  "waitProbability": 1,
  "meanInSystem": null,
  "backlogDrainSeconds": null,
  "consumersForTargetUtilization": 7,
  "visibilityTimeoutHintSeconds": 30,
  "warnings": [
    "Unstable: arrivals exceed capacity; backlog grows without bound",
    "Backlog never drains at current capacity"
  ],
  "limitations": [
    "Assumes Poisson arrivals and exponential service times (M/M/c); bursty traffic waits longer",
    "Ignores batch receive, broker limits, retries and failures"
  ]
}

cost_model: preço com fonte e data, ou nada

Uma tarefa Fargate de 2 vCPU e 4 GB rodando o mês inteiro (730 horas) e um banco db.t4g.medium. Três itens, cada um com quantity, unit, pricePerUnit, source e priceDate:

  • Fargate vCPU-hours: 1460 (2 × 730) × 0,04048 = subtotal: 59.1008
  • Fargate GB-hours: 2920 (4 × 730) × 0,004445 = subtotal: 12.9794
  • RDS db.t4g.medium: 730 × 0,065 = subtotal: 47.45

subtotal: 119.53, contingencyPercent: 20, totalWithContingency: 143.44. Os preços são os que a pessoa consultou em 2026-10-20 nas páginas de preço do Fargate e do RDS PostgreSQL; não estou afirmando que valem hoje, e esse é o ponto: a data viaja junto com o número, e quem ler em março sabe que precisa reconsultar.

O que torna esse resultado defensável é o que ele declara que não contém. excluded lista impostos, câmbio, transferência de dados, logs e suporte não modelados, e créditos, descontos e free tier a menos que você os coloque como item. creditWarning diz que créditos dependem da elegibilidade e vencem, e que orçamento não é teto de gasto. Eu já vi conta de startup dobrar no mês em que o crédito acabou e ninguém tinha escrito isso em lugar nenhum.

Contra o orçamento de USD 1.500 do marketplace, 143 dólares parece folga. É folga para um container e um banco, sem NAT Gateway, sem egress, sem WAF, sem ambiente de homologação. A disciplina é a da skill cost-scenario do próprio servidor (recurso moretes://skills/cost-scenario): liste toda dimensão cobrável por componente, ache a página de preço e anote URL e data, separe fixo de variável, 20% de contingência por padrão, liste as exclusões, nunca modele crédito como desconto, e grave o cenário num decision_record com a data do preço.

cost_model: um item do pedido e o resultado (docs/examples/cost_model.json), encurtado. Sem source e priceDate a chamada é rejeitada.
json
// arguments (1 of 3 items shown)
{
  "currency": "USD",
  "items": [
    {
      "name": "Fargate vCPU-hours",
      "quantity": 1460,
      "unit": "vCPU-hour",
      "pricePerUnit": 0.04048,
      "source": "https://aws.amazon.com/fargate/pricing/",
      "priceDate": "2026-10-20"
    }
  ],
  "contingencyPercent": 20
}

// result
{
  "kind": "scenario-not-quote",
  "currency": "USD",
  "items": [
    { "name": "Fargate vCPU-hours", "subtotal": 59.1008 },
    { "name": "Fargate GB-hours", "subtotal": 12.9794 },
    { "name": "RDS db.t4g.medium", "subtotal": 47.45 }
  ],
  "subtotal": 119.5302,
  "totalWithContingency": 143.43624,
  "contingencyPercent": 20,
  "excluded": [
    "Taxes",
    "Exchange rate",
    "Unlisted data transfer, logs and support",
    "Credits, discounts and free tiers unless explicitly modeled"
  ],
  "creditWarning": "Credits depend on your billing eligibility and expiration; budgets are not hard spending caps."
}

finops_unit_economics e storage_growth: quanto custa cada pedido, e quanto vai custar guardar

A fatura de setembro de 2026 do marketplace, por linha: Fargate baseline 420 e RDS 95 em fixedCosts; API Gateway 38 e transferência de dados 22 em variableCosts. Unidades: 410.000 pedidos e 52.000 clientes ativos. Meta: 0,001 USD por pedido. O finops_unit_economics devolve:

  • totals: fixo 515, variável 60, total 575, fixedSharePercent: 89.6.
  • perUnit[0]: costPerUnit: 0.001402 por pedido, sendo 0,001256 de fixo e 0,000146 de variável.
  • marginalCostPerPrimaryUnit: 0.000146: o pedido número 410.001 custa isso.
  • breakEvenVolumeForTarget: 603286: 515 / (0,001 − 0,000146).

A leitura que a ferramenta escreve é a decisão: mostly fixed: unit cost falls with volume; idle capacity is the lever. Com 89,6% de custo fixo, otimizar requisição não move o número; o que move é vender mais pedidos sobre a mesma infraestrutura ou reduzir a infraestrutura ociosa. Se a meta fosse menor que o custo marginal, a ferramenta avisaria que volume nenhum a alcança. A limitação lembra que a alocação é proporcional, ignora rateio de custo compartilhado, e que a fonte de cada valor tem que ser fatura datada.

O storage_growth projeta o lago de eventos: 25 GB por dia de ingestão, retenção de 365 dias, 3% de crescimento ao mês, compressão 2,5 (10 GB/dia armazenados), 30 dias na camada quente a 0,023 por GB-mês e o resto na fria a 0,004. Os preços são os que você digitou; a ferramenta não traz tabela nenhuma. A series de 24 meses mostra a curva: mês 1 com 300 GB e 6,90; mês 12 com 4.257,6 GB e 24,92; a partir do mês 13 a retenção descarta os dias mais velhos e a curva achata; mês 24 com finalStoredGB: 6139.5 (6 TB) e 35,81 por mês. tiering resume: objetos com mais de 30 dias na camada fria deixam 90,4% dos dados frios no mês 24. steadyState: false porque o crescimento não é zero. Limitações: meses de 30 dias, sem metadados, versionamento, custo de requisição nem taxa de recuperação.

Pergunta → ferramenta → o que você traz → o campo que você lê

PerguntaFerramentaEntrada que você precisa trazerCampo do resultado para ler
Quantas execuções simultâneas eu preciso?capacity_planrequestsPerSecond, durationMs, payloadKB, headroom (padrão 1.3)plannedConcurrency, dailyPayloadGB
Cabe em 2 segundos no p95?latency_budgettargetMs, hops[] com estimatedMs, parallelGroup, cacheableremainingMs, hops[].overEqualShare, suggestions
Quantos consumidores a fila precisa?queue_sizingarrivalsPerSecond, serviceTimeMs, consumers, backlogMessages, targetUtilizationPercentstable, consumersForTargetUtilization, backlogDrainSeconds
Quanto tempo posso ficar fora do ar?slo_budgetavailabilityPercent, periodDays, usedDowntimeMinutesremainingMinutes, exhausted
Quanto custa por mês?cost_modelitems[] com quantity, pricePerUnit, source (URL) e priceDate (obrigatórios)totalWithContingency, excluded, creditWarning
Quanto custa cada pedido?finops_unit_economicsfixedCosts[], variableCosts[] da fatura datada, units[], targetCostPerPrimaryUnitperUnit[].costPerUnit, breakEvenVolumeForTarget, reading
Quanto espaço (e custo) em 24 meses?storage_growthdailyIngestGB, retentionDays, compressionRatio, hotDays, preços hot/cold por GB-mêsfinalStoredTB, tiering, series[]

O que você leva desta aula

Lei de Little resolve dois problemas desta aula: 5 pedidos/s × 0,4 s = 2 execuções simultâneas (capacity_plan) e 5 msg/s × 0,8 s = 4 Erlangs (queue_sizing). Concorrência é taxa vezes tempo de permanência.
Num orçamento de latência, grupo paralelo conta só o caminho crítico (350 ms, não 470), e dois saltos levam 85% dos 1460 ms. Percentis não somam: 1460 é teto conservador.
Utilização de 100% numa fila não é "eficiente", é instável: stable: false, backlog nunca esvazia. Para 60% de utilização são 7 consumidores, e a hipótese é M/M/c; rajada espera mais.
99,9% em 30 dias são 43,2 minutos. Com 12 usados sobram 31,2. É SLO por tempo; disponibilidade por requisição precisa de contagem de eventos.
cost_model não aceita item sem source e priceDate; devolve scenario-not-quote, 20% de contingência e a lista excluded. Crédito não é desconto e orçamento não é teto.
Com 89,6% de custo fixo, o custo por pedido (0,001402) cai com volume; a meta de 0,001 só chega em 603 mil pedidos/mês. storage_growth mostra 6 TB e 90,4% frio no mês 24, com os preços que você trouxe.
Quiz

Faça a conta

1. No orçamento do checkout (alvo 2000 ms, total 1460 ms), o fornecedor de antifraude avisa que o `fraud score` vai subir de 350 para 700 ms. Ele continua no `parallelGroup: "pre"` com o `inventory check` de 120 ms. Qual passa a ser o `estimatedTotalMs`?
2. SLO de 99,9% em 30 dias, 12 minutos já usados. Um incidente de 35 minutos acontece hoje. O que o `slo_budget` devolve ao final?
3. A fila recebe 5 mensagens por segundo e cada uma leva 800 ms. Quantos consumidores (concorrência 1 cada) o `queue_sizing` devolve em `consumersForTargetUtilization` para 60% de utilização?
4. No `finops_unit_economics` de setembro: fixo 515, variável 60, 410.000 pedidos, meta de 0,001 USD por pedido. Por que `breakEvenVolumeForTarget` dá cerca de 603 mil e não 575 mil (575 / 0,001)?

Referências

Moretes Architect MCP: repositório (src/reliability.ts, src/architecture.ts, docs/examples/) · GitHub, MITInventário de ferramentas do servidor público (/tools.json) · Moretes Architect MCP v0.2.0AWS Well-Architected, Reliability Pillar: Availability · AWS DocumentationAmazon SQS visibility timeout · AWS DocumentationAWS Fargate pricing · AWSAWS Well-Architected, Cost Optimization Pillar · AWS Documentation
Concluir e ir para a próxima Aula anterior