Pipeline lento é dinheiro parado: como cortar o tempo de build

Build de 40 minutos custa caro em fatura e em foco do time. Onde o tempo se perde, o que cache, paralelismo e build incremental resolvem, e em que ordem atacar.

Equipe Solvefy 7 min de leitura

Um pipeline de 40 minutos não é um detalhe de infraestrutura. Ele muda o comportamento do time inteiro: as pessoas param de rodar o pipeline para validar pequenas mudanças, acumulam alterações em commits grandes, trocam de contexto enquanto esperam e voltam ao trabalho anterior com metade da atenção. Quando o build quebra, a correção demora mais 40 minutos para ser confirmada.

E, em plataforma de CI cobrada por minuto, isso também é uma linha na fatura que cresce com o tamanho do time.

A boa notícia é que a maior parte do tempo perdido está concentrada em poucos lugares previsíveis, e o ganho costuma vir rápido.

Primeiro: meça onde o tempo está

Antes de otimizar qualquer coisa, obtenha a distribuição do tempo por etapa. Todas as plataformas de CI mostram isso, e quase ninguém olha.

O padrão que aparece na maioria dos pipelines lentos:

EtapaFatia típicaCostuma ser
Instalar dependências25–40%Quase todo eliminável com cache
Build da imagem de container20–35%Muito reduzível com camadas e cache
Testes20–40%Reduzível com paralelismo e separação por estágio
Preparar o ambiente/runner5–15%Reduzível com imagem base própria
Deploy5–10%Geralmente já é o menor

Se você não sabe qual dessas linhas é a sua maior, está prestes a otimizar a errada. Meça antes.

Cache de dependências: o ganho mais barato que existe

Baixar as mesmas bibliotecas a cada execução é puro desperdício. Todas as plataformas de CI têm mecanismo de cache, e configurá-lo é questão de minutos.

O que importa para o cache funcionar de verdade:

  • A chave precisa ser o arquivo de lock, não o nome do branch. Enquanto o lock não muda, o cache é reaproveitado — que é exatamente o comportamento desejado.
  • Chave de reserva parcial ajuda: se o lock mudou, aproveita-se o cache mais próximo e baixa-se só a diferença.
  • Cache do gerenciador de pacotes, não da pasta de módulos instalados. É mais portável e menos propenso a estado inconsistente.
  • Verifique se o cache está sendo usado. É comum configurar cache com chave errada e ele nunca acertar — o pipeline continua lento e todo mundo acha que já foi otimizado. O log diz se houve acerto; leia.

Em projetos com muitas dependências, essa única mudança costuma cortar de 5 a 15 minutos.

Build de imagem: camadas na ordem certa

A regra do cache de camada de container é simples e frequentemente violada: uma camada invalida todas as seguintes. Se você copia o código-fonte inteiro antes de instalar as dependências, qualquer alteração em qualquer arquivo invalida a instalação — e ela roda de novo, toda vez.

A ordem correta é sempre a mesma:

  1. Copiar apenas os arquivos de manifesto e lock.
  2. Instalar as dependências.
  3. Copiar o restante do código.
  4. Compilar.

Assim, mudança de código só invalida da etapa 3 em diante. Dependência só é reinstalada quando o lock muda de verdade.

Some a isso:

  • Multi-stage, com o estágio de build separado do de execução. A imagem final leva só o artefato, o que reduz tamanho e tempo de envio ao registry.
  • Um arquivo de exclusão (.dockerignore) bem escrito. Enviar a pasta de módulos, o .git e os artefatos locais para o contexto de build custa tempo em toda execução.
  • Cache de camadas entre execuções, exportado para o registry ou para o cache da plataforma. Sem isso, runner efêmero começa do zero sempre.
  • Imagem base enxuta e fixada por versão. Base menor baixa mais rápido; versão fixa evita rebuild surpresa.

Testes: paralelismo e estágios

Testes costumam ser a maior fatia depois que dependências e build estão resolvidos. Três frentes, em ordem de retorno:

Paralelize. A maioria dos executores de teste divide a suíte em partes que rodam em máquinas simultâneas. Dividir em quatro não corta o tempo por quatro — há sobrecarga de inicialização —, mas corta bastante. É a mudança de maior impacto na etapa de testes.

Separe por estágio. Nem todo teste precisa rodar a cada commit:

  • A cada commit: lint, teste unitário, build. Deve terminar em poucos minutos e é o portão que dá retorno rápido a quem programou.
  • A cada pull request: integração, contrato, scan de segurança.
  • Antes do merge ou em agenda: ponta a ponta, carga, matriz completa de versões.

Isso não reduz o tempo total de teste do projeto; reduz o tempo até o desenvolvedor saber que quebrou, que é a métrica que muda comportamento.

Elimine espera artificial. Teste que dorme por tempo fixo esperando serviço subir é um clássico. Substitua por espera ativa com verificação de saúde: o teste segue assim que o serviço responde, em vez de esperar o pior caso sempre.

E um cuidado: teste instável (que falha aleatoriamente) custa mais que teste lento, porque gera reexecução completa e destrói a confiança no pipeline. Vale rastrear os instáveis e corrigi-los ou isolá-los.

Execute só o que mudou

Em monorepo, rodar o pipeline inteiro quando só um serviço mudou é o maior desperdício isolado que existe.

Duas abordagens:

  • Filtro por caminho, nativo nas plataformas de CI: o job de um serviço só dispara se arquivos daquela pasta mudaram. É simples e resolve a maior parte dos casos.
  • Ferramenta de build com grafo de dependência, que sabe o que foi afetado pela mudança e executa apenas isso, com cache de resultado. É mais poderoso e exige mais investimento inicial.

Comece pelo filtro por caminho. O ganho aparece no mesmo dia.

O runner: a etapa que ninguém olha

Se cada execução começa instalando as mesmas ferramentas — compilador, CLI de nuvem, utilitários —, você está pagando esse tempo em toda execução.

A correção é construir uma imagem base própria do runner, com tudo já instalado, publicada no seu registry e atualizada por um pipeline separado, semanal. As execuções passam a começar prontas.

Para builds pesados, runner auto-hospedado com disco rápido e cache local persistente muda o patamar — especialmente para compilação e para build de imagem. O contrapeso é que ele deixa de ser efêmero, então precisa de limpeza periódica e de atenção a segurança.

A ordem de ataque

Se tudo estiver ruim ao mesmo tempo, siga esta ordem — ela é decrescente em retorno por esforço:

  1. Medir a distribuição do tempo por etapa.
  2. Cache de dependências, e confirmar que ele está acertando.
  3. Reordenar as camadas do build da imagem e escrever o arquivo de exclusão.
  4. Paralelizar os testes.
  5. Separar os testes por estágio, com retorno rápido no commit.
  6. Filtrar por caminho o que roda.
  7. Imagem base própria do runner.
  8. Cache de camadas exportado e, se necessário, runner dedicado.

O número que vale acompanhar

Não é o tempo do pipeline completo. É o tempo do commit até o retorno que diz se quebrou. Esse é o número que altera o comportamento do time — e é ele que deve ficar em um painel visível.

Uma meta realista para começar: retorno em menos de 10 minutos no commit, pipeline completo em menos de 30. Times que chegam lá param de acumular mudança e voltam a integrar com frequência, que era o objetivo do CI desde o começo.

Erros comuns

  • Otimizar sem medir, e atacar a etapa que não é a maior.
  • Configurar cache com chave errada e nunca verificar se acerta.
  • Copiar o código antes de instalar as dependências no build da imagem.
  • Não ter arquivo de exclusão, enviando .git e módulos no contexto de build.
  • Usar imagem base grande e sem versão fixa.
  • Rodar a suíte inteira, inclusive ponta a ponta, a cada commit.
  • Espera por tempo fixo em vez de verificação de saúde nos testes.
  • Conviver com testes instáveis e reexecutar o pipeline inteiro por causa deles.
  • Rodar o pipeline completo do monorepo por mudança em um único serviço.
  • Instalar as mesmas ferramentas no runner em toda execução.
  • Resolver lentidão comprando runner maior, sem tocar na causa.

Onde a Solvefy/Cloud entra

Otimização de pipeline é um dos trabalhos de retorno mais rápido da nossa consultoria: começamos medindo a distribuição real do tempo, atacamos na ordem de retorno e entregamos o antes e depois em minutos e em custo de execução. Também deixamos o painel do tempo até o primeiro retorno e a documentação das decisões — para o ganho não se perder no trimestre seguinte. Trabalhamos com GitHub Actions, GitLab CI e Jenkins.

Quanto tempo o seu time passa esperando o pipeline? Diagnóstico gratuito, sem compromisso, em solvefy.cloud.

Solvefy/Cloud — Infraestrutura que cresce com você.

Faça agora o seu diagnóstico e orçamento

Inicie com um diagnóstico gratuito da sua infraestrutura e receba uma proposta personalizada — com escopo, cronograma e valores.