Vuncloud Blog
← Zurück zu den Feldnotizen

iOS CI langsam? Warum xcodebuild auf GitHub Actions schwankt

Feldnotizen · Gleicher PR: mal 6 Min., mal 18 · cold/warm zuerst trennen · 14-Tage-Shadow · warm P95 14:12→6:05 ·ca. 9 Min.

Mac-Bildschirm mit Xcode und Terminal bei Diagnose langsamer GitHub Actions iOS CI xcodebuild-Builds
TL;DR · Erst sehen, dann handeln
  • Von 187 PRs sind 86,6 % warm — SLA im Alltag auf warm fokussieren, cold nicht in P95 mischen
  • Dependency-Änderung, Cache-Löschung, Scheme-Wechsel → cold; 2–3× Wandzeit-Spread ist normal
  • Auch bei warm: Cache-Miss, parallele Jobs auf derselben Platte, Tests + Signing in einem Job → Varianz bleibt
  • Grobe Reihenfolge: Queue → cold/warm trennen → Cache → Concurrency → Hardware zuletzt (Wasserfall)

Vollständige Benchmark-Daten → Pillar · Benchmark

86,6%
Shadow-Samples: warm build
14:12→6:05
warm P95 (macos-latest → dediz. M4)
2–3×
typische cold vs warm Wandzeit

1. CI wie Lotterie: gleicher Commit, anderer Spread

Wer iOS CI langsam? Warum xcodebuild auf GitHub Actions schwankt sucht, kennt diese Bilder aus dem Alltag:

  • Gleicher Commit, Re-Run — Wandzeit 2–3× auseinander
  • Dashboard-P95 rot, Team-Gefühl: „Merge wartet eigentlich nicht so lang“
  • pod install mal 30 Sekunden, mal „hängt“
  • Freitagnachmittag kein Merge — Angst vor dem langsamen CI-Zug

Nicht immer fehlt CPU. In unserem 14-Tage-Shadow mit Teams war häufiger: Zahlen vermischt, Umgebung instabil — Cache hält nicht, Platte wird gekappt. Chip-Wechsel steht meist ganz hinten.

Diese Feldnotiz behandelt nur: warum Builds schwanken. Job wartet in der Queue? → Queue-Artikel. Cache-Setup, Kauf vs. Miete → Cache-Cluster und ROI.

2. cold und warm: nicht in einen Topf

Klassische Falle: alle Build-Zeiten in eine P95. Einzelne cold-Läufe ziehen den Schwanz — Daten sagen „täglich langsam“, Merge-Alltag sagt etwas anderes.

Definition · Shadow-Vergleich
  • warm: Dependencies unverändert, Cache da, gleiches Scheme — meist inkrementell; repräsentiert „normaler Merge“
  • cold: Lockfile geändert, Cache weg, Target/Scheme neu, erster Lauf auf Runner — resolve, pod install, Voll-Rebuild

SLA: warm P50/P95. cold eigene Linie — nicht mit Merge-Erlebnis verknüpfen.

2.1 Wann wird es cold?

Auf macos-latest öfter cold — Workspace nach Job-Ende weg, Cache „zieht nicht ein“:

Trigger typ. Zusatzzeit Log-Hinweise
Podfile.lock geändert +3–8 Min. pod install, Downloading dependencies
DerivedData miss / gelöscht +5–15 Min. CompileSwift, volle .o-Neuerstellung
Scheme / Target gewechselt +2–10 Min. anderes xcodebuild -scheme
SPM-Resolve geändert +1–5 Min. Resolve Package Graph
Xcode-Minor-Upgrade (macos-latest) erster Build +10–20 Min. neues SDK / Modul-Cache-Rebuild

Viele Pod-Upgrades → mehr cold. Kein Hardware-Verfall, sondern andere Wochenarbeit. „Pod-Woche“ und „Dev-Woche“ getrennt reporten.

2.2 Warum warm trotzdem schwankt

Auch nur warm: Wandzeit kann ~30 % schwanken. Typisch:

  • Cache-Miss: Key ohne arm64, Branch nicht gebunden, Jobs teilen Slot
  • Viele macOS-Jobs in einer Org → Platte und Netz bremsen sich
  • Mal nur main bauen, mal volle Unit+UI-Tests — andere Arbeit
  • Änderungsfläche: Pod-Quellcode vs. eine SwiftUI-Zeile — anderer Compile-Umfang
Entwickler analysiert GitHub Actions iOS-CI-Logs und xcodebuild warm/cold-Zeitdifferenzen

3. Wo die Zeit hingeht

Workflow-Steps timen → Wandzeit in fünf Blöcke. Warm-Verteilung (projektabhängig):

iOS CI wall clock 5 segments (warm · illustrative)
① checkout + env setup        ~0:30 – 1:30
② pod install / SPM resolve   ~0:30 – 2:00   (cold ↑↑)
③ xcodebuild compile+link      ~3:00 – 8:00   (code change surface)
④ tests (simulator / unit)    ~1:00 – 6:00   (optional, underestimated)
⑤ archive + codesign           ~1:00 – 4:00   (release pipeline)

warm P50 total typical: 6 – 14 min

Praxis: Step-Zeiten oder time für pod install, xcodebuild build, xcodebuild test. Block ② >5 Min. → cold/Cache; ③ schwankt → DerivedData/Concurrency; ④ dauernd lang → Tests auslagern oder nightly.

4. Tests & Signing: versteckte Bremsen

Viele „xcodebuild langsam“-Meldungen: Tests oder Signing im selben Build:

  • Simulator cold start: erste Boot-Zeit auf CI — ohne Warm-up pro Job neu
  • UI Tests eine Größenordnung langsamer als Unit; mit Compile in einem Job → hässliche P95
  • Zertifikate, Keychain, Profile — auf Hosted Runner oft pro Job neu
  • Archive/IPA = Release-Pipeline, nicht PR-Validierung
Kurz-Tipp · PR und Release trennen

PR: build + leichte Tests, warm P95.
TestFlight/Release: eigener Workflow, eigene Metrik. Zusammen → „Merge-Wartezeit“ passt nie.

5. 14-Tage-Shadow-Messung

Dual-Track: macos-latest und dedizierter Mac mini M4, je 187 PRs, Xcode 16.2 / CocoaPods 1.15.2 aligned. Methodik: Pillar Benchmark.

Klasse Samples Anteil macos-latest P95 dediz. M4 P95
warm build 162 86,6% 14:12 6:05
cold build 25 13,4% 19:40 11:20
gemischt (Fehlinterpretation) 187 100% ~16:00+ ~7:30+

cold+warm in einer P95 → Alltagserlebnis ~15–25 % zu pessimistisch → „sofort Hardware“. Besser: Merge = warm, Pod-Wochen = cold separat.

Auf dediziertem M4 bleibt warm schneller als cold — DerivedData/Pods mit festem Zuhause lohnt. Details: Cache-Cluster.

6. Reihenfolge: Klarheit → Cache

Nach Queue (oder wenn Queue klein ist) — nicht mit Kauf beginnen:

  1. Builds taggen: cold vs warm (Podfile.lock, Cache-Hit)
  2. SLA nur warm P95; cold wöchentlich oder eigene Linie
  3. DerivedData, Pods, SPM Cache-Strategie — Cache Best Practices (in Vorbereitung)
  4. macOS-Jobs nicht überfüllen: self-hosted 1–2 parallel; hosted wenig Workspace-Sharing
  5. PR-Validierung und Release/Signing trennen
  6. Cache reicht nicht → M4 / M4 Pro — Chip & Engineering Hours
workflow snippet · warm / cold tag (illustrative)
- name: Classify build type
  run: |
    if git diff --name-only HEAD~1 | grep -q Podfile.lock; then
      echo "BUILD_TYPE=cold" >> $GITHUB_ENV
    else
      echo "BUILD_TYPE=warm" >> $GITHUB_ENV
    fi

- name: Record wall clock
  run: |
    echo "build_type=${BUILD_TYPE}" >> metrics.csv
    echo "wall_sec=$(( $(date +%s) - START ))" >> metrics.csv

7. FAQ

2–3× Spread beim Re-Run — normal?

Auf macos-latest ja: einmal voller Cache-Hit, einmal Miss plus Queue. Zuerst pod install und Cache-Logs vergleichen, nicht sofort Chip tauschen.

P95 richtig berechnen?

Merge-Alltag: warm P95, ~30 Samples in zwei Wochen. cold separat — gemischt verzerrt Beschaffung.

pod install immer langsam — CocoaPods Schuld?

Auf Hosted Runner öfter: Pods ohne festen Ort, Cache-Key ohne Branch/Architektur. Self-hosted mit fester Platte: warm oft <30 Sek.

Mac mini M4 eliminiert Varianz?

warm P95 −57 %, σ −40 % — aber ohne cold/warm-Trennung und Cache-Design bleiben Spikes auf dedizierter Hardware.

Queue oder Build?

Log-Anfang: Waiting for a runner. Queue zählt zur Wandzeit, nicht zu GitHub-Minuten. Queue ≈0 und trotzdem langsam → Build-Seite dieser Notiz → Cache.

Weiterlesen?

Cache YAML → Cache-Cluster; Kauf vs. Miete → ROI und 500 Builds/Monat.

8. Fazit

„iOS CI langsam“ auf GitHub Actions ist selten nur CPU. Häufiger überlagert:

  1. cold und warm in einer P95
  2. Hosted Job räumt Platte → Cache weg
  3. Tests, Signing und PR-Build in einem Job

Erst warm P95 sauber, dann Cache → Concurrency → Hardware. Im 14-Tage-Shadow reichten Metrik + Cache für warm P95 14:12 → 6:05 — ohne vorschnellen Hardware-Beschluss.

DerivedData braucht ein festes Zuhause?

Vuncloud Cloud Mac M4 Pro: 1 TB Datenplatte, actions-runner vorinstalliert. DerivedData / Pods bleiben auf der Platte — weniger „jeder Lauf cold“ wie auf Hosted Runnern.

Cloud Mac Pakete · CI/CD-Anbindung FAQ

Feldnotizen · iOS CI

Erst schnell und langsam sehen, dann Chips tauschen

cold/warm trennen · Cache zuerst · Shadow-Dual-Run eine Woche

Pillar-Benchmark lesen
Zeitlich begrenzt Tarife ansehen