Engineering-Praxis

SourceKit-Indexierungsstau und hohe Xcode-CPU-Last diagnostizieren

SourceKit-Indexierungsstau und hohe Xcode-CPU-Last diagnostizieren

Nach dem Öffnen eines großen Xcode-Workspace auf einem entfernten Mac ist zwar kein Lüfter zu sehen, doch die CPU-Kurve im Terminal steigt kontinuierlich an. Die Codevervollständigung bleibt bei „Indexierung läuft“ stehen, und „Zur Definition springen“ liefert kein Ergebnis. Die gefährlichste Reaktion ist jetzt nicht das Abwarten, sondern das sofortige Löschen des gesamten Entwicklungsverzeichnisses. Richtig ist folgende Reihenfolge: Zuerst klären, ob die Last vom Build oder von SourceKit stammt, anschließend den aktuellen Zustand für die Diagnose sichern und zuletzt nur den betroffenen Index neu aufbauen.

Normale Neuindexierung von einem echten Stillstand unterscheiden

SourceKit erstellt den Index neu, wenn ein Projekt erstmals geöffnet, die Xcode-Version gewechselt, eine Lockdatei für Abhängigkeiten geändert oder zwischen stark voneinander abweichenden Branches gewechselt wird. Eine kurzfristig hohe CPU-Last ist daher noch kein Fehler. Stoppen Sie zunächst aktive Builds, lassen Sie die Dateien im Workspace unverändert und vergleichen Sie zwei bis drei aufeinanderfolgende Prozess-Snapshots:

date
ps -axo pid,ppid,%cpu,%mem,etime,command | grep -E 'SourceKit|sourcekitd|XCBBuildService|swift-frontend' | grep -v grep

Achten Sie vor allem auf drei Punkte: Kompilieren XCBBuildService oder swift-frontend noch? Steigt die Laufzeit der SourceKit-Prozesse kontinuierlich? Bleibt die CPU-Last auch nach dem Ende der Bearbeitung über längere Zeit auf dieselbe Prozessgruppe konzentriert? Solange Build-Prozesse noch arbeiten, sollte der Build zunächst abgeschlossen werden. Kompilierlast darf nicht mit einer festgefahrenen Indexierung verwechselt werden.

Ein einzelner CPU-Messwert reicht nicht aus, um einen Stillstand festzustellen. Eine Bereinigung ist erst dann angebracht, wenn vier Bedingungen gleichzeitig erfüllt sind: Die Dateimenge ist stabil, es läuft kein Build, die Codevervollständigung macht über längere Zeit keine Fortschritte und die Logs verweisen wiederholt auf dasselbe Modul.

Vergleichbare Diagnosedaten sichern

Starten Sie Xcode nicht sofort neu. Legen Sie zunächst ein Diagnoseverzeichnis für Prozesslisten, Logs und Aufrufstapel an. Nur so lässt sich später beurteilen, ob die Wiederherstellung tatsächlich erfolgreich war.

STAMP="$(date +%Y%m%d-%H%M%S)"
OUT="$HOME/sourcekit-diagnostics/$STAMP"
mkdir -p "$OUT"
ps -axo pid,ppid,%cpu,%mem,etime,command > "$OUT/processes.txt"
log show --last 10m --style compact --predicate 'process CONTAINS[c] "SourceKit" OR process == "Xcode"' > "$OUT/sourcekit.log"
PID="$(pgrep -n -f 'SourceKit|sourcekitd')"
test -n "$PID" && sample "$PID" 10 -file "$OUT/sourcekit.sample.txt"

Wechseln Sie während des Samplings weder den Branch noch eine große Anzahl von Dateien. Suchen Sie in den Logs vorrangig nach wiederkehrenden Modulnamen, nicht lesbaren Pfaden, fehlgeschlagenen Plugin-Ladevorgängen und Schleifen aus wiederholt abgebrochenen Anfragen. Wenn der Aufrufstapel mehrfach beim Parsen derselben Swift-Datei oder derselben generierten Datei landet, prüfen Sie zuerst, ob ein Skript diese Datei fortlaufend neu schreibt.

Schleifen durch generierte Dateien ausschließen

Eine häufige Ursache sind Codegeneratoren, die bei jeder Ausführung den Zeitstempel ändern. Selbst wenn der Inhalt unverändert bleibt, muss der Indexer die Datei dadurch erneut verarbeiten. Ein Generierungsskript sollte deshalb zunächst die Inhalte vergleichen und das Ziel erst anschließend atomar ersetzen. Prüfen Sie außerdem, ob das Ausgabeverzeichnis gleichzeitig als Ordnerreferenz und als Quellcodeverzeichnis zum Projekt hinzugefügt wurde.

DerivedData für jeden Workspace isolieren

Wenn mehrere Workspaces dasselbe DerivedData-Verzeichnis verwenden, können sich unterschiedliche Branches, Xcode-Versionen und parallele Jobs gegenseitig beeinträchtigen. Auf einem Cloud-Mac ist es zuverlässiger, jedem Workspace oder CI-Job einen eindeutigen Pfad zuzuweisen:

ROOT="$PWD"
DERIVED="$ROOT/.derived-data"
xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -derivedDataPath "$DERIVED" \
  -showBuildSettings > "$ROOT/build-settings.txt"

.derived-data darf nicht in das Repository eingecheckt werden. Parallele Jobs benötigen zusätzlich eine Job-Kennung wie .derived-data/job-42, damit nicht zwei Builds gleichzeitig in denselben Modul-Cache schreiben.

Szenario DerivedData-Strategie Indexierungsstrategie
Interaktive Remote-Entwicklung Festes, separates Verzeichnis pro Workspace Aktiviert lassen
Einmaliger Validierungs-Build Separates Verzeichnis pro Job Nach Bedarf aktivieren
Reine CI-Builds und -Tests Separates Verzeichnis pro Job Schreiben des Index deaktivierbar
Wechsel der Xcode-Version Neues versionsspezifisches Verzeichnis verwenden Neu erstellen

Bei CI-Jobs, die keine Codevervollständigung benötigen, kann COMPILER_INDEX_STORE_ENABLE=NO zur Befehlszeile hinzugefügt werden. Tragen Sie diese Einstellung jedoch nicht pauschal in eine gemeinsam genutzte Projektkonfiguration ein. Andernfalls verlieren Entwickler Funktionen wie Definitionssprünge, Diagnosen und Codevervollständigung.

Wiederherstellung schrittweise nach Auswirkungsbereich durchführen

Schließen Sie vor der Bereinigung den Workspace und stellen Sie sicher, dass keine xcodebuild-, swift-frontend- oder SourceKit-Prozesse mehr auf das Zielverzeichnis zugreifen. Entfernen Sie im ersten Schritt ausschließlich den Index und den Modul-Cache:

DERIVED="$PWD/.derived-data"
rm -rf "$DERIVED/Index.noindex"
rm -rf "$DERIVED/ModuleCache.noindex"

Öffnen Sie das Projekt anschließend erneut, ohne Branch oder Abhängigkeiten zu ändern, und warten Sie, bis die erste Indexierung abgeschlossen ist. Tritt das Problem erneut auf, löschen Sie nur das separate DerivedData-Verzeichnis des aktuellen Projekts, nicht den gesamten Inhalt von ~/Library/Developer. Bleibt das Problem bestehen, kehren Sie zu den gesicherten Diagnosedaten zurück: Wird es nur von einem bestimmten Branch ausgelöst? Ändert sich eine generierte Datei fortlaufend? Ist ein Compiler-Plugin mit der aktuellen Xcode-Version inkompatibel?

Die Feststellung „Nach einem Neustart funktioniert es wieder“ ist keine belastbare Schlussfolgerung. Ein Neustart beendet lediglich die Prozesse. Er zeigt nicht, ob ein beschädigter Cache, ständig wechselnde Eingabedateien oder Unterschiede in der Toolchain die Ursache waren.

Reproduzierbare Abnahme-Checkliste erstellen

Prüfen Sie die Wiederherstellung mit demselben Commit, demselben Xcode-Pfad und derselben DerivedData-Strategie. Folgende Ergebnisse sollten dokumentiert werden:

  1. xcodebuild -version und xcode-select -p verweisen auf die erwartete Toolchain.
  2. Nach dem Öffnen des Workspace wird die Indexierung abgeschlossen; Codevervollständigung und Definitionssprünge funktionieren wieder.
  3. Nach dem Ende von Bearbeitung und Build sinkt die CPU-Auslastung von SourceKit.
  4. In den Logs erscheint nicht mehr wiederholt dieselbe Datei oder dasselbe Modul.
  5. Das Problem tritt auch nach mehrmaligem Schließen und erneuten Öffnen des Workspace nicht wieder auf.
  6. CI und interaktive Entwicklung verwenden unterschiedliche DerivedData-Verzeichnisse.

Auch bei Remote-Sitzungen auf NowMini sollte das Diagnoseverzeichnis zusammen mit den Build-Logs aufbewahrt werden. Erfassen Sie dabei jedoch weder Quellcodeinhalte noch Schlüssel oder unmaskierte Umgebungsvariablen. Das Ziel besteht nicht darin, die CPU-Auslastung sofort auf null zu senken, sondern nachzuweisen, dass die Eingaben für die Indexierung stabil sind, die Prozesse Fortschritte machen und die Bereinigung ausschließlich das aktuelle Projekt betrifft.

Häufig gestellte Fragen

Bedeutet eine hohe SourceKit-CPU-Last immer einen Indexierungsstau?

Nein. Nach dem ersten Öffnen, einem Xcode-Wechsel oder neuen Abhängigkeiten ist hohe Last normal. Ein Stau liegt eher vor, wenn dasselbe Modul lange wiederholt wird und die Editorfunktionen keinen Fortschritt zeigen.

Muss der gesamte DerivedData-Ordner gelöscht werden?

Normalerweise nicht. Beenden Sie Xcode und Builds und entfernen Sie nur Index.noindex sowie ModuleCache.noindex aus dem projektspezifischen Pfad. Erstellen Sie erst danach bei Bedarf dessen DerivedData neu.

Darf die Indexerzeugung in CI deaktiviert werden?

Ja. Für nicht interaktive Build- und Testjobs kann COMPILER_INDEX_STORE_ENABLE=NO gesetzt werden. In Entwicklungssitzungen sollte die Indexierung für Navigation und Vervollständigung aktiv bleiben.

NowMini M4

Die nächste Aufgabe auf einem exklusiven physischen Knoten ausführen

Cloud-Mac mit M4, 16 GB RAM und 256 GB SSD – wählen Sie je nach Aufgabenzeitraum einen Knoten in Singapur, Tokio, Seoul oder Hongkong.

Cloud-Mac jetzt mieten