top of page

Aufgeräumt: wie wir GitHubs Cache endlich richtig benutzen 🧹

Autorenbild: Marcel Dütscher
Marcel Dütscher
27. Juli
4 Min. Lesezeit

Im letzten Technik-Artikel hatten wir nachgemessen, was unsere Bau- und Prüfstrecke wirklich kostet — und festgestellt: kein Geld, sondern Wartezeit und Speicherplatz. Der Artikel endete mit „die Aufräumarbeiten sind geplant, aber noch nicht umgesetzt". Das ging dann doch schneller als gedacht: Seit heute Mittag sind sie auf dem Hauptzweig. Hier sind die Zahlen.


Das Ergebnis zuerst. Der Cache-Verbrauch unseres Repositories ist von 5,32 GB in 63 Einträgen auf 2,46 GB in 13 Einträgen gefallen. Die C#-Sicherheitsanalyse, die bisher bei jedem Pull Request mitlief, braucht dort jetzt 4 Sekunden statt 4½ Minuten — und prüft trotzdem weiterhin alles, was ausgeliefert wird. Beides ohne eine einzige Prüfung wegzulassen, die euch schützt.


Kurz erklärt: wozu ein Cache? Jeder Build startet auf einem frisch aufgesetzten, leeren Rechner. Damit er nicht jedes Mal bei null anfängt, darf er Zwischenergebnisse ablegen — beim nächsten Mal lädt er sie herunter statt sie neu zu erzeugen. Dafür gibt es 10 GB pro Repository. Läuft das voll, wirft GitHub das am längsten Unbenutzte raus. Und genau da lag unser Problem: Der Platz war belegt, nur eben vom Falschen.


Fehler 1: ein Schlüssel ohne Plattform. Unity legt beim Bauen einen riesigen Zwischenordner an. Vier Builds greifen darauf zu — Windows, Linux, macOS, WebGL — und jeder braucht seinen eigenen. Der Windows-Build war der einzige, dessen Cache-Schlüssel keine Plattform-Kennung enthielt. Er passte damit auf den erstbesten Eintrag, und das war der von WebGL: 1,7 GB herunterladen, die Unity dann korrekt als „gehört nicht hierher" erkennt und komplett neu importiert. Im Protokoll: 257 Sekunden Import statt 85. Der Schlüssel heißt jetzt Library-Windows-, und der Spuk ist vorbei.


Fehler 2: gespeichert hat nie jemand. Der peinlichere Fund. Alle vier Builds versuchten brav, den Zwischenordner wiederherzustellen — aber kein einziger Arbeitsablauf hat ihn je geschrieben. Der eine Eintrag, der überhaupt existierte, stammte aus einem Werkzeug, das man von Hand startet. Der wichtigste Cache des Projekts wurde also aus Versehen gepflegt. 🙈 Jetzt wird er absichtlich geschrieben.


Die Entscheidung, die Platz spart: nur der kritische Pfad zählt. Naheliegend wäre gewesen, jetzt alle vier Plattformen zu cachen. Ein Blick auf die Zeiten eines echten Releases zeigt, warum das Unsinn wäre: Alle vier Builds starten gleichzeitig, Windows ist nach 11 Minuten fertig, Linux und macOS nach 13,7 — und WebGL nach 31,6. Solange WebGL läuft, warten die anderen ohnehin. Nur dessen Cache verkürzt also die Wartezeit; die übrigen drei hätten rund 4,5 GB unseres 10-GB-Budgets gekostet und dafür null Minuten gespart. Also cachen wir genau einen.


Fehler 3: Docker hat den Rest aufgefressen. Beim Bauen unserer Server-Images wurde bisher jede Zwischenschicht als Cache abgelegt — rund 3,27 GB in 24 Einträgen, also 64 % des gesamten Budgets, und das bei jedem Push neu. Diese Flut verdrängte zuverlässig genau den Unity-Cache, auf den es ankommt. Jetzt wird nur noch die Endstufe abgelegt, und jedes unserer vier Images bekommt einen eigenen Namensraum — vorher haben sie sich einen geteilt und sich gegenseitig hinausgeworfen. Aus 3,27 GB sind rund 150 MB geworden.


Sicherheitsanalyse: prüfen, wo es zählt. CodeQL, GitHubs Werkzeug zur Suche nach Sicherheitslücken, war unser zweitgrößter Zeitfresser: 230 Minuten in acht Tagen, fast alles davon der C#-Teil, der für die Analyse das komplette Projekt neu baut — bei jedem Pull Request. Das läuft jetzt beim Zusammenführen in den Hauptzweig und einmal pro Woche vollständig; im Pull Request bleiben die schnellen Prüfungen. Gemessen nach der Umstellung: 4 Sekunden im Pull Request, unverändert gut 4 Minuten auf dem Hauptzweig. Es wird also nach wie vor jede Zeile geprüft, die tatsächlich bei euch landet — nur nicht mehr dreimal.


Kleinkram mit Wirkung. Die .NET-Pakete werden jetzt zwischengespeichert, aber nur auf dem Weg, auf den ein Mensch wartet. Ein Emulator für fremde Prozessor-Architekturen wird nur noch eingerichtet, wenn wir wirklich für eine bauen. Ein Aufräumbefehl, der auf einem frisch gestarteten Rechner nichts zu tun hatte, aber pro Build bis zu 1,4 Minuten kostete, ist gestrichen. Und die Zwischen-Dateien eines Release-Builds werden nach 7 statt nach 90 Tagen weggeräumt — es hatten sich 703 Stück mit 7,4 GB angesammelt.


Was wir bewusst nicht gemacht haben. Zwei Vorschläge aus unserer eigenen Analyse haben wir nach dem Messen verworfen, statt sie als hübsche Zeilen im Änderungsprotokoll zu verkaufen. Die Tests auf zwei parallele Jobs aufzuteilen hätte exakt vier Sekunden gebracht (der eine Teil dauert 2:54, der andere 4 Sekunden) — und hätte den Namen einer Pflichtprüfung geändert, was jeden Pull Request auf ewig „wartend" stehen ließe. Und das große Test-Tor vor einem Release zu überspringen hätte ebenfalls nichts gespart, weil es längst fertig ist, während WebGL noch baut. Eine Optimierung, die nichts bringt, aber ein Sicherheitsnetz entfernt, ist keine Optimierung.


Und dann fiel noch ein echter Bug heraus. Mitten in diesen Aufräumarbeiten scheiterte plötzlich jeder Pull Request an einer Zeitgrenze: Ein einzelner Test brauchte 212 Sekunden, erlaubt sind 120. Der zweitlangsamste im selben Durchlauf: 10,5 Sekunden. Die Ursache war ein Fehlerpfad in unserem Server, der eine kaputte HTTP-Anfrage einfach hart abgeschnitten hat, statt sie sauber zu beenden. Unter Linux räumt das System solche Verbindungen erst in einem Aufräumlauf weg, der alle zwei Minuten kommt — und das Herunterfahren wartete geduldig darauf. Unter Windows fiel das nie auf, deshalb hat es nur auf dem Build-Server zugeschlagen. Jetzt antwortet der Server in diesem Fall mit einem ehrlichen Fehlercode und schließt die Verbindung selbst: Der Test läuft in gut 20 Sekunden durch — und echte Spieler bekommen bei einem solchen Aussetzer künftig eine verständliche Fehlermeldung statt eines abgebrochenen Verbindungsversuchs. Ein CI-Aufräumtag, der einen Produktionsfehler findet, ist ein guter Tag. 🐛


Für euch ändert sich durch all das direkt nichts — kein neuer Block, kein neues Tier. Es verkürzt schlicht den Weg zwischen „fertig programmiert" und „bei euch installierbar". Und es beweist noch einmal denselben Satz wie beim letzten Mal: erst messen, dann anfassen. 🚀

Aktuelle Beiträge

Alle ansehen

Kommentare


bottom of page