- Sur 187 PRs, 86,6 % sont des warm — le SLA quotidien doit viser le warm, pas mélanger le cold dans le P95
- Changement de deps, cache vidé, scheme différent → cold ; un écart 2–3× en temps mur est normal
- Même en warm : cache miss, jobs qui se battent pour le disque, tests + signature dans le même job → ça continue de fluctuer
- Ordre grossier : file → séparer cold/warm → cache → concurrence → matériel en dernier (cascade temps)
Données complètes → Pilier · Benchmark
1. CI loterie : même commit, temps différent
Si vous cherchez CI iOS lent ? Pourquoi xcodebuild traîne sur GitHub Actions, ces scènes vous diront quelque chose :
- Même commit, re-run — temps mur 2–3× d’écart
- P95 rouge sur le dashboard, ressenti équipe : « le merge n’attend pas si longtemps d’habitude »
pod installparfois 30 s, parfois « bloqué »- Vendredi après-midi, on évite de merger — peur du run lent
Pas toujours un manque de CPU. Sur 14 jours de Shadow avec des équipes, le plus fréquent : chiffres mélangés, environnement instable — cache qui ne tient pas, disque disputé. Changer de puce vient bien après.
Cette note ne traite qu’une chose : pourquoi les builds fluctuent. Job en file ? → article file. Cache, achat vs location → cluster cache et ROI.
2. cold et warm : ne pas tout mélanger
Piège classique : tout verser dans un seul P95. Quelques cold tirent la queue — les données crient « lent tous les jours », le merge quotidien dit autre chose.
- warm : deps inchangées, cache présent, même scheme — compile incrémentale ; représente le « merge normal »
- cold : lockfile modifié, cache effacé, nouveau target/scheme, premier run sur le runner — resolve, pod install, rebuild large
SLA : warm P50/P95. cold sur sa propre courbe — pas lié à l’expérience merge.
2.1 Quand ça devient cold
Sur macos-latest, le cold est plus fréquent — workspace nettoyé en fin de job, le cache ne « s’installe » pas :
| Déclencheur | temps ajouté typ. | indices log |
|---|---|---|
Podfile.lock modifié |
+3–8 min | pod install, Downloading dependencies |
| DerivedData miss / effacé | +5–15 min | CompileSwift, reconstruction .o complète |
| changement scheme / target | +2–10 min | autre xcodebuild -scheme |
| resolve SPM modifié | +1–5 min | Resolve Package Graph |
| upgrade mineur Xcode (macos-latest) | premier build +10–20 min | nouveau SDK / rebuild cache modules |
Beaucoup de montées Pod → plus de cold. Pas une machine qui vieillit — un travail de semaine différent. Reporter « semaine Pod » et « semaine dev » séparément.
2.2 Pourquoi le warm reste instable
Même tout en warm, le temps mur peut bouger ~30 %. Souvent :
- cache miss : clé sans
arm64, branche non liée, jobs sur le même slot - jobs macOS empilés dans l’org → disque et réseau se ralentissent
- une fois main seul, une fois tests unit+UI complets — charge différente
- surface de changement : source Pod vs une ligne SwiftUI — compile très différent
3. Où part le temps
Horodater chaque step → cinq blocs de temps mur. Distribution warm (varie selon projet) :
① checkout + env setup ~0:30 – 1:30 ② pod install / SPM resolve ~0:30 – 2:00 (cold ↑↑) ③ xcodebuild compile+link ~3:00 – 8:00 (surface de changement) ④ tests (simulator / unit) ~1:00 – 6:00 (optionnel, souvent sous-estimé) ⑤ archive + codesign ~1:00 – 4:00 (pipeline release) warm P50 total typique : 6 – 14 min
En pratique : durées par step ou time sur pod install, xcodebuild build, xcodebuild test. Bloc ② >5 min → cold/cache ; ③ instable → DerivedData/concurrence ; ④ toujours long → sortir les tests ou nightly.
4. Tests & signature : freins cachés
Beaucoup de « xcodebuild lent » cachent tests ou signature dans le même build :
- cold start simulateur : premier boot sur CI — sans préchauffage, à refaire chaque job
- UI tests un ordre de grandeur plus lents que unit ; avec compile dans un job → P95 illisible
- certificats, Keychain, profiles — souvent reconfigurés à chaque job sur runner hébergé
- archive/IPA = pipeline release, pas validation PR
PR : build + tests légers, warm P95.
TestFlight/release : workflow et métrique séparés. Mélangés → « temps d’attente merge » ne colle jamais.
5. Mesure Shadow 14 jours
Double piste : macos-latest et Mac mini M4 dédié, 187 PRs chacun, Xcode 16.2 / CocoaPods 1.15.2 alignés. Méthode : benchmark pilier.
| classe | échantillons | part | macos-latest P95 | M4 dédié P95 |
|---|---|---|---|---|
| warm build | 162 | 86,6% | 14:12 | 6:05 |
| cold build | 25 | 13,4% | 19:40 | 11:20 |
| mélangé (erreur fréquente) | 187 | 100% | ~16:00+ | ~7:30+ |
cold+warm dans un P95 → expérience quotidienne surévaluée ~15–25 % → « acheter tout de suite ». Mieux : merge = warm, semaines Pod = cold à part.
Sur M4 dédié, warm reste plus rapide que cold — DerivedData/Pods avec un domicile fixe paie. Détails : cluster cache.
6. Ordre : clarifier → cache
Après la file (ou si elle est faible) — ne pas commencer par l’achat :
- étiqueter builds : cold vs warm (
Podfile.lock, cache hit) - SLA sur warm P95 seulement ; cold en rapport hebdo ou courbe séparée
- stratégie cache DerivedData, Pods, SPM — bonnes pratiques cache (en préparation)
- ne pas surcharger les jobs macOS : self-hosted 1–2 parallèles ; hébergé, peu de workspace partagé
- séparer validation PR et release/signature
- cache insuffisant → M4 / M4 Pro — puce & heures ingénieur
- 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× sur le même commit — normal ?
Sur macos-latest, oui : un run cache plein, l’autre miss + file. Comparez pod install et logs cache avant de changer de puce.
Comment calculer le P95 ?
Expérience merge : P95 warm, ~30 échantillons en deux semaines. cold à part — mélangé fausse les achats.
pod install toujours lent — faute de CocoaPods ?
Sur runner hébergé : Pods sans lieu fixe, clé cache sans branche/archi. Self-hosted disque fixe : warm souvent <30 s.
Mac mini M4 supprime la variance ?
warm P95 −57 %, σ −40 % — mais sans séparation cold/warm et design cache, les pics restent sur machine dédiée.
File ou build ?
Début de log : Waiting for a runner. La file compte dans le temps mur, pas dans les minutes GitHub. File ≈0 et toujours lent → côté build de cette note → cache.
Pour aller plus loin ?
YAML cache → cluster cache ; achat vs location → ROI et 500 builds/mois.
8. Conclusion
« CI iOS lent » sur GitHub Actions, ce n’est rarement que du CPU. Plus souvent :
- cold et warm dans le même P95
- job hébergé qui efface le disque → cache perdu
- tests, signature et build PR dans un seul job
D’abord un warm P95 propre, puis cache → concurrence → matériel. En 14 jours Shadow, métriques + cache ont suffi pour passer warm P95 de 14:12 à 6:05 — sans décision d’achat précipitée.
Besoin d’un « domicile » fixe pour DerivedData ?
Vuncloud Cloud Mac M4 Pro : disque données 1 To, actions-runner préinstallé. DerivedData / Pods restent sur le disque — moins de « cold à chaque run » comme sur runner hébergé.