Zum Inhalt springen
use-cases / a-ci-cache-thats-just-two-curl-commands / hero
PIPE · CI · COST OPTIMIZE

Ein CI-Cache, der nur aus zwei curl-Befehlen besteht

Der erste Job in deiner Pipeline tart node_modules und pipet sie in eine Hoody-URL. Zwanzig nachgelagerte Jobs holen dieselbe URL per curl und entpacken. Mit ?n=20 wartet der Producer, bis sich alle zwanzig Worker verbunden haben, und streamt dann einmal — gefannt out an alle. Kein S3-Bucket, keine Cache-Action, keine Egress-Rechnung.

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

Die ganze Mechanik in zwei Shell-Zeilen

Es gibt keine Client-Library, keinen Daemon, kein SDK. Der Producer streamt seinen Tarball in PUT /pipe/cache?n=20. Jeder Worker streamt ihn aus GET /pipe/cache?n=20 wieder raus. Die Pipe hält die Bytes nur, während sie in Flight sind — niemals auf der Disk.

PUT · PRODUCEREINMAL PRO BUILD AUSFÜHREN

Cache erzeugen

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

tar packt node_modules; zstd komprimiert on the fly; curl PUTtet die Bytes direkt in den Pipe-Pfad. Kein Tempfile, kein upload-artifact-Step, keine Bucket-Credentials.

GET · CONSUMERAUF JEDEM WORKER AUSFÜHREN

Cache konsumieren

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

Jeder Test-Worker GETtet dieselbe URL, dekomprimiert und entpackt in sein Working Directory. Langsame Worker üben Backpressure auf den Producer aus, blockieren aber nicht die schnelleren.

DIE GESAMTE COORDINATION-SURFACE?n=20

Ein Query-Parameter. Producer und Consumer einigen sich auf dasselbe n. Die Pipe hält den Upload, bis exakt so viele Receiver verbunden sind, und öffnet dann die Schleusen.

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

Wofür du nicht mehr zahlst

CI-Caching war früher eine Steuer: Storage, den du morgen überschreibst, Egress jedes Mal, wenn ein Worker zieht, Engineering-Zeit für die Macken der Cache-Action. Die Pipe streicht alle drei Posten auf einmal.

Kein Bucket. Keine Egress-Rechnung.

Es gibt keinen S3-Bucket, weil es keinen Storage gibt. Die Pipe vergisst die Bytes in der Sekunde, in der der Transfer endet — also gibt es nichts, wofür man pro GB-Monat oder pro GB-Out abrechnen könnte. Der Cache ist kein Kostenposten mehr.

Keine Cache-Action-Macken

Keine YAML-codierten Keys, kein save-cache/restore-cache-Split, kein Debugging, warum ein Cache-Hit nicht auf dem richtigen Runner passiert ist. Nur curl. Dieselben zwei Zeilen laufen auf GitHub Actions, BuildKite, Jenkins, deinem Laptop oder einem Cron-Container.

Keine Key-Kollisionen über Branches

Branches nutzen einfach unterschiedliche Pfade. /pipe/cache/main, /pipe/cache/feat-x, /pipe/cache/PR-742. Nichts zu invalidieren. Nichts zu evicten. Wenn der Branch stirbt, wird sein Pfad nicht mehr abgefragt — und das ist der gesamte Lifecycle.

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

Die Kostenform, vorher und nachher

Bei einem realen Workload — node_modules um die 800 MB, zwanzig parallele Test-Worker, hundert CI-Runs pro Tag — ist der Großteil der Rechnung Egress, nicht Storage.

S3 + GH ACTIONS CACHE

20× Egress

Jeder der zwanzig Worker zieht den Cache aus S3. Zwanzig Downloads eines 800-MB-Tarballs sind 16 GB Egress pro CI-Run. Der Bucket selbst ist der einfache Teil — der Egress ist es, was sich aufaddiert.

VS
HOODY PIPE · ?n=20

1× Transfer

Der Producer streamt die 800 MB einmal. Die Pipe fanntoutet die Bytes in Flight an alle zwanzig Receiver. Ein Transfer durch die Leitung, kein Per-Receiver-Multiplikator, keine Storage-Rechnung.

Zahlen sind illustrativ für einen typischen Node-Monorepo-Cache. Tatsächliche Einsparungen hängen von Tarball-Größe, Worker-Fan-out und dem Egress-Preis ab, den dein Provider aus der Cache-Region berechnet. Die Form — linear vs. konstant in der Worker-Anzahl — bleibt invariant.

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

Die Cache-Schicht ist HTTP. War sie immer. Wir haben es nur nicht gemerkt.

Caches waren nie Storage. Es ging darum, dieselben Bytes ohne Rebuild an N Worker zu kriegen. HTTP macht das längst — sobald du eine URL an eine bekannte Anzahl von Receivern fan-outten lässt. Der Bucket war ein Workaround für das Fan-out, das wir nicht hatten.

BYTES

Einmal streamen, fan-outten

Ein PUT, n GETs, identische Bytes. Backpressure ist pro Receiver, damit der langsame Worker die schnellen nicht ausbremst.

PUT /pipe/cache?n=20
TIME

Lebt nur in Flight

Die Pipe hält die Bytes für den Transfer und vergisst sie, wenn er endet. Nichts zu evicten, nichts zu lifecyclen, nichts zu backupen.

TTL ≤ 5 min · dann evicted
PATH

Branches sind Pfade

Jeder Branch wählt seinen eigenen Pfad. Kein gemeinsamer Keyspace, keine Kollisionen. Der Pfad ist der Cache-Key und die URL zugleich.

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

Was das ersetzt

Die meisten CI-Caches lösen dasselbe Problem: denselben Tarball an N Worker bringen. Sie tun es über Storage und Egress. Die Pipe tut es über die Leitung.

  • AWS S3 (Cache-Bucket + Egress)Storage, den du morgen überschreibst + Per-Pull-Egress
  • GitHub Actions CacheYAML-Keys, Runner-spezifische Macken, 10-GB-Limit
  • BuildJet / Garnix CI CachesSchneller, trotzdem pro Vendor, trotzdem pro Byte
  • Bazel Remote CacheHervorragend, wenn du voll auf Bazel setzt; schwer, wenn nicht
  • Turborepo Remote CacheVercel-gehostet, monorepo-geformt, opinionated
  • Earthly Satellite CacheNoch ein Daemon, noch ein Bucket, noch eine Rechnung
  • Eigene rsync-CachesEine NFS-Box und ein SSH-Key, den jeder vergisst zu rotieren
use-cases / a-ci-cache-thats-just-two-curl-commands / cta

Zwei curl-Befehle. Eine URL. Zwanzig Worker versorgt.

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

Lies die anderen