ist jetzt verfügbar! Lesen Sie über die neuen Funktionen und Fehlerbehebungen vom November.

Verbesserung der CI-Build-Zeiten

18. Februar 2020 von Ethan Dennis, @erdennis13 und João Moreno, @joaomoreno

Visual Studio Code ist ein großes Projekt mit vielen beweglichen Teilen und einer aktiven Teilnehmerliste. Wir haben gezeigt, wie wir Azure Pipelines aktiv nutzen, um gute Ingenieurspraktiken aufrechtzuerhalten, indem wir unsere Build- und Continuous-Integration-Infrastruktur pflegen. In diesem Blogbeitrag werden wir darüber sprechen, wie wir die Azure Pipelines Artifact Caching Tasks genutzt haben, um unsere CI-Build-Zeiten drastisch zu reduzieren.

In einem früheren Blogbeitrag haben wir beschrieben, wie wir die CI-Build-Zeiten um 33 % reduziert haben. Dies wurde durch die Verwendung benutzerdefinierter Build-Aufgaben erreicht, die die von VS Code verwendeten Node-Module zwischenspeichern, anstatt die Pakete zur Build-Zeit aufzulösen. Obwohl wir mit dieser Leistungssteigerung zufrieden waren, wollten wir sehen, wie weit wir die von uns entwickelten Caching-Aufgaben noch treiben konnten.

Als wir das letzte Mal über unser CI-Engineering sprachen, umfassten unsere Zielplattformen Windows, macOS und Linux. Heute zielt VS Code auf eine viel breitere Palette von Plattformen ab, wie z. B. Arm64 und Alpine Linux für seine Remote-Serverkomponenten. Insgesamt haben wir acht verschiedene Ziele, die alle gemeinsame Build-Schritte haben. Dieser Beitrag beschreibt, wie wir die Caching-Aufgaben genutzt haben, um CI-Duplikation zu reduzieren und unsere Build-Zeiten weiter zu verbessern.

Raum für Verbesserungen

Was genau waren also die gemeinsamen Schritte für alle Build-Jobs? Jedes Build-Ziel hat einen Job, der eine ähnliche Reihe von Schritten durchläuft. Auf einer sehr hohen Ebene muss jeder Job

  1. Abhängigkeiten wiederherstellen
  2. TypeScript und JavaScript linten
  3. TypeScript nach JavaScript kompilieren
  4. Unit-Test-Suiten ausführen
  5. Integrationstest-Suiten ausführen
  6. VS Code paketieren

Unsere Caching-Aufgaben waren die offensichtliche Wahl, um den Schritt **Abhängigkeiten wiederherstellen** zu beschleunigen. Warum zum Beispiel einen teuren `npm install`-Schritt ausführen, wenn man die Ergebnisse eines früheren Laufs cachen könnte, da die `package-lock.json`-Datei selten geändert wird? Da wir das Caching von Paketen bereits besprochen haben, ist das Interessante an diesem Beitrag, wie wir das Caching auf die anderen Schritte angewendet haben.

Da Linting und Kompilierung plattformunabhängig sind, könnten diese Schritte problemlos von einem einzigen Build-Agenten ausgeführt werden, der seine Ergebnisse mit anderen plattformabhängigen Agenten teilt, anstatt dass alle Agenten diese Arbeit wiederholt ausführen. Wir haben einen Linux-Build-Agenten erstellt, dessen alleinige Aufgabe genau darin bestand: Pakete wiederherstellen, linten und den Quellcode kompilieren. Alles, was wir tun mussten, war, die Ergebnisse mit anderen Agenten zu teilen.

Alles cachen

Um Cache-Ergebnisse zwischen Build-Agenten zu teilen, benötigten wir plattformunabhängige Caches, die von den Caching-Aufgaben anfangs nicht unterstützt wurden. Daher wurde ein optionaler Parameter `platformIndependent` zu den Azure Pipelines Artifact Caching Tasks hinzugefügt.

Hier ist, wie VS Code den Parameter `platformIndependent` verwendet

- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
  inputs:
    keyfile: keyfile
    targetfolder: target
    vstsFeed: $(ArtifactFeed)
    platformIndependent: true

Beim Caching von Node-Modulen ist es logisch, `package-lock.json`-Dateien als Cache-Schlüssel zu verwenden. Wenn sich diese Datei ändert, müssen wir den Cache invalidieren. Beim Caching von Kompilierungsausgaben muss die gesamte Codebasis als Cache-Schlüssel dienen. Um die Dinge zu vereinfachen, haben wir uns entschieden, den HEAD-Commit als Cache-Schlüssel zu verwenden, da ein neuer Commit zwangsläufig einen neuen Cache-Eintrag erzeugt. Dies funktioniert für unsere Zwecke gut, da ein einzelner Build, obwohl er über Build-Agenten läuft, immer über einen einzelnen Commit läuft.

Eine weitere fehlende Funktion war die Möglichkeit, mehrere Caches pro Build-Job zu erstellen. Wir jonglierten nun mit zwei Caches (Node-Module, Kompilierung), ohne die Möglichkeit, jeden Cache individuell anzusprechen. Die Caching-Aufgaben geben eine Umgebungsvariable namens `CacheRestored` aus, die verwendet werden kann, um Build-Aufgaben optimistisch zu überspringen. Diese Umgebungsvariable funktioniert großartig bei Builds, die mit einem einzelnen Cache interagieren, aber nicht so gut bei mehreren Caches - wir fragten uns, auf welchen Cache `CacheRestored` sich bezieht. Wieder einmal wurde ein weiterer optionaler `alias`-Parameter zu den Azure Pipelines Artifact Caching Tasks hinzugefügt.

Und hier ist, wie wir den Parameter `alias` verwenden

- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
  inputs:
    keyfile: "yarn.lock"
    targetfolder: "node_modules"
    vstsFeed: "$(ArtifactFeed)"
    alias: "Packages"

- script: |
    yarn install
  displayName: Install Dependencies
  condition: ne(variables['CacheRestored-Packages'], 'true')

Hier wird ein Alias von `Packages` an die Umgebungsvariablenausgabe angehängt, was es uns ermöglicht, NPM-Pakete und Kompilierungsausgaben in einem einzigen Build-Job zu cachen. Wir haben endlich viel von der CI-Arbeit dedupliziert, die nun nur einmal ausgeführt und zwischen plattformspezifischen Agenten geteilt werden konnte.

Es gab noch Raum für eine letzte Optimierung, angesichts eines spezifischen Anwendungsfalls: Build-Wiederholungen. Wir müssen manchmal VS Code-Builds auf zuvor erstellten Commits erneut auslösen, da Tests flüchtig sein können oder einige Agenten zufällig fehlschlagen. Idealerweise würde der gemeinsam genutzte Agent den gemeinsamen Code nicht wiederherstellen oder neu kompilieren, sondern die plattformabhängigen Agenten ihre Arbeit erledigen lassen. Das Problem, das wir bemerkten, war, dass die Kompilierungs-Cache-Pakete riesig waren und deren Wiederherstellung etwa 8 Minuten dauern würde – alles umsonst, da der gemeinsam genutzte Agent einfach die Kontrolle abgeben würde, wenn dieser Cache existierte. Daher wurde erneut ein neuer optionaler `dryRun`-Parameter zu den Azure Pipelines Artifact Caching Tasks hinzugefügt, der es uns ermöglicht, die Existenz eines Cache-Pakets zu überprüfen, ohne es wiederherzustellen – was unsere Build-Wiederholungen effektiv um 8 Minuten verkürzt.

Die Verwendung des Parameters `dryRun` in unserem Build sieht so aus

- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
  inputs:
    keyfile: commit
    targetfolder: output
    vstsFeed: "$(ArtifactFeed)"
    dryRun: true

- script: |
    npm run compile install
  displayName: Install Dependencies
  condition: ne(variables['CacheExists'], 'true')

Beachten Sie, dass dies auch eine neue Variable `CacheExists` eingeführt hat, die zusammen mit dem Parameter `dryRun` funktioniert.

Ergebnisse

Nachdem diese Änderungen implementiert waren, sahen wir drastische Reduzierungen der gesamten Build-Zeit. Die folgende Tabelle zeigt die Änderung der gesamten Build-Zeiten für jede Plattform, die VS Code unterstützt.

Plattform Vorher Nachher Zeitersparnis
Windows 58 Min. 44 Min. 24%
Windows 32 59 Min. 46 Min. 22%
Linux 38 Min. 23 Min. 39%
macOS 68 Min. 42 Min. 38%
Linux Arm 22 Min. 21 Min. 5%
Linux Alpine 23 Min. 26 Min. -13%

VS Code before and after build times

Die Ziele Linux Arm und Linux Alpine erstellen nur die VS Code Remote-Serverkomponenten, daher waren ihre ursprünglichen Build-Zeiten gut genug. Da sie jedoch einige gemeinsame Aufgaben mit den Standard-VS-Code-Client-Plattformen teilen, haben wir beschlossen, sie vom gemeinsamen Build-Agenten abhängig zu machen. Dies führte in einem Fall zu leicht erhöhten Build-Zeiten aufgrund des erhöhten Overheads.

Build-Wiederholungen zeigten eine drastische Verbesserung, da die gemeinsam genutzten Agenten-Aufgaben ganz übersprungen werden können. Hier sind zum Beispiel einige Zahlen für macOS

Plattform Vorher Nachher Zeitersparnis
macOS 68s 34s 50%

Insgesamt waren wir begeistert, eine kombinierte Reduzierung der CI-Build-Zeiten von VS Code um etwa 50 % zu sehen! Die beste Nachricht ist, dass Sie sich von unseren Build-Definitionen inspirieren lassen können, um auch selbst Verbesserungen bei den Build-Zeiten zu erzielen.

Viel Spaß beim Caching,

Ethan Dennis, Developer Services Senior Software Engineer @erdennis13

João Moreno, VS Code Senior Software Engineer @joaomoreno

© . This site is unofficial and not affiliated with Microsoft.