Pular para o conteúdo
use-cases / a-ci-cache-thats-just-two-curl-commands / hero
PIPE · CI · OTIMIZAÇÃO DE CUSTO

Um cache de CI que é só dois comandos curl

O primeiro job da sua pipeline tara node_modules e encanca para uma URL Hoody. Vinte jobs downstream fazem curl na mesma URL e desempacotam. Com ?n=20 o produtor espera os vinte workers se conectarem, depois transmite uma vez — distribuído para todos. Sem bucket S3, sem cache action, sem conta de egress.

use-cases / a-ci-cache-thats-just-two-curl-commands / flow

A mecânica inteira em duas linhas de shell

Não tem biblioteca cliente, daemon nem SDK. O produtor encanca seu tarball para PUT /pipe/cache?n=20. Cada worker encanca de volta a partir de GET /pipe/cache?n=20. O pipe segura os bytes só enquanto eles estão em trânsito — nunca em disco.

PUT · PRODUTOREXECUTAR UMA VEZ POR BUILD

Produzir o cache

$tar c node_modules|zstd|curl -T -
https://hoody.com/pipe/cache?n=20
# blocks until 20 receivers connect, then streams once

tar empacota node_modules; zstd comprime no ato; curl faz PUT dos bytes direto no caminho do pipe. Sem tempfile, sem passo de upload-artifact, sem credenciais de bucket.

GET · CONSUMIDOREXECUTAR EM CADA WORKER

Consumir o cache

$curlhttps://hoody.com/pipe/cache?n=20
| zstd -d | tar x
# fan-out: every worker gets an identical copy

Cada worker de teste faz GET na mesma URL, descomprime e desempacota no diretório de trabalho. Workers lentos aplicam backpressure no produtor mas não bloqueiam os mais rápidos.

TODA A SUPERFÍCIE DE COORDENAÇÃO?n=20

Um query parameter. Produtor e consumidor concordam no mesmo n. O pipe segura o upload até exatamente esse número de receivers estarem conectados, depois abre as comportas.

use-cases / a-ci-cache-thats-just-two-curl-commands / relief

O que você para de pagar

Cache de CI sempre foi imposto: storage que você sobrescreve amanhã, egress toda vez que um worker puxa, tempo de engenharia nas manias do cache action. O pipe apaga as três linhas de uma vez.

Sem bucket. Sem conta de egress.

Não existe bucket S3 porque não existe storage. O pipe esquece os bytes no segundo em que a transferência termina, então não tem nada para cobrar por GB-mês ou por GB-saída. O cache deixa de ser uma linha do orçamento.

Sem manias de cache action

Sem chaves codificadas em yaml, sem split de save-cache / restore-cache, sem debugar por que um cache hit não aconteceu no runner certo. Só curl. As mesmas duas linhas rodam em GitHub Actions, BuildKite, Jenkins, no seu notebook ou em um contêiner cron.

Sem colisão de chave entre branches

Branches só usam caminhos diferentes. /pipe/cache/main, /pipe/cache/feat-x, /pipe/cache/PR-742. Nada para invalidar. Nada para evictar. Quando a branch morre, o caminho dela deixa de ser pedido e esse é o ciclo de vida inteiro.

use-cases / a-ci-cache-thats-just-two-curl-commands / compare

A forma do custo, antes e depois

Em uma carga real — node_modules de uns 800 MB, vinte workers de teste paralelos, cem builds de CI por dia — a maior parte da conta é egress, não storage.

S3 + GH ACTIONS CACHE

20× egress

Cada um dos vinte workers puxa o cache do S3. Vinte downloads de um tarball de 800 MB são 16 GB de egress por build. O bucket em si é a parte fácil — o egress é o que se acumula.

VS
HOODY PIPE · ?n=20

1× transferência

O produtor transmite os 800 MB uma vez. O pipe distribui os bytes para todos os vinte receivers em trânsito. Uma transferência pelo fio, sem multiplicador por receiver, sem conta de storage.

Os números são ilustrativos para um cache típico de monorepo Node. A economia real depende do tamanho do tarball, do fan-out de workers e do preço de egress que seu provedor cobra na região do cache. A forma — linear vs. constante na contagem de workers — é invariante.

use-cases / a-ci-cache-thats-just-two-curl-commands / punchline

A camada de cache é HTTP. Sempre foi. Só não tinham notado.

Caches nunca foram sobre storage. Eram sobre entregar os mesmos bytes a N workers sem reconstruir. HTTP já faz isso — assim que você deixa uma URL distribuir para um número conhecido de receivers. O bucket era um workaround para o fan-out que não existia.

BYTES

Transmita uma, distribua

Um PUT, n GETs, bytes idênticos. O backpressure é por receiver, então o worker lento não freia os rápidos.

PUT /pipe/cache?n=20
TEMPO

Vivo só em trânsito

O pipe segura os bytes pela transferência e os esquece quando ela termina. Nada para evictar, nada para gerenciar ciclo de vida, nada para fazer backup.

TTL ≤ 5 min · depois evictado
CAMINHO

Branches são caminhos

Cada branch escolhe seu caminho. Sem keyspace compartilhado, sem colisões. O caminho é a chave do cache e a URL ao mesmo tempo.

/pipe/cache/[branch]
Ler a API de pipe
use-cases / a-ci-cache-thats-just-two-curl-commands / replaces

O que isso substitui

A maioria dos caches de CI resolve o mesmo problema: entregar o mesmo tarball a N workers. Eles fazem isso por storage e egress. O pipe faz pelo fio.

  • AWS S3 (bucket de cache + egress)Storage que você sobrescreve amanhã + egress por puxada
  • GitHub Actions cacheChaves yaml, manias específicas de runner, teto de 10 GB
  • Caches do BuildJet / Garnix CIMais rápido, ainda por fornecedor, ainda por byte
  • Bazel remote cacheExcelente se você for full Bazel; pesado se não for
  • Turborepo remote cacheHospedado pela Vercel, em formato monorepo, opinativo
  • Earthly satellite cacheOutro daemon, outro bucket, outra conta
  • Caches rsync customizadosUma caixa NFS e uma chave SSH que ninguém lembra de rotacionar
use-cases / a-ci-cache-thats-just-two-curl-commands / cta

Dois comandos curl. Uma URL. Vinte workers alimentados.

use-cases / a-ci-cache-thats-just-two-curl-commands / related

Leia os outros