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 quiserDica: 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.
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: 432000edailyPayloadGB: 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.
// 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.
// 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.
// 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.001402por 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ê
| Pergunta | Ferramenta | Entrada que você precisa trazer | Campo do resultado para ler |
|---|---|---|---|
| Quantas execuções simultâneas eu preciso? | capacity_plan | requestsPerSecond, durationMs, payloadKB, headroom (padrão 1.3) | plannedConcurrency, dailyPayloadGB |
| Cabe em 2 segundos no p95? | latency_budget | targetMs, hops[] com estimatedMs, parallelGroup, cacheable | remainingMs, hops[].overEqualShare, suggestions |
| Quantos consumidores a fila precisa? | queue_sizing | arrivalsPerSecond, serviceTimeMs, consumers, backlogMessages, targetUtilizationPercent | stable, consumersForTargetUtilization, backlogDrainSeconds |
| Quanto tempo posso ficar fora do ar? | slo_budget | availabilityPercent, periodDays, usedDowntimeMinutes | remainingMinutes, exhausted |
| Quanto custa por mês? | cost_model | items[] com quantity, pricePerUnit, source (URL) e priceDate (obrigatórios) | totalWithContingency, excluded, creditWarning |
| Quanto custa cada pedido? | finops_unit_economics | fixedCosts[], variableCosts[] da fatura datada, units[], targetCostPerPrimaryUnit | perUnit[].costPerUnit, breakEvenVolumeForTarget, reading |
| Quanto espaço (e custo) em 24 meses? | storage_growth | dailyIngestGB, retentionDays, compressionRatio, hotDays, preços hot/cold por GB-mês | finalStoredTB, tiering, series[] |
O que você leva desta aula
capacity_plan) e 5 msg/s × 0,8 s = 4 Erlangs (queue_sizing). Concorrência é taxa vezes tempo de permanência.stable: false, backlog nunca esvazia. Para 60% de utilização são 7 consumidores, e a hipótese é M/M/c; rajada espera mais.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.storage_growth mostra 6 TB e 90,4% frio no mês 24, com os preços que você trouxe.