Ein Workflow, der bei jedem Push npm ci ausführt, lädt jedes Mal dieselben paar Hundert Pakete aus der Registry herunter, selbst wenn sich die Lockfile seit einer Woche nicht geändert hat. Bei einem Projekt mit einem echten Abhängigkeitsbaum sind das ein bis zwei Minuten, bevor der erste Test überhaupt startet, und Sie zahlen das bei jedem Push und jeder Pull Request erneut. actions/cache behält diese Downloads zwischen den Ausführungen und gibt sie zurück, wenn sich nichts Relevantes geändert hat.
Wie die cache-Action Hit oder Miss entscheidet
actions/cache speichert jeden Eintrag unter einer von Ihnen gewählten Zeichenkette, dem key, begrenzt auf den Branch, der ihn erstellt hat. Ein Lauf kann Caches vom eigenen Branch und vom Default-Branch lesen. Beim Wiederherstellen vergleicht die Action Ihren key mit den gespeicherten Keys:
- Eine exakte Übereinstimmung mit
keyist ein Hit. Die Dateien werden heruntergeladen und der Job läuft weiter. - Ohne exakte Übereinstimmung greift
restore-keys: Die Einträge werden der Reihe nach geprüft, jeweils als Prefix-Match gegen bestehende Keys. Die zuletzt erstellte Übereinstimmung gewinnt. - Ohne jede Übereinstimmung stellt der Schritt nichts wieder her. Der Job startet kalt und speichert bei Erfolg einen neuen Eintrag unter
key.
Deshalb hasht das Standardmuster die Lockfile in den Key:
| |
Ändern Sie eine Zeile in package-lock.json, ändert sich der Hash, sodass der exakte Key absichtlich verfehlt wird. Sie wollen einen Cache, der zur neuen Lockfile passt, nicht zu der von letzter Woche. Der Prefix von restore-keys passt aber weiterhin, sodass der Job vom vorherigen Cache statt von einem leeren Verzeichnis startet, nur das Neue herunterlädt und bei Erfolg einen neuen exakten Eintrag speichert.
Zwei Grenzen gelten. Ein Repository hat 10 GB Cache-Speicher, den sich alle seine Workflows teilen, und jeder seit 7 Tagen ungenutzte Eintrag wird entfernt. Aufräumen müssen Sie nicht von Hand: Überschreitet das Repository das Limit, werden die ältesten Einträge gelöscht, und der nächste Lauf, der sie gebraucht hätte, zahlt einmalig die Kosten der kalten Installation.
npm mit der cache-Option von setup-node cachen
Für die gängigen Paketmanager schreiben Sie überhaupt keinen actions/cache-Schritt. actions/setup-node, actions/setup-python und actions/setup-java nehmen jeweils einen cache-Input entgegen, der Key, Wiederherstellung und Speichern für Sie übernimmt:
| |
cache: 'npm' findet package-lock.json (oder npm-shrinkwrap.json) im Repository-Root, hasht sie und cacht den Download-Cache von npm. cache: 'yarn' macht dasselbe mit yarn.lock. In einem Monorepo, in dem die Lockfile unter packages/api/package-lock.json liegt, verweisen Sie explizit darauf:
| |
setup-python funktioniert genauso: cache: 'pip' (oder 'pipenv', 'poetry'), standardmäßig gehasht über requirements.txt. setup-java nimmt cache: 'maven' oder cache: 'gradle' entgegen. Fangen Sie hier an. Wechseln Sie nur dann zu einem manuellen actions/cache-Schritt, wenn Sie einen Key oder Pfad brauchen, den die Setup-Action nicht liefert, oder wenn Sie etwas cachen, das nichts mit einer Sprach-Runtime zu tun hat.
Maven und pip direkt mit actions/cache cachen
Ein manueller Schritt hat immer dieselben drei Teile: das Verzeichnis, in das das Tool seinen Cache schreibt, einen Key, der sich aus der Datei ergibt, die die Abhängigkeitsversionen festlegt, und einen Prefix-Fallback.
| |
| |
path ist der Cache des Tools, nicht Ihres Projekts: der Download-Cache von pip statt Ihres virtualenv, das lokale Repository von Maven statt target/. Sie können stattdessen auch Build-Output wie target/ oder dist/ cachen, aber das lohnt sich nur bei einem inkrementellen Build. Sonst komprimieren und laden Sie Dateien hoch, die der nächste Lauf wegwirft.
Wenn Sie bereits Docker-Layer mit cache-to: type=gha cachen, wie in Docker-Image mit GitHub Actions bauen und veröffentlichen, landen diese Layer im selben Actions-Cache-Dienst. Sie fallen unter dasselbe 10-GB-Limit und dieselbe 7-Tage-Eviction wie Ihre Abhängigkeits-Caches. Ein Repository, das beides tut, erreicht das Limit früher als erwartet, und große Layer-Caches können Ihren npm-Cache verdrängen.
Cache-Keys in Matrix-Builds
Eine Matrix über Node-Versionen oder Betriebssysteme braucht den Matrix-Wert im Key, sonst konkurrieren alle drei Matrix-Jobs um einen einzigen Eintrag:
| |
Cache-Einträge lassen sich nicht überschreiben. Entfernen Sie matrix.node-version aus dem Key, versuchen alle drei Matrix-Jobs, Linux-npm-<hash> zu speichern: Der zuerst fertige gewinnt, die anderen protokollieren eine Warnung, dass das Speichern fehlgeschlagen ist. Für ~/.npm ist das größtenteils harmlos, da die heruntergeladenen Tarballs nicht von der Node-Version abhängen. Harmlos hört es auf zu sein, wenn der gecachte Pfad kompilierte Ausgabe enthält, wie node_modules mit einem nativen Addon. Dann stellt der Matrix-Job für Node 24 Binärdateien wieder her, die für Node 20 gebaut wurden, und Sie bekommen Fehler, die nach allem Möglichen aussehen, nur nicht nach einem Cache-Problem. runner.os im Key erledigt dieselbe Aufgabe für Matrix-Jobs mit unterschiedlichen Betriebssystemen.
Was Sie in GitHub Actions nicht cachen sollten
Behandeln Sie einen Cache-Eintrag wie einen Image-Layer: Wer einen Workflow ausführen kann, der ihn wiederherstellt, kann ihn lesen. Halten Sie Tokens und .env-Dateien aus jedem gecachten Verzeichnis heraus. Es gilt dieselbe Regel wie in Umgebungsvariablen und Secrets in Docker: Secrets kommen zur Laufzeit aus secrets:, nie aus etwas, das auf die Platte geschrieben und wiederverwendet wird.
Zwei Fälle, in denen Cachen mehr kostet, als es spart:
- Alles, was sich in wenigen Sekunden installiert. Eine Handvoll reiner Python-Pakete ohne kompilierte Erweiterungen installiert sich etwa so schnell, wie es sich wiederherstellen lässt. Der Cache-Schritt hat seinen eigenen Overhead für Komprimieren und Übertragen des Archivs, und der kann eine so kleine Ersparnis auffressen.
- Ein Key, der zu breit ist, um je zu treffen. Hashen Sie statt der Lockfile das gesamte Verzeichnis
src/, macht jede Codeänderung den Cache ungültig. Bauen Sie den Key auf der Datei auf, die die Abhängigkeitsversionen festlegt, und auf nichts sonst.
Wie Sie erkennen, ob der Cache trifft
Der actions/cache-Schritt protokolliert bei einem Hit Cache restored from key: ... und bei einem Miss Cache not found for input keys: .... Lesen Sie diese Zeile, bevor Sie dem Setup vertrauen.
Um es zu messen, fügen Sie cache: 'npm' oder den manuellen Schritt zu einem Workflow hinzu, pushen zweimal und vergleichen, wie lange der Schritt npm ci beim kalten und beim warmen Lauf braucht. Diese Differenz zeigt, was die Änderung tatsächlich bringt. Soll ein Job den Cache aufbauen und andere Jobs ihn nur lesen, ersetzen Sie actions/cache durch actions/cache/restore@v6 und actions/cache/save@v6, die die beiden Hälften trennen, sodass Sie entscheiden, wo welche läuft.