Notícias8 min de leitura

Como medir desperdício de computação em pipelines e serviços

Combine duração, CPU, memória, cache e frequência de execução para achar otimizações com impacto real em CI/CD e serviços — sem chasing de média enganosa.

  • devops
  • ci-cd
  • finops
  • observabilidade
  • performance
  • cache
  • sre
  • otimizacao

Um pipeline de 12 minutos que roda em 200 PRs por dia e um serviço que fica o dia inteiro a 8% de CPU. Qual desperdiça mais computação? A resposta intuitiva (“o serviço ocioso”) costuma estar errada. Desperdício não é CPU baixa. É recurso × tempo × frequência, distorcido por cache miss, runner errado, retry e overprovisioning.

Eu vejo times otimizarem a média de CPU de um pod e ignorarem o workflow que come metade da fatura de Actions. Ou o contrário: cortarem minutos de CI e deixarem um m5.2xlarge acordado 24h para um job noturno de uma hora. O método abaixo existe para achar alavancas com impacto real — sem chasing de métrica isolada.

Uma fórmula útil (de propósito grosseira)

Para pipelines e jobs batch:

impacto ≈ duração_efetiva × custo_do_runner × frequência × fator_de_desperdício

Onde fator_de_desperdício sobe com cache miss, flake/retry, fila que gera re-trigger, e trabalho que não precisava rodar (path filter ausente, schedule zumbi).

Para serviços sempre ligados, troque frequência por uptime com demanda real e separe duas contas:

  • Waste (lixo): recurso que não entrega valor — idle genuíno, disco órfão, artifact que ninguém baixa.
  • Overprovisioning: workload vivo, só que oversized — candidato a rightsizing com guarda de SLO, não a “desligar e torcer”.

A FinOps Foundation trata redução de waste como prioridade recorrente; o erro clássico é usar só “CPU abaixo de 5%” e matar um batch noturno que parece idle 23 horas por dia. Sinal de aplicação (tráfego, conexões, erros, fila) separa quieto-mas-vivo de morto-de-verdade.

Cinco eixos que precisam andar juntos

1. Duração (e a cauda)

Olhe p50, p90 e p95 — não só a média. A razão p90 / p50 conta a história da variância. Em guias de CostOps para CI, algo na faixa 1,3–1,5× é comum; acima de ~2,5× quase sempre há loteria: cache frio, fila de runner, hardware heterogêneo, flake.

Duração de wall-clock ≠ minutos faturáveis. Fila (tempo até o runner pegar o job) irrita o desenvolvedor e empurra re-runs; a fatura começa quando o job de fato roda. Ainda assim, fila instável é desperdício de atenção e, indiretamente, de compute via retries.

2. CPU

CPU alta com duração alta: talvez paralelismo ou máquina maior ajude — se o speedup pagar o multiplicador de preço. Um runner 4× mais caro precisa terminar bem mais que 4× mais rápido para reduzir custo; Analytics de GitHub Actions / Depot deixam esse trade-off explícito.

CPU baixa com duração alta: o job está esperando rede, lock, teste serial, ou sleep disfarçado de “build”. Mais core não resolve. Cache, I/O e critical path resolvem.

Em serviços, média de CPU é a métrica mais mentirosa do rightsizing. Picos curtos (ingest matinal, checkout) somem na média e voltam como incidente depois do downsize. Prefira percentis no pico de demanda, não a média da semana.

3. Memória

Use pico / working set, não a média. Workload JVM ou Node “com CPU folgada” pode estar no limite de heap; downsize de instância corta RAM e o GC come o que você achou que economizou. Em nuvem, memória muitas vezes não entra no métricas default (CloudWatch sem agent, por exemplo) — sem ela, Compute Optimizer e qualquer planilha mentem.

Em CI, OOM mata o job, gera retry e infla minutos. Ferramentas que expõem CPU/memória por agente ou por task (Nx Cloud resource usage, runners com telemetria) transformam “o build às vezes morre” em “este projeto estoura RAM quando o paralelismo passa de N”.

4. Cache

Cache hit rate é o multiplicador escondido da duração. O mesmo workflow: 6 minutos com cache quente, 25 com frio. Se a chave inclui SHA do commit, ou se branches nunca compartilham restore-keys estáveis baseadas em lockfile, cada PR paga install completo. A variância explode; o p50 fica otimista; a fatura segue o p90.

Boas práticas chatas e eficazes: chave estável (hash de lockfile), restore-keys de fallback, imagem base enxuta, menos “um container gigante para todos os jobs”. GitLab documenta o mesmo eixo: dependências, tamanho de imagem e storage de artifact são parte da eficiência do pipeline, não acessórios.

5. Frequência

Um job medíocre que roda a cada push em monorepo sem path filters pode custar mais que um job lento e raro. Cancele runs superados (concurrency / cancel-in-progress). Evite matrix que multiplica acidente. Trate flake como custo de compute, não só como irritação: cada retry é duração × runner de novo.

Em serviços, “frequência” vira horário de carga e escala. Autoscaling mal calibrado (min replicas altas “por segurança”) é overprovisioning com nome elegante.

Waste versus overprovisioning

Tipo O que é Sinal típico Ação
Waste Zero valor Sem tráfego, sem conexões, job morto, artifact eterno Remover, expirar, desligar com checagem de schedule
Overprovisioning Valor real, capacidade sobrando Tráfego existe; CPU/memória/rede com folga no pico Rightsizing / right-typing com guarda de SLO
Subprovisioning Capacidade faltando Latência, erro, OOM, fila Subir recurso ou repartir carga — otimizar custo depois

Os four golden signals do SRE (latência, erros, tráfego, saturação) são o filtro para não chamar de waste o que é só horário off-peak. Idle criteria de cloud (ex.: pico de CPU muito baixo e rede quase nula por lookback) ajudam no lixo óbvio; batch e APIs com spike pedem contexto de aplicação.

Método em quatro passos

  1. Rankear por custo total no período (minutos faturáveis × preço do runner; ou $/serviço). Não por “job mais lento”. O lento e raro pode ser teatro; o mediano e frequente paga as contas.
  2. Abrir o top 3. Por job: steps, CPU/memória, cache hit, fila. Por serviço: percentis no pico, memória, rede, latência e erros.
  3. Cruzar os cinco eixos com a mini-matriz abaixo. Escolher uma hipótese dominante.
  4. Uma mudança por vez, medir uma semana representativa (duração, taxa de falha, minutos/$, p95 de latência). Sem big-bang de “otimizamos o CI”.

Sintoma → causa provável → alavanca

Sintoma Causa provável Alavanca
Duração alta, CPU baixa Espera de rede, lock, etapa serial Cache de deps, parallelize o critical path, menos download
Duração alta, CPU saturada Compute bound de verdade Runner maior só se o speedup passa do multiplicador de preço; ou algoritmo/artifact
p90/p50 alto Loteria de cache, fila, flake Lockfile keys, restore-keys, timeout, menos re-run
CPU baixa, memória alta Memory-bound Right-type (mais RAM), não só menos vCPU
Minutos altos, job “ok” Frequência Path filters, concurrency cancel, menos matrix
Serviço “8% CPU” 24h Média mentirosa ou idle real Olhar pico + tráfego; idle genuíno → consolidar; vivo → rightsizing no pico
OOM intermitente no CI Paralelismo × working set Limitar paralelismo do monorepo; subir RAM do runner com conta explícita

Checklist: pipelines

  • Workflows ordenados por minutos totais no mês, não só por duração média.
  • p50/p90 (e razão) por job crítico; cauda investigada.
  • Cache com chave de lockfile + fallback; hit rate acompanhado depois de mudanças.
  • Path filters / rules para não buildar o monorepo inteiro a cada doc typo.
  • Cancel de runs superados; timeout para job zumbi.
  • Runner size justificado com antes/depois de wall-clock e custo.
  • Artifacts com expire_in / retenção; registry sem imagem órfã eterna.
  • Flake rate entra no ranking de custo (retry × duração).

Checklist: serviços

  • Separar candidatos a waste (sem tráfego/conexões no lookback) de rightsizing.
  • Memória coletada de fato; sizing pelo working set no pico + headroom (faixa típica 10–30% conforme risco).
  • CPU/memória/rede/latência juntos — rightsizing em uma dimensão só gera rollback.
  • Batch e cron avaliados no horário do job, não na média diária.
  • Mudança canário + SLO / error budget como freio; uma família de instância por vez.
  • Autoscaling com mínimo honesto; “min=10 por medo” é linha na fatura.

O que otimizar primeiro

Ordene alavancas por impacto estimado × esforço. Na maioria dos repositórios que eu vejo, cache estável e cortar frequência inútil batem runner maior. Em serviços, idle óbvio e right-type memory-bound batem “reduzir 10% CPU média”.

Desperdício de computação se mede com produto de eixos, não com um gráfico verde. Duração, CPU, memória, cache e frequência — juntos — dizem onde o próximo PR de otimização paga a si mesmo.

Se você só pudesse instrumentar uma coisa amanhã no CI, seria minutos totais por workflow ou hit rate de cache? E no serviço: memória no pico, ou tráfego junto da CPU?

Referências

Adelson de Bittencourt Junior

Desenvolvedor full-stack em Criciúma, SC, com passagem por suporte de telefonia VoIP e redes. Escreve sobre redes, segurança, infraestrutura e bancos de dados. Sobre o site · Enviar correção