Un même commit est compilé trois fois de suite sur un Mac cloud : le premier build se déroule normalement, le deuxième ralentit soudainement, puis le troisième retrouve ses performances habituelles. Le CPU ne reste pas saturé et les téléchargements réseau sont terminés depuis longtemps. Ce type de variation est souvent attribué à tort à un manque de ressources. En réalité, Spotlight peut être en train d’analyser DerivedData, un éditeur peut surveiller récursivement tout l’espace de travail, ou plusieurs tâches CI peuvent écrire simultanément dans le même répertoire de cache. La bonne approche ne consiste pas à vider immédiatement tous les caches, mais à préserver l’état du système, à quantifier les E/S, puis à isoler chaque source de contention.
Établir une base de référence comparable pour les builds
Avant toute investigation, fixez le commit, la version de Xcode, la cible de build et les chemins de cache. Ne mélangez pas le premier téléchargement des dépendances avec les builds incrémentaux suivants dans un même jeu de mesures, et n’exécutez pas de script de nettoyage pendant un build.
set -o pipefail
WORKSPACE="$HOME/ci/work/app"
DERIVED="$HOME/ci/derived/app-main"
PACKAGES="$HOME/ci/packages/app"
mkdir -p "$DERIVED" "$PACKAGES"
cd "$WORKSPACE"
xcodebuild \
-resolvePackageDependencies \
-clonedSourcePackagesDirPath "$PACKAGES"
for run in 1 2 3; do
/usr/bin/time -lp xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Debug \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath "$DERIVED" \
-clonedSourcePackagesDirPath "$PACKAGES" \
build 2>&1 | tee "$HOME/ci/build-$run.log"
done
Consignez le temps écoulé, la mémoire résidente maximale, la phase de résolution des dépendances visible dans les journaux, ainsi que la présence éventuelle d’autres tâches au moment du ralentissement. Si les trois exécutions sont lentes, examinez en priorité le projet ou la chaîne d’outils. Si seules certaines exécutions sont anormales, une contention d’E/S en arrière-plan est plus probable.
| Observation | Vérification prioritaire |
|---|---|
| CPU peu sollicité, mais build à l’arrêt | Attentes du système de fichiers, verrous de répertoires |
mds ou mdworker actif |
Périmètre d’indexation de Spotlight |
| Retour à la normale après la fermeture de l’éditeur | Surveillance récursive des fichiers |
| Problème uniquement avec des tâches parallèles | DerivedData ou cache de paquets partagé |
Capturer l’activité d’E/S avec les outils système
Commencez par vérifier l’état de l’indexation, puis capturez les accès aux fichiers pendant un build anormal. fs_usage nécessite des privilèges d’administration et produit un volume de données important : limitez d’abord les processus observés, puis filtrez la sortie à l’aide de mots-clés correspondant aux répertoires.
mdutil -s /
sudo fs_usage -w -f filesys mds mdworker_shared |
grep -E 'DerivedData|SourcePackages|/ci/work/'
Dans un autre terminal, examinez les processus concernés :
ps -axo pid,ppid,%cpu,%mem,etime,command |
grep -E 'mds|mdworker|xcodebuild|swift-frontend|SourceKit' |
grep -v grep
Si mdworker ne fait que lire brièvement du code source récemment récupéré, cela ne suffit pas à établir qu’il est à l’origine du problème. Un indice plus probant est une analyse prolongée d’un grand nombre d’artefacts intermédiaires pendant la période anormale, avec des chemins d’analyse qui recoupent ceux dans lesquels le build écrit.
N’exécutez pas
rm -rf DerivedDataavant d’avoir recueilli les éléments de diagnostic. Vider le cache peut ralentir le build suivant et supprimer les indices permettant d’identifier « quel processus accède de façon répétée à quels fichiers ».
Restreindre le périmètre d’indexation de Spotlight
Il est déconseillé de désactiver directement l’indexation du volume système. Les développeurs peuvent avoir besoin de la recherche système, et une modification globale masquerait également le véritable problème d’organisation des répertoires. Une solution plus sûre consiste à placer les caches fréquemment régénérés et entièrement reconstructibles sur un volume APFS distinct, puis à ne modifier que les paramètres de ce volume.
Après avoir vérifié le point de montage du volume de cache, exécutez :
mdutil -s /Volumes/CICache
sudo mdutil -i off /Volumes/CICache
mdutil -s /Volumes/CICache
Conservez l’indexation du code source, de la documentation et des ressources qui doivent rester consultables. Placez sur ce volume DerivedData, le cache de téléchargement des paquets, les pièces jointes de test et les répertoires temporaires d’archives. Pour réactiver ultérieurement l’indexation :
sudo mdutil -i on /Volumes/CICache
sudo mdutil -E /Volumes/CICache
Ne placez pas les éléments de signature, les artefacts à long terme et les caches temporaires dans un même périmètre de nettoyage. L’exclusion de l’indexation ne traite que la contention liée à l’analyse ; elle ne remplace ni le contrôle des autorisations ni la classification des données.
Maîtriser la surveillance récursive et les caches partagés
Les éditeurs, générateurs de code et serveurs de développement surveillent souvent la racine du dépôt. Si les règles de surveillance incluent .git, DerivedData, les pièces jointes de test ou les caches de paquets, chaque build peut déclencher des dizaines de milliers d’événements inutiles.
Réduire le périmètre de surveillance
Limitez la surveillance aux répertoires du code source et de la configuration, et excluez explicitement les éléments suivants :
.gitet les fichiers temporaires créés lors de l’extraction ;- DerivedData, les archives et les répertoires de résultats de test ;
SourcePackageset les autres caches de dépendances reconstructibles ;- les journaux, les rapports de couverture et les sorties de code généré.
Fermez d’abord l’éditeur ou l’agent suspect, puis exécutez de nouveau la même base de référence. Si les variations disparaissent, réactivez les processus un par un : il sera ainsi plus facile d’identifier le responsable que si vous modifiez tous les outils simultanément.
Attribuer des chemins d’écriture distincts aux tâches parallèles
Les builds séquentiels peuvent réutiliser le cache du projet. En revanche, plusieurs tâches CI parallèles ne doivent pas écrire dans le même répertoire DerivedData. Le chemin doit contenir au minimum les identifiants du dépôt et de la tâche :
SAFE_REPO="${REPO_NAME//[^a-zA-Z0-9_-]/_}"
SAFE_JOB="${JOB_ID//[^a-zA-Z0-9_-]/_}"
export DERIVED_DATA="$HOME/ci/derived/$SAFE_REPO/$SAFE_JOB"
export PACKAGE_CACHE="$HOME/ci/packages/$SAFE_REPO"
mkdir -p "$DERIVED_DATA" "$PACKAGE_CACHE"
Le cache de paquets peut être partagé en lecture, mais la mise à jour des dépendances peut toujours provoquer des écritures concurrentes. Dans les environnements à forte concurrence, effectuez d’abord la résolution des dépendances lors d’une étape contrôlée, puis faites utiliser son résultat stable aux tâches de build.
Séparer le nettoyage de la validation de non-régression
Les scripts de nettoyage doivent éviter toute exécution de xcodebuild en cours et supprimer les caches expirés par répertoire de tâche, plutôt que de vider systématiquement le répertoire racine chaque jour. Commencez par afficher uniquement les éléments candidats :
DERIVED_ROOT="$HOME/ci/derived"
if ! pgrep -x xcodebuild >/dev/null; then
find "$DERIVED_ROOT" \
-mindepth 2 -maxdepth 2 \
-type d -mtime +7 -print
fi
Après avoir validé la profondeur des répertoires et la durée de conservation, placez la suppression dans une tâche distincte. Pour la validation, exécutez de nouveau trois fois la base de référence fixe tout en surveillant fs_usage. Le critère de réussite n’est pas un build ponctuellement très rapide : les processus d’indexation ne doivent plus analyser continuellement les répertoires de build, les tâches parallèles ne doivent partager aucun chemin d’écriture et la répartition des phases doit rester stable d’un build à l’autre.
Enfin, consignez dans le diagnostic du build la version de Xcode, le commit, le chemin de DerivedData, le chemin du cache de paquets, les processus de surveillance actifs et l’état de l’indexation. Lors de la prochaine variation, vous pourrez commencer par comparer les différences d’environnement au lieu de repartir d’hypothèses après avoir vidé les caches.
Questions fréquentes
Faut-il désactiver Spotlight sur tout le disque système ?
Non, pas par défaut. Vérifiez d’abord si mds ou mdworker accède durablement aux répertoires de build, puis limitez l’exclusion à un volume de cache dédié afin de conserver la recherche normale du système.
Plusieurs tâches CI peuvent-elles partager le même DerivedData ?
Le partage convient surtout aux builds séquentiels d’un même projet. Pour des tâches parallèles, utilisez un chemin distinct par dépôt, branche ou identifiant de tâche afin d’éviter les écritures concurrentes et les invalidations imprévisibles.
Choisissez un Mac dans le cloud pour votre prochain pipeline de développement
Comparez trois configurations fixes et quatre nœuds en Asie, puis choisissez la durée de location adaptée à votre charge de travail. Chaque location correspond à une machine physique dédiée, et non à une machine virtuelle.