Ir al contenido
use-cases / a-ci-cache-thats-just-two-curl-commands / hero
PIPE · CI · OPTIMIZAR COSTES

Una caché de CI que son solo dos comandos curl

El primer job en tu pipeline empaqueta node_modules con tar y lo redirige a una URL de Hoody. Veinte jobs aguas abajo hacen curl a esa misma URL y desempaquetan. Con ?n=20 el productor espera a que se conecten los veinte workers, y luego transmite una sola vez — distribuido a todos. Sin bucket S3, sin acción de caché, sin factura de egress.

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

Toda la mecánica en dos líneas de shell

No hay librería cliente, ni daemon, ni SDK. El productor transmite su tarball a PUT /pipe/cache?n=20. Cada worker lo recoge desde GET /pipe/cache?n=20. El pipe sostiene los bytes solo mientras están en vuelo — nunca en disco.

PUT · PRODUCTOREJECUTAR UNA VEZ POR BUILD

Produce la caché

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

tar empaqueta node_modules; zstd comprime al vuelo; curl hace PUT de los bytes directo a la ruta del pipe. Sin tempfile, sin paso upload-artifact, sin credenciales de bucket.

GET · CONSUMIDOREJECUTAR EN CADA WORKER

Consume la caché

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

Cada worker de test hace GET a la misma URL, descomprime y desempaqueta en su directorio de trabajo. Los workers lentos aplican backpressure al productor pero no bloquean a los rápidos.

TODA LA SUPERFICIE DE COORDINACIÓN?n=20

Un parámetro de query. Productor y consumidor acuerdan el mismo n. El pipe sostiene el upload hasta que se conecten exactamente esos receptores, y entonces abre las compuertas.

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

Qué dejas de pagar

El cacheo de CI solía ser un impuesto: almacenamiento que sobrescribirás mañana, egress cada vez que un worker tira, tiempo de ingeniería en las rarezas de la acción de caché. El pipe borra los tres conceptos de la factura a la vez.

Sin bucket. Sin factura de egress.

No hay bucket S3 porque no hay almacenamiento. El pipe olvida los bytes en cuanto termina la transferencia, así que no hay nada que cobrar por GB-mes ni por GB-out. La caché deja de ser un concepto en la factura.

Sin rarezas de la acción de caché

Sin claves codificadas en yaml, sin separación save-cache / restore-cache, sin depurar por qué un cache hit no ocurrió en el runner correcto. Solo curl. Las mismas dos líneas corren en GitHub Actions, BuildKite, Jenkins, tu portátil o un contenedor cron.

Sin colisiones de claves entre ramas

Las ramas simplemente usan rutas distintas. /pipe/cache/main, /pipe/cache/feat-x, /pipe/cache/PR-742. Nada que invalidar. Nada que evictar. Cuando la rama muere, su ruta deja de pedirse y ese es todo el ciclo de vida.

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

La forma del coste, antes y después

Sobre una carga real — node_modules de unos 800 MB, veinte workers de test en paralelo, cien runs de CI al día — la mayor parte de la factura es egress, no almacenamiento.

S3 + CACHÉ DE GH ACTIONS

20× egress

Cada uno de los veinte workers tira de la caché desde S3. Veinte descargas de un tarball de 800 MB son 16 GB de egress por run de CI. El bucket es la parte fácil — el egress es lo que se acumula.

VS
HOODY PIPE · ?n=20

1× transferencia

El productor transmite los 800 MB una sola vez. El pipe distribuye los bytes a los veinte receptores en vuelo. Una transferencia por el cable, sin multiplicador por receptor, sin factura de almacenamiento.

Las cifras son ilustrativas para una caché típica de monorepo Node. El ahorro real depende del tamaño del tarball, el fan-out de workers y el precio de egress que tu proveedor cobra fuera de la región de la caché. La forma — lineal vs constante en número de workers — es invariante.

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

La capa de caché es HTTP. Siempre lo fue. Solo no nos habíamos dado cuenta.

Las cachés nunca trataron de almacenamiento. Trataban de llevar los mismos bytes a N workers sin reconstruir. HTTP ya hace eso — en cuanto dejas que una URL se distribuya a un número conocido de receptores. El bucket era un parche para el fan-out que no teníamos.

BYTES

Transmite una vez, distribuye

Un PUT, n GETs, bytes idénticos. El backpressure es por receptor para que el worker lento no ralentice a los rápidos.

PUT /pipe/cache?n=20
TIEMPO

Vivo solo en vuelo

El pipe sostiene los bytes durante la transferencia y los olvida cuando acaba. No hay nada que evictar, nada que ciclar, nada de lo que hacer backup.

TTL ≤ 5 min · luego evictado
RUTA

Las ramas son rutas

Cada rama elige su propia ruta. Sin keyspace compartido, sin colisiones. La ruta es la clave de caché y la URL a la vez.

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

Qué reemplaza esto

La mayoría de cachés de CI resuelven el mismo problema: llevar el mismo tarball a N workers. Lo hacen mediante almacenamiento y egress. El pipe lo hace mediante el cable.

  • AWS S3 (bucket de caché + egress)Almacenamiento que sobrescribirás mañana + egress por descarga
  • Caché de GitHub ActionsClaves yaml, rarezas por runner, techo de 10 GB
  • Cachés de BuildJet / Garnix CIMás rápidas, aún por proveedor, aún por byte
  • Caché remota de BazelExcelente si vas todo-Bazel; pesada si no
  • Caché remota de TurborepoHospedada por Vercel, con forma de monorepo, opinada
  • Caché de satélite EarthlyOtro daemon, otro bucket, otra factura
  • Cachés rsync a medidaUna caja NFS y una clave SSH que todos olvidan rotar
use-cases / a-ci-cache-thats-just-two-curl-commands / cta

Dos comandos curl. Una URL. Veinte workers alimentados.

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

Lee los otros