Sechzig Container auf einem Server
Eine Bare-Metal-Box betreibt Dutzende bis Hunderte Hoody-Container. KSM und BTRFS-Dedup drücken die Grenzkosten fast auf null.
Deine Matrix-CI fächert sich über dreißig Test-Runner auf. Jeder davon braucht dasselbe 800-MB-Image. Stream das Tarball mit ?n=30 in einen Pipe-Pfad. Alle dreißig Worker rufen dieselbe URL per curl ab. Die Bytes laufen einmal durch, der Server hält nichts, und keine Registry-Credentials werden rotiert.
# stream the image oncetar c ./image.tar | curl -T - https://pipe.hoody.com/build-12af?n=30EIN SENDER LINKS. DREISSIG GET-EMPFÄNGER RECHTS. DIE PIPE WARTET, BIS ALLE LAUSCHEN, DANN LAUFEN DIE BYTES EINMAL HINDURCH.
Die Pipe ist ein Fan-out-Router ohne Disk. Der POST des Senders auf /api/v1/pipe/[path]?n=30 blockiert, bis dreißig Empfänger sich mit demselben n auf derselben URL verbinden. Dann fließen die Bytes vom Build-Container direkt zu jedem Runner — gleichzeitig, in der Geschwindigkeit des langsamsten Empfängers.
tar c | curl -T - https://pipe/.../build?n=30Der Build-Container pipet das Tarball direkt in curl. Keine Datei geschrieben, keine Registry gepusht.
POST /api/v1/pipe/[path]?n=30Der Server hält den Sender, bis alle dreißig Empfänger verbinden. Abweichendes n liefert 400. Vorab verbundene Empfänger sind okay.
curl https://pipe/.../build?n=30 | tar xJeder Runner bekommt identische Bytes. Backpressure kommt vom langsamsten Empfänger, nicht von der Bandbreite des Senders.
Nichts persistiert. Nichts wird gecached. Die Pipe vermittelt die Verbindung und tritt dann zurück. Wenn der langsamste Runner fertig ist, ist der Transfer fertig — und die URL ist weg.
Naiv: dreißig Registry-Pulls desselben 800-MB-Tarballs, dreißig kalte Caches, dreißig Netzwerk-Round-Trips. Pipe: ein Egress, ein Transfer, der langsamste Empfänger gibt das Tempo vor.
12s
Ein Egress mit Linerate. Der langsamste Empfänger bestimmt das Tempo, aber niemand lädt erneut.
1× / Build
Bytes verlassen den Builder einmal, fächern sich an der Pipe auf. Keine S3-GET-Gebühren, keine Docker-Hub-Pulls.
0 Bytes
Die Pipe hält nichts auf Disk. Keine Registry zum Aufräumen, kein Cache-Key zum Invalidieren.
Die Wall-Time-Zahl geht von einer 30-fachen Matrix im selben regionalen Netz wie der Build-Container aus; Cross-Region-Transfers hängen an der Inter-Region-Bandbreite, nicht an der Pipe.
Sobald der Build eine URL und dreißig curls ist, fällt ein ganzer Stack an CI-Gerüst weg. Kein Artefakt-Storage zum Altern. Keine Registry-Credentials zum Rotieren. Keine Cache-Action zum Debuggen.
Backpressure ist in die Pipe eingebaut. Die schnellen Worker verschwenden keinen Registry-Round-Trip, um auf den langsamen zu warten — sie warten an der Pipe und trinken dann im selben Tempo. Niemand lädt erneut.
Nichts wird in eine Registry gepusht, also muss sich auch nichts daran authentifizieren. Die URL selbst ist das Credential — kurzlebig, auf einen Transfer beschränkt, evictet, wenn der Build fertig ist.
Bytes verlassen den Builder einmal. Die Pipe broadcastet. Du zahlst einen Egress pro Build statt dreißig Registry-Pulls pro Matrix-Run.
Die Pipe ist pro Build, nicht pro Key. Es gibt keinen GitHub-Actions-Cache, der danebenliegt, kein Buildx-Layer-Mysterium, kein abgestandenes Tarball vom letzten Main.
Dasselbe Muster handhabt node_modules, .pnpm-store, target/, den Wheel-Cache, den Dataset-Shard. Was streamt, fächert sich auf.
Ein Sender. Dreißig Empfänger. Null S3-Rechnungen.
Ein 30er-Push, der früher an einem S3-Roundtrip steckengeblieben ist, fließt jetzt durch eine Pipe und einen Single Egress. Niemand downloaded neu. Keine Registry-Credentials rotieren. Die URL verdampft, wenn die Matrix fertig ist.
Die Teile, die ein Matrix-CI-Flow normalerweise zusammensetzen muss — Registry, Cache-Action, Mirror, Custom-Upload-Schritt. Die Pipe faltet sie in eine URL.
Hör auf, dasselbe Tarball dreißig Mal zu pushen. Push es einmal. Lass dreißig curls den Stream teilen.