Dieser Artikel beschreibt ein iOS CI/CD Scaling Failure Model — keinen Kapazitätsrechner. Bevor wir eingestiegen sind, wuchs das Team in vierzehn Monaten von 8 auf 20 Personen, während die Runner-Topologie praktisch unverändert blieb: Release-Wochen mit durchgehend roten PRs, Builds, die ewig in Queued hingen, TestFlight zwei Tage zu spät. Im Folgenden erklären wir, warum CI bei mehr Leuten langsamer wird und an welcher Pipeline-Stelle der Engpass sitzt.
Wer wissen will, wie viele Macs man für 500 Builds pro Tag braucht, findet die Antwort im Schwesterartikel 3 Cloud Macs für 500 iOS-Builds pro Tag — das ist die Umsetzung nach unserem Einstieg. Hier geht es umwarum es vorher kollabierte.
Auf einen Blick: Wie CI von „mehr Leute“ in die Failure Zone rutscht
Abbildung 1 ist das Übersichtsdiagramm des gesamten Artikels — besser geeignet für Postmortem-Eröffnungen oder Team-Onboarding als Pipeline-Details. Ein Kollaps ist selten ein Einzelpunkt, sondern die Überlagerung der folgenden Kette.
Abb. 1 · CI-Kollaps-Mechanismus (Scaling Failure Cascade)
Bei diesem Kunden blieb es bei 12 Personen in der Queue stecken, bei 16 leuchteten Runner/Archive-Abzweigung gleichzeitig, bei 20 landete man unten im roten Bereich. Der Vergleich mit der healthy baseline folgt unten.
iOS CI/CD-Architektur: Engpasskarte von PR bis TestFlight
Abbildung 2 zeigt den Ausführungspfad eines macOS-Jobs (L0/L1/L2) und markiert die beiden Stellen, die in diesem Fall zuerst voll liefen. So erkennt man: Kompilierung langsam — oder der Job läuft gar nicht erst.
Abb. 2 · iOS CI/CD Pipeline: L0 / L1 / L2 und typische Engpässe (Kunde 2024–2025)
L0: lint / leichte Checks; L1: PR-Integrationsbuild und Simulator-Tests; L2: Archive, Notarisierung, Upload. Der Kunde kollabierte in Queue und Runner-Mischpool, nicht in L1-Kompilierung — Matrix-Inflation wird oft fälschlich als „Xcode wird langsamer“ gelesen.
Ausgangslage: Warum die CI bei 8 Personen „reichte“, aber nicht mitwuchs
Mitte 2024 sah das Setup typisch aus:
- 1 Mac mini M2 im Büro, nebenbei als Self-hosted Mac Runner (siehe GitHub-Doku zu Self-hosted Runners)
- Weitere Jobs über GitHub-gehostete macOS Runner
- Monorepo mit 2 Apps, ein Workflow-YAML für alles
- Etwa 80–120 macOS-Jobs pro Tag, Queue-P95 unter 5 Minuten
Der CTO sagte wörtlich: „CI ist kein Engpass, verschwendet keine Zeit damit.“ — Bei 8 Personen stimmte das; nach Verdoppelung der Produktivität wurde CI zur unsichtbaren Steuer, weil niemand Kapazität plante und Feature-Teams nur Workflows stapelten.
Healthy baseline: Woran misst man „normal“?
Betrachtet man nur die Kollapswoche bei 20 Personen, vermutet man schnell: „Großes Team = viele Macs kaufen.“ Wir ergänzen einen Vergleich healthy baseline vs. Kollapswoche — Zahlen aus dem 8-Personen-Messfenster des Kunden plus Erfahrungswerte aus mid-size iOS-Teams, die wir betreut haben (kein Branchenstandard, aber ausreichend für interne Reviews).
| Metrik | Healthy baseline (8–12 Pers. · saubere Topologie) | Kunde · Kollapswoche bei 20 Pers. |
|---|---|---|
| L1 queue_wait P95 | < 8 Min. | 47 Min. |
| L1 run duration P95 | 12–18 Min. | 14 Min. (Kompilierung nicht langsamer) |
| Anteil Archive-Jobs | < 15% | 38% |
| macOS Jobs pro Push · P99 | < 6 | 19 |
| macOS Jobs / Tag | 80–180 | 500+ (Release-Woche) |
| Runner-Topologie | L1/L2 per Label getrennt | hosted + Büro-mini gemischt |
Entscheidend: In der Kollapswoche blieb run duration fast gleich, queue time stieg fast zehnfach — das Problem lag in Warteschlangen und Workflows, nicht in Xcode-Kompilierung. Genau hier irreführt der Begriff „CI wird langsamer“ am meisten.
Zeitstrahl: Drei Warnungen, dreimal als „Ausreißer“ abgetan
Erstes Mal (12 Personen, +4 iOS): PR-Merges stiegen von 6 auf 14 pro Tag. Die build queue auf gehosteten Runnern zeigte nachmittags 25–40 Minuten Wartezeit. Reaktion: „GitHub spinnt heute wohl.“ Niemand zog die queue_wait_seconds-Kurve.
Zweites Mal (16 Personen, +2 Backend im selben Repo): Im Büro kam ein zweiter Mac mini dazu, beide als self-hosted registriert, ohne Label-Trennung. Archive und PR-Builds teilten sich einen Pool — leichte Jobs sprangen sporadisch von 8 auf 35 Minuten, schwere Jobs scheiterten zufällig bei der Notarisierung. Reaktion: „Kaufen wir noch eine Festplatte.“
Drittes Mal (20 Personen, große Versionen zweier Apps): In der Release-Woche stiegen macOS-Jobs von 120 auf 280+ pro Tag. Im Slack-Kanal #ios-ci war rund um die Uhr jemand im Dienst. Reaktion: „Einkauf prüft 8 Mac minis.“ — Genau das 8-Maschinen-Szenario aus unserem 500-Builds/Tag-Fall; damals hatte noch niemand Job-Aufschlüsselungen gerechnet.
Abb. 3 · Teamgröße vs. macOS Jobs/Tag vs. Risikostufe (Kunde 2024–2025, gemessen)
| Teamgröße | macOS Jobs / Tag | GitHub Actions queue time (L1 P95) | Risikostufe | Phasennotiz |
|---|---|---|---|---|
| 8 Pers. | ~100 | < 5 Min. | Baseline | Ein Büro-mini reichte |
| 12 Pers. | ~180 | 25–40 Min. | Warning Zone | Queue erstmals dauerhaft über Schwellwert |
| 16 Pers. | ~260 | ~35 Min. | Structural Debt | Archive gemischt, Signing sporadisch fehlgeschlagen |
| 20 Pers. | 500+ | 47 Min. | Failure Zone | Release-Wochen-Peak, CI faktisch kollabiert |
Quelle: GitHub Actions Workflow-Logs (14-Tage-Gleitfenster); bei 20 Personen Peak der Release-Woche beider Apps. Job-Volumen wächst schneller als Kopfzahl — hauptsächlich durch Matrix-Inflation und PR-Archive, nicht bloß „mehr Leute“.
Zwei Details in der Tabelle: Zwischen 16 und 20 Personen steigt das Job-Volumen steil, Archive-Anteil von ~18 % auf 38 %; queue time und Job-Volumen sind nichtlinear — die Köpfe verdoppeln sich nicht, die Wartezeit schon. Genau hier verrechnen sich Skalierungsbudgets.
Schwellenmodell: 12 Warning / 16 Structural Debt / 20 Failure Zone
Wir haben den Fall zu einem Framework für Weekly Reviews verdichtet — keine exakte Formel, aber ausreichend, um Runner-Topologie anzufassen:
(PR-Frequenz × Matrix-Breite × Archive-Anteil) ÷ effektive Runner-Pool-Kapazität
Alle drei Zähler lassen sich aus GitHub Actions-Logs ableiten; der Nenner „effektiver Runner-Pool“ ist nicht die registrierte Maschinenanzahl, sondern gleichzeitig nutzbare Mac-Slots nach Label-Trennung — wenn Entwickler den Büro-mini blockieren, bricht der Nenner sofort ein. Gelten zwei der folgenden Bedingungen gleichzeitig, stoppen wir die Diskussion „noch ein Mac mini“ und refaktorisieren die Pipeline:
- queue_wait_seconds P95 > 30 Min. und Archive-Jobs > 30 % → L1/L2 trennen, kein vollständiges PR-Archive mehr
- macOS Jobs pro Push · P99 > 15 → Matrix/path filter außer Kontrolle, erst Jobs reduzieren, dann Hardware
- L2 queue P95 > 40 Min. an 3 Tagen hintereinander und Cache-Hit > 60 % → strukturelle Skalierung, dedizierter Release-Pool
In der Kollapswoche bei 20 Personen trafen alle drei zu — damit Failure Zone, nicht bloß Warning, die man mit Minutenpaketen wegdrückt.
GitHub Actions queue time: Erste Kennzahl, die ab 12 Personen kippt
Viele Teams lesen „CI wird langsamer“ als langsames Kompilieren und optimieren DerivedData. In den Logs dieses Kunden geriet queue time außer Kontrolle — Jobs blieben lange in Queued. GitHub trennt queue_wait_seconds und run duration; wer nur Letzteres betrachtet, optimiert Kompilierung, während der Engpass in Organisations-Concurrency und Runner-Pools liegt.
Organisationsweite macOS-Concurrency: GitHub Actions usage limits. Ab 12 Personen bei Nachmittags-PR-Stürmen: Runner sind nicht langsam, Jobs warten auf Slots — Queued im UI wird oft Netzwerk oder Xcode zugeschoben. Beim Debuggen Job-Ausführungszeit und Wartezeit trennen.
Bei 12 Personen lag queue P95 schon bei 25–40 Minuten. Hätte man fast/archive per Label getrennt (statt zwei minis gemischt), wäre Signing-Konflikt später gekommen. Runner-Topologie: Entscheidungsmatrix elastischer Pool vs. feste Knoten.
Fünf Skalierungsengpässe (Scaling Taxonomy)
Die folgenden fünf Kategorien sind unsere internen Mobile-CI-Fehlerlabels — passend zu Abb. 2, praktisch für Postmortems.
① Capacity bottleneck · Organisations-macOS-Concurrency ausgeschöpft
Bei voller gehosteter Concurrency warten leichte Jobs und Archive gemeinsam — schnelle Queues werden von langsamen Jobs blockiert, wie in der GitHub Actions queue time-Kurve oben. Statt Minutenpakete: L2 aus dem gehosteten Pool; sonst bleibt Organisations-Concurrency die erste Decke.
② Resource contention · Büro-Mac mini als „Reserve-Entwicklungsrechner“
Runner auf Maschinen, auf die Entwickler per SSH kommen — irgendwann läuft lokal xcodebuild debug. Runner und Alltagsentwicklung konkurrieren um CPU/Disk, Timeouts oder sporadische compile errors folgen. Bei 16 Personen: zwei minis „freitags tot, montags ok“ — Ursache Remote Desktop und Branch-Wechsel.
③ Workflow design debt · Jedes PR führt vollständiges Archive aus
Bei 8 Personen die Abkürzung „grünes PR = releasefähig“: Archive + Upload am Ende jedes pull request workflows. Bei 6 Merges pro Tag noch erträglich; bei 20 Personen stieg der Archive-Anteil von 10 % auf 35 %+ — langsame Jobs fluten die ohnehin volle Queue. Richtig: L1-Integrationsbuild und L2 Archive trennen — erst nach unserem Einstieg umgesetzt, siehe Job-Schichtung im Schwesterartikel.
④ Scaling explosion · Matrix-Jobs im Monorepo wachsen exponentiell
Bei 20 Personen löste ein Push bis zu 22 macOS-Jobs aus — 2,5× mehr Leute, 4× mehr Jobs. Der am leichtesten übersehene Kollapspunkt im Produkt-Takt.
⑤ Crypto / signing contention · Keychain und Signing am Release-Tag
Zwei Self-hosted Macs parallel mit Archive — auf 16 GB RAM sporadisch noch ok; plus Match-Unlock, Notarisierung, TestFlight-Upload werden Keychain-Sperren und codesign-Konflikte zu „derselbe Fehlercode zufällig“. In der Release-Woche vier Mal hintereinander „Zertifikat nicht gefunden“, lokal am Mac ok — typisch für parallele Jobs in derselben Signing-Umgebung, nicht abgelaufenes Zertifikat. Doku: Apple Xcode Signing and Capabilities.
Ihre „Fixes“ — und warum es schlimmer wurde
Mehr GitHub-Minuten kaufen: Entlastet nur gehostete Queues, Archive-Anteil unverändert — Geld ausgegeben, P95 weiter über 60 Minuten.
Dritter Mac mini im Büro: Weiter ohne fast/archive-Labels, drei Maschinen gemischt — Signing-Fehlerrate stieg.
Freitags-Merges verbieten: PR-Frequenz künstlich gedrückt, Release-Woche konzentriert Spitzen noch höher.
timeout-minutes: 180 pro Job: Queued-Zeit zählt nicht zum Timeout — mehr Jobs blockieren lange Slots.
Ein halbwegs wirksamer Versuch blieb unvollendet: Release auf separate Maschine — weiter Büro-mini, nachts niemand für Keychain-Unlock, L2-Queue staut sich montags.
Drei Zahlen, die ins Monitoring gehören — aber fehlten
Vor dem Kollaps zeigte Grafana nur Erfolgsrate und Durchschnittsdauer. In Woche eins nach Einstieg reichten drei Metriken für 80 % der Diagnose (queue_wait_seconds-Aufteilung: GitHub-Monitoring-Doku):
- queue_wait_seconds P95 (nach L0/L1/L2) — „Kompilierung langsam“ vs. „in der Queue“
- Anteil Archive-Jobs (wöchentlich) — Workflow-Design außer Kontrolle?
- macOS Jobs pro Push · P99 — Matrix-Explosion?
Kollapswoche bei 20 Personen: L1 queue_wait P95 47 Min., Archive 38 %, Jobs pro Push P99 19. Zwei Werte über Schwellwert → Topologie anfassen, nicht zuerst Einkauf.
Reihenfolge beim Stoppen der Blutung: Topologie, dann Mac-Anzahl
In den ersten zwei Wochen nach Einstieg: keine neuen Maschinen, vier Maßnahmen:
- Archive aus PR-Workflow, L2 nur nightly + release branches
- Path filter enger — README/Backend löst iOS-Matrix nicht mehr aus
- Büro-mini als Runner abgemeldet, keine Konkurrenz mit Entwicklung
- Gehostete Runner nur für L1-Spitzen, L2 auf dedizierte Knoten
Nur das senkte queue_wait P95 in der Release-Woche von 47 auf 22 Minuten — noch nicht genug, aber bewies: Hauptursache Topologie und Workflow, kein „mysteriöses Mac-Formel“. Erst danach Cloud-Mac fast/release-Pools und Kapazitätstests; elastischer vs. fester Pool: GitHub Actions Self-hosted macOS Runner: Entscheidungsmatrix.
Drei Signale für wachsende Teams
Wer von 12 Richtung 20 skaliert und eines davon sieht, sollte diese Woche 30 Minuten CI-Review einplanen — nicht auf Release-Kollision warten:
- Entwickler fragen: „Können wir ohne grünes CI mergen?“
- Am Büro-Mac klebt „Bitte nicht neu starten, Runner läuft“
- TestFlight-Build ok, aber „Warten auf Upload-Fenster“ ist Normalzustand
Hinter diesen Signalen stecken meist mindestens zwei der fünf Kollapspunkte oben.
Postmortem Summary (zitierbar)
Root cause
Beim Teamwachstum CI-Topologie nicht mit skaliert: PR-Frequenz und Matrix-Breite stiegen, Runner blieben „hosted + Büro-mini gemischt“, PR-Workflows liefen weiter L2 Archive — queue und signing doppelt gesättigt.
Contributing factors
- Kein Monitoring von
queue_wait_seconds, Queued als langsames Kompilieren missverstanden - Zweiter Mac mini ohne fast/archive-Labels, Archive und PR im selben Pool
- Monorepo path filter zu breit, README-Änderung löst iOS-Matrix aus
- Release-Wochen-Peaks nicht mit Einkauf/Topologie verknüpft
Fixes tried (wirkungslos oder verschlimmernd)
- Mehr GitHub-Minuten — nur hosted-Queue, Archive-Anteil gleich
- Dritter Büro-mini gemischt — Signing-Fehler häufiger
- Freitags-Merge-Verbot — Spitze in Release-Woche
timeout-minutes: 180— Queued zählt nicht
What worked (erste zwei Wochen nach Einstieg)
- Archive aus PR; L2 nur nightly + release branches
- Path filter enger; Büro-mini ohne Runner
- L2 auf dedizierte Release-Knoten — queue P95 47min → 22min
FAQ
GitHub Actions bleibt in Queued — nicht langsame Kompilierung?
Zuerst queue_wait_seconds und run duration prüfen. Hoher Queued-Anteil → Engpass Queue oder Runner-Pool, nicht Kompilierung. Abb. 2 Queue-Knoten und Abb. 3 healthy baseline zum Abgleich.
Bei wie vielen iOS-Entwicklern bricht CI am ehesten?
In diesem Fall: 12 Warning / 16 Structural Debt / 20 Failure Zone. Zuverlässiger: queue P95 > 30 Min. und Archive > 30 % → Pipeline refaktorisieren.
Beim Wachstum: erst Hardware oder erst Pipeline?
Job-Schichtung, PR-Archive stoppen, Matrix straffen. Sonst verschiebt Hardware den Kollaps nur — queue-Spitzen kommen in der nächsten Release-Woche zurück.
Ist das derselbe Artikel wie „500 Builds/Tag, wie viele Macs“?
Nein. Hier warum der Kollaps (Failure Model); Schwesterartikel Kapazität und Maschinenzahl (Sizing). Erst diesen Artikel + Diagnose, dann Sizing.
Failure Zone-Rezept: erst L2 isolieren, dann Runner-Pool validieren
Alle drei Bedingungen erfüllt → Failure Zone (wie Abb. 1 unten)
- queue_wait_seconds P95 > 30 Min.
- Archive-Jobs > 30 %
- macOS Jobs pro Push > 15
Engineering zuerst (vor Einkauf)
1. L2 isolation — Archive aus PR; release/nightly exklusiv macos-archive-Label.
2. Dedicated Mac pool — L2 raus aus Büro-Entwicklung und hosted-Mischpool; L1 weiter hosted oder elastisch self-hosted für Spitzen.
3. Validierung — Release-Workflow auf isoliertem Knoten, queue P95 gegen Abb. 3 healthy baseline (< 8 Min.).
Maschinenzahl und fast/release-Pool-Logik: 3 Cloud Macs für 500 iOS-Builds pro Tag; elastisch vs. fest: GitHub Actions Self-hosted macOS Runner Entscheidungsmatrix.
Für einen tagesweisen PoC eines release-dedizierten Knotens zur L2-isolation-Validierung: Mac Cloud Hosting oder VPSSpark Startseite — isolierte Archive-Queue auf Cloud Mac. Topologie validieren, nicht sofort volles Cluster kaufen.