Índice do artigofaltam 2 min de leitura
Cache é uma das maiores alavancas de performance em CI/CD. Também é uma das maiores fontes de bugs intermitentes.
O objetivo deste post é prático: ensinar a desenhar cache keys que aceleram o pipeline sem deixar você com builds “assombrados”.
Este post faz parte da série:
Mapa da série CI/CD
- Guia de CI/CD no Git para times pequenos: performance, cache e runners
- GitLab CI: tags de runner e roteamento de jobs
- Cache no CI/CD: como desenhar keys e evitar cache inválido (GitHub e GitLab)
- Docker layer caching no CI: BuildKit, buildx, cache-from e cache-to
- Runner dedicado no CI/CD: quando vale a pena e como planejar em time pequeno
- Runners macOS no CI: Intel vs Apple Silicon, custos e armadilhas
Cache vs artifacts: comece separando o que é o quê
Regra de bolso:
- Cache = reutilizar diretórios reconstruíveis (dependências, downloads).
- Artifacts = transportar resultado do build entre jobs (dist, binário, relatório).
Quando você usa cache para transportar output de build, você aumenta o risco de:
- um job consumir um output “antigo”
- resultados inconsistentes por branch/arquitetura
1) Como desenhar cache keys (o que entra e o que não entra)
Um cache key bom costuma ter:
- Sistema operacional
- Arquitetura (ARM64/x64)
- Hash do lockfile
- Versão do runtime (quando necessário)
Um cache key ruim costuma ter:
- SHA do commit (invalida a cada commit)
- timestamp
- nome de branch sem normalização (explode número de caches)
1.1 Exemplo (GitHub Actions)
- name: Cache npm
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-${{ runner.arch }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-npm-Por que isso funciona:
- O hash do lockfile só muda quando dependência muda.
- O prefixo em
restore-keyspermite reaproveitar cache “próximo” quando o exato não existe. runner.archevita misturar ARM64 e x64.
1.2 Exemplo (GitLab CI/CD)
cache:
key: "node-${CI_RUNNER_EXECUTABLE_ARCH}-${CI_COMMIT_REF_SLUG}-${CI_PROJECT_NAME}"
paths:
- .npm/
fallback_keys:
- "node-${CI_RUNNER_EXECUTABLE_ARCH}-main-${CI_PROJECT_NAME}"
- "node-${CI_RUNNER_EXECUTABLE_ARCH}-default-${CI_PROJECT_NAME}"Por que isso funciona:
- Separa por arquitetura.
- Permite fallback da branch principal para branches novas.
- Evita cache “cross-project” (quando você tem múltiplos projetos e runners compartilhados).
2) Onde cachear (Node, Python, Java, Ruby)
Regra geral: cacheie o “cache do gerenciador”, não necessariamente a pasta final.
- Node (npm):
~/.npm - Node (pnpm):
~/.pnpm-store - Python (pip):
~/.cache/pip - Java/Gradle:
~/.gradle/caches - Ruby/bundler:
vendor/bundle(com critério)
O que evitar como primeiro passo:
- cachear
node_modulesindiscriminadamente (pode ser enorme e instável) - cachear outputs de build (isso é artifact)
3) Invalidação: o que muda cache (e como evitar “cache inútil”)
Três causas clássicas de cache “sempre miss”:
- O key muda a cada commit (ex.: inclui SHA).
- O lockfile não está sendo incluído no hash (cache fica stale e pode quebrar).
- Você está cacheando um diretório que muda por execução.
Checklist para corrigir:
- lockfile faz parte do key (
hashFiles(...)ou equivalente). - arquitetura faz parte do key.
- os paths de cache realmente existem (não inventar diretório).
- o cache é restaurado antes de instalar dependências.
4) Cache em pipelines com ARM64 e x64
Quando você tem ARM64 e x64:
- caches de dependências nativas não podem ser compartilhados
- caches precisam ser duplicados (um por arquitetura)
Regra prática:
- nunca compartilhe cache entre arquiteturas sem colocar
archno key
Checklist final
- Cache key inclui lockfile hash.
- Cache key inclui OS + arquitetura.
- restore/fallback keys existem (mas não para output de build).
- Cache é para dependências (artifact é para outputs).
- Métrica observável: tempo economizado por job (antes/depois).