Vuncloud Blog
← Zurück zu Dev Notes

Xcode-Cache-Sharing in der Praxis: Build-Daten über mehrere Knoten synchronisieren

DerivedData · SPM · CocoaPods · Multi-Runner-Syncca. 13 Min. Lesezeit

Datenanalyse-Dashboard — symbolisiert Multi-Node-Xcode-Build-Cache-Monitoring und Sync-Effizienz
TL;DR · Drei Sätze
  • DerivedData-Cache auf einem Mac existiert auf einem zweiten Runner standardmäßig nicht — die versteckten Kosten von Multi-Node-iOS-CI sind, dass jede Maschine eigenständig kalt startet
  • Teilen lässt sich die Build-Artefakt-Schicht (DerivedData, SPM-Auflösung, Pods/) — nicht „mehrere Maschinen schreiben gleichzeitig in dasselbe Verzeichnis“; pull → build → push oder ein regionaler Cache-Hub ist 2026 der Mainstream
  • Auf einer Cloud-Mac-Flotte: Lockfile-Hash + Xcode-Version als Cache-Keys mit rsync/Object Storage — warme Builds sparen oft weitere 5–15 Minuten (zusätzlich zur Einzelmaschinen-Optimierung)

Sie haben GitHub Actions actions/cache konfiguriert und warme Builds sind von 18 auf 9 Minuten gesunken — dann skalieren Sie auf drei selbst gehostete M4-Runner und P95 kriecht wieder auf 16 Minuten. Jede Maschine „sieht diesen Commit zum ersten Mal“.

Das ist kein Rückschritt auf null, sondern Phase zwei von Multi-Node-Builds: Xcode-Cache-Sharing. Dieser Artikel behandelt nicht „warum Cache“ (siehe den CocoaPods-/SPM-/DerivedData-Einzelmaschinen-Leitfaden), sondern wie Build-Daten zwischen mehreren Macs synchronisiert werden, damit jeder Runner Module wiederverwenden kann, die ein Teamkollege bereits kompiliert hat.

3
Cache-Schichten: Abhängigkeiten · Build-Artefakte · Indizes
5–15 Min
typische Extra-Ersparnis durch Multi-Node-Warm-Builds
1
Eisernes Gesetz: niemals gleichzeitig in dieselbe DerivedData schreiben

1. Warum Multi-Node-CI langsamer ist als Einzelmaschine

Wenn Load Balancing Jobs zufällig Runner A/B/C zuweist, ist der Festplattenzustand jeder Maschine unabhängig. MyAppKit.swiftmodule, das Runner A gerade kompiliert hat, nützt Runner B nichts — es sei denn, Sie übertragen die Artefakte.

GitHub-gehostete Runner nutzen actions/cache, um Tarballs in der Cloud zu speichern und beim Jobstart wiederherzustellen. Selbst gehostete oder Cloud-Mac-Flotten haben oft keine vergleichbare verwaltete Schicht, daher wählen Teams entweder:

  • Pro-Maschinen-Aufwärmung zu akzeptieren (verschwendete Rechenzeit)
  • Cache-Verzeichnisse per NFS mit gleichzeitigem Schreiben zu mounten (Korruptionsrisiko)
  • Einen Cache-Hub aufzubauen: pull vor dem Build, push nach Erfolg

Die dritte Option ist bei iOS-Teams am verbreitetsten und passt zu Multi-Region-Node-Deployments: drei M4 in US East teilen einen Cache-Bucket, zwei in Asien-Pazifik einen anderen, und nur die SPM-/Pods-Schicht wird regionsübergreifend synchronisiert.

2. Schichten der Xcode-Build-Daten

Vor dem Sync trennen Sie, was sich zu übertragen lohnt, von dem, was nicht gemischt werden darf.

2.1 DerivedData

Das Verzeichnis, auf das xcodebuild -derivedDataPath zeigt, mit kompilierten .o, .swiftmodule, Link-Zwischenprodukten und Teilindizes. Das Volumen erreicht oft mehrere GB — es ist der größte Hebel für Warm-Build-Geschwindigkeit und die Schicht, die am empfindlichsten auf die Xcode-Version reagiert.

Festen Pfad in CI setzen, z. B. /Volumes/CI/DerivedData/<scheme>, passend zur Variable REMOTE im Sync-Skript — der Einzelmaschinen-Cache-Artikel betonte, dass abweichende Pfade keinen Cache bedeuten.

2.2 SPM und CocoaPods

SPM: ~/Library/Caches/org.swift.swiftpm + im Repo .build (falls Sie Auflösungsoutput committen). CocoaPods: Pods/ und ~/Library/Caches/CocoaPods.

Diese Schichten ändern sich seltener als DerivedData (sie folgen dem Lockfile) und eignen sich für maschinen- und sogar regionsübergreifenden Sync. Viele Teams pushen nur SPM/Pods nach S3 und halten DerivedData regional per rsync.

2.3 ModuleCache und globale Xcode-Caches

~/Library/Developer/Xcode/DerivedData/ModuleCache.noindex und diverse SDKStatCaches. Werden meist zusammen mit DerivedData behandelt; bei separatem Sync gilt dieselbe Xcode-Minor-Version.

Code-Editor auf einem MacBook — Symbol für mehrere Runner, die denselben Xcode-Compile-Cache teilen
Sie teilen kompilierte Module — kein offenes Projekt auf jeder Maschine. Jeder Runner baut weiterhin aus einer lokalen DerivedData-Kopie.

3. Vier Multi-Node-Sync-Architekturen

MusterAnsatzVorteileRisiken
Nur lokalJeder Runner hat unabhängige Platte, kein SharingNull OpsHorizontale Skalierung spart keine Zeit
NFS-Shared-MountDerivedData auf Netzwerkvolume, read-only oder read-writeEinfaches SetupGleichzeitiges Schreiben korrumpiert leicht; Netzwerklatenz bremst Linking
rsync-HubZentrales Verzeichnis oder Leader-Maschine; pull vor Build, push nach ErfolgKontrollierbar, gute Xcode-KompatibilitätSkripte und Locking nötig
Object-Storage-TarballNach Cache-Key packen und nach S3/R2 hochladen; beim Jobstart entpackenRegionsübergreifend, ähnlich GHA-Cache-ModellGroße DerivedData-Uploads sind langsam; Kompression nötig

In der Praxis 2026 bevorzugen Cloud-Mac-Flotten im selben Rechenzentrum / derselben Region rsync-Hubs; globale Teams nutzen Object Storage für SPM/Pods und halten DerivedData regional. Behandeln Sie NFS nicht als Allheilmittel für Multi-Writer-Shared-DerivedData.

Eisernes Gesetz

Niemals xcodebuild auf zwei Runnern gleichzeitig gegen denselben DerivedData-Baum laufen lassen. Wenn Sie ein Volume teilen müssen: Dateisperren oder Single-Writer-Multi-Reader (ein designierter Warmer baut, dann verteilt rsync).

4. Cache-Keys und Invalidierung

Multi-Node-Shared-Cache-Keys sollten konservativer sein als auf Einzelmaschinen — ein falscher Treffer ist schlimmer als ein Kaltstart (rätselhafte Link-Fehler, Signing-Ausfälle).

Felder im Key:

  • xcodebuild -version oder Umgebungsvariable XCODE_VERSION
  • hashFiles('**/Podfile.lock') oder Package.resolved
  • Scheme-Name oder Target-Set (getrennte Caches für Multi-Scheme-Repos)
  • arm64 (nur Apple Silicon — vermeidet Legacy-x86-Verschmutzung)

Nicht nur den vollen github.sha als Key — sonst verfehlt jede Maschine immer. Geschichtete restore-keys: dd-arm64-main-<lockhash>dd-arm64-main-dd-arm64-.

Invalidieren bei: Xcode-Upgrade, Lockfile-Änderung, SWIFT_VERSION oder größere Build-Settings-Änderung, oder nach manuellem clean build — Key-Suffix erhöhen oder Remote-Verzeichnis löschen.

5. Praxis: rsync pull/push-Skript

Unten ein minimales Skript, validiert auf einer M4-Cloud-Mac-Flotte. Der Cache-Hub liegt unter cache-leader.internal:/cache/ios/ (Maschine mit großer Platte in der Flotte oder NAS).

#!/usr/bin/env bash
# cache-sync.sh — 在 xcodebuild 前后调用
set -euo pipefail

ACTION="${1:?pull|push}"
CACHE_ROOT="${CACHE_ROOT:-cache-leader.internal:/cache/ios}"
SCHEME="${SCHEME:-MyApp}"
KEY="${CACHE_KEY:-arm64-$(md5 -q Podfile.lock 2>/dev/null || echo nolock)-$(xcodebuild -version | head -1 | tr ' ' '-')}"
LOCAL_DD="${DERIVED_DATA_PATH:-/Volumes/CI/DerivedData/${SCHEME}}"
REMOTE="${CACHE_ROOT}/${KEY}/${SCHEME}/DerivedData"
LOCAL_SPM="${HOME}/Library/Caches/org.swift.swiftpm"
REMOTE_SPM="${CACHE_ROOT}/${KEY}/swiftpm"

rsync_opts=(-az --delete-delay --contimeout=10)

case "$ACTION" in
  pull)
    rsync "${rsync_opts[@]}" "${REMOTE}/" "${LOCAL_DD}/" || true
    rsync "${rsync_opts[@]}" "${REMOTE_SPM}/" "${LOCAL_SPM}/" || true
    ;;
  push)
    # 仅成功构建后调用
    rsync "${rsync_opts[@]}" "${LOCAL_DD}/" "${REMOTE}/"
    rsync "${rsync_opts[@]}" "${LOCAL_SPM}/" "${REMOTE_SPM}/"
    ;;
  *) echo "usage: $0 pull|push" >&2; exit 1 ;;
esac

CI-Ablauf:

  1. Jobstart → cache-sync.sh pull
  2. xcodebuild -scheme "$SCHEME" -derivedDataPath "$LOCAL_DD" build
  3. Nur bei erfolgreichem Build → cache-sync.sh push

Bei parallelen Maschinen kann push kollidieren — flock nutzen oder last-write-wins akzeptieren; DerivedData unter gleichem Lockfile + Xcode sollte kompatibel sein. Bei sehr häufigem Push auf asynchronen Upload wechseln (rsync im Hintergrund nach dem Build, ohne Pipeline zu blockieren).

6. Regionale Cloud-Mac-Cache-Hubs

Wenn Runner in US East, US West und Asien-Pazifik stehen, lohnt sich oceansübergreifendes rsync voller DerivedData oft nicht (Latenz + Egress). Empfohlener Ansatz:

  • Ein Cache-Bucket pro Region (S3 / R2 / Provider-Object-Storage)
  • Sync-Inhalt: Pods/-Tarball nach Podfile.lock-Hash + SPM-Cache nach Package.resolved
  • DerivedData nur innerhalb der Region per rsync; erster Job kalt, folgende warm
  • Image-Versionen mit Golden-Image-Tags abgleichen, um Xcode-Drift zu vermeiden, die die ganze Bibliothek invalidiert

SPM-Cache vor Upload mit tar czf komprimieren — oft 40 %–60 % kleiner; DerivedData besteht bereits aus vielen kleinen Dateien, daher ist tar + paralleler Upload stabiler als rohes regionsübergreifendes rsync.

7. Anbindung an GitHub Actions / Self-hosted Runner

Auf Self-hosted Runnern den Workflow so wrappen:

- name: Restore shared Xcode cache
  run: ./scripts/cache-sync.sh pull
  env:
    CACHE_ROOT: ${{ secrets.IOS_CACHE_SSH }}
    SCHEME: MyApp
    DERIVED_DATA_PATH: /Volumes/CI/DerivedData/MyApp

- name: Build
  run: xcodebuild -scheme MyApp -derivedDataPath /Volumes/CI/DerivedData/MyApp build

- name: Save shared Xcode cache
  if: success()
  run: ./scripts/cache-sync.sh push

Nutzen Sie weiterhin GitHub-gehostetes actions/cache, können Self-hosted-Flotten dual track fahren: GHA-Cache als Fallback, rsync-Hub als latenzarme Same-Region-Schicht. Beide Key-Namespaces getrennt halten, um Überschreiben zu vermeiden.

Monitoring: pull/push-Dauer, übertragene Bytes und ob der Build inkrementell war (CompileSwift-Zeilen in xcodebuild-Logs zählen). Wenn P95 nachlässt, zuerst Cache-Miss prüfen, dann Maschinenlast — dasselbe Playbook wie beim Troubleshooting schwankender Build-Zeiten.

8. Fallstrick-Checkliste

  • Gleichzeitiges DerivedData-Schreiben: zufällige stat cache file corrupted → auf pull/push wechseln
  • Abweichende Xcode-Minor-Versionen: Modul-ABI-Mismatch → Key enthält Version, Images teilen einheitlichen Tag
  • Pfad-Mismatch: Cache unter Pfad A, Kompilierung unter Pfad B → Voll-Rebuild
  • Push nach clean: leere Verzeichnisse überschreiben guten Cache → push nur bei Erfolg und nicht nach clean
  • Signierte Produkte im Cache: veraltetes Signing nach Provisioning-Änderungen → Build/Products vom Cache ausschließen oder Keys nach Konfiguration trennen
  • Platte voll: DerivedData-Aufblähung → Remote-${KEY}-Verzeichnisse periodisch per LRU bereinigen, letzte N Lockfile-Hashes behalten

9. FAQ

Können mehrere Macs dasselbe DerivedData-Verzeichnis direkt teilen?

Nicht mit gleichzeitigem Schreiben. Read-only-Mounts oder Single-Writer/Multi-Reader können funktionieren, aber Linking kann trotzdem sperren. Produktions-Setups bevorzugen lokales DerivedData plus rsync.

Braucht Xcode-Cache-Sharing dieselbe Xcode-Version?

Ja. Nach Xcode-Upgrade neuen Cache-Key nutzen; alte Verzeichnisse asynchron löschen.

Vergleich mit Bazel / Tuist Remote Cache?

Bazel/Tuist Remote Cache ist feiner (Action-Ebene) und passt zu riesigen Monorepos. Standard-Xcode-Projekte haben geringere Migrationskosten durch DerivedData-Sync und bleiben mit bestehenden xcodebuild-Flows kompatibel.

Wie viel Speicher brauchen mehrere Cloud Macs?

Lokal mindestens 50–100 GB pro Maschine für DerivedData + SPM; Hub oder Object Storage nach parallelen Schemes × Cache-Größe pro Scheme × behaltenen Versionen dimensionieren. M4 + 2 TB Erweiterung reicht meist für mittlere Teams.

Fazit

Xcode-Cache-Sharing adressiert den „zweiten Kaltstart“ der Multi-Runner-Ära: Nach Einzelmaschinen-Cache-Optimierung sollte horizontale Skalierung nicht wieder die volle Compile-Steuer zahlen. Drei getrennte Schichten (Pods / SPM / DerivedData), eine Sync-Disziplin (pull-build-push) und ein eisernes Gesetz (niemals gleichzeitig in DerivedData schreiben).

Cache ist der günstigste Compute-Multiplikator in verteilten Builds — schnellerer ROI als zwei weitere M4 zu kaufen.

Starten Sie mit einem Cache-Leader und cache-sync.sh; benennen Sie Remote-Verzeichnisse nach Lockfile-Hash; pushen Sie nur bei grünem Build. Die P95-Kurve nächste Woche zeigt, ob es sich gelohnt hat.

Cloud Mac mit Speicher für Multi-Node-CI

Vuncloud Mac mini M4: US East, US West und Asien-Pazifik per SSH — ideal für große DerivedData-Volumes, rsync-Cache-Hubs und Self-hosted-Runner-Flotten.

Cloud-Mac-Tarife ansehen · M4-CI-Runner-Kostenmodell

Das Verhalten der Apple-Toolchain folgt offiziellen Releases; prüfen Sie SSH- und Object-Storage-Credentials gemäß der Sicherheitsrichtlinie Ihres Teams. Zuletzt aktualisiert: 27. Juli 2026.

Dev Notes · Build-Beschleunigung

DerivedData-Sync · Multi-Runner · Cloud Mac

rsync-Hub · Cache-Key · regionale Cache-Buckets

Cloud-Mac-Tarife ansehen
Zeitlich begrenzt Tarife ansehen