이 출시되었습니다! 11월의 새로운 기능 및 수정 사항을 읽어보세요.

CI 빌드 시간 개선

2020년 2월 18일, Ethan Dennis (@erdennis13) 및 João Moreno (@joaomoreno)

Visual Studio Code는 많은 구성 요소와 활발한 참여자를 보유한 대규모 프로젝트입니다. 저희는 빌드 및 지속적인 통합 인프라를 유지 관리하여 우수한 엔지니어링 관행을 따르기 위해 Azure Pipelines를 적극적으로 사용하고 있음을 보여 드린 바 있습니다. 이 블로그 게시물에서는 Azure Pipelines 아티팩트 캐싱 작업을 사용하여 CI 빌드 시간을 크게 단축한 방법을 설명하겠습니다.

이전 블로그 게시물에서 CI 빌드 시간을 33% 단축한 방법을 설명했습니다. 이는 VS Code에서 사용하는 노드 모듈을 빌드 시간에 패키지를 확인하는 대신 캐싱하는 사용자 지정 빌드 작업을 사용하여 달성되었습니다. 이 성능 향상에 만족했지만, 저희가 구축한 캐싱 작업으로 얼마나 더 개선할 수 있는지 확인하고 싶었습니다.

이전 CI 엔지니어링에 대해 이야기했을 때 타겟 플랫폼은 Windows, macOS 및 Linux였습니다. 오늘날 VS Code는 원격 서버 구성 요소에 대한 Arm64 및 Alpine Linux와 같이 훨씬 더 다양한 플랫폼을 대상으로 합니다. 총 8개의 서로 다른 타겟이 공통 빌드 단계를 공유합니다. 이 게시물에서는 캐싱 작업을 활용하여 CI 중복을 줄이고 빌드 시간을 더욱 개선한 방법을 설명합니다.

개선할 여지

그렇다면 모든 빌드 작업에서 정확히 어떤 공통 단계가 있었을까요? 각 빌드 타겟은 유사한 일련의 단계를 따르는 작업을 갖습니다. 매우 높은 수준에서 각 작업은 다음을 수행해야 합니다.

  1. 종속성 복원
  2. TypeScript 및 JavaScript 린팅
  3. TypeScript를 JavaScript로 컴파일
  4. 단위 테스트 스위트 실행
  5. 통합 테스트 스위트 실행
  6. VS Code 패키징

저희의 캐싱 작업은 **종속성 복원** 단계를 가속화하는 데 분명한 선택이었습니다. 예를 들어, `package-lock.json` 파일이 거의 변경되지 않으므로 이전 실행 결과를 캐싱할 수 있는데 왜 비용이 많이 드는 `npm install` 단계를 실행해야 할까요? 패키지 캐싱에 대해 이미 논의했으므로, 이 게시물이 흥미로운 이유는 다른 단계에 캐싱을 적용한 방법입니다.

린팅 및 컴파일은 플랫폼에 독립적이므로 해당 단계는 다른 플랫폼 종속 에이전트와 결과를 공유하는 단일 빌드 에이전트에서 쉽게 실행될 수 있으며, 모든 에이전트가 이 작업을 반복적으로 수행하는 대신에 가능합니다. 저희는 정확히 이 작업을 수행하는 Linux 빌드 에이전트를 만들었습니다. 패키지를 복원하고, 린팅하고, 소스 코드를 컴파일하는 것입니다. 저희가 해야 할 일은 다른 에이전트와 결과를 공유하는 것뿐이었습니다.

모든 것 캐싱

빌드 에이전트 간에 캐시 결과를 공유하려면 플랫폼 독립적인 캐시가 필요했으며, 이는 초기에 캐싱 작업에서 지원되지 않았습니다. 따라서 Azure Pipelines 아티팩트 캐싱 작업에 선택적 `platformIndependent` 매개변수가 추가되었습니다.

VS Code에서 `platformIndependent` 매개변수를 사용하는 방법

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

노드 모듈을 캐싱할 때 `package-lock.json` 파일을 캐시 키로 사용하는 것이 논리적입니다. 이 파일이 변경되면 캐시를 무효화해야 합니다. 컴파일 출력을 캐싱할 때 전체 코드베이스가 캐시 키 역할을 해야 합니다. 간단하게 하기 위해 HEAD 커밋을 캐시 키로 사용하기로 결정했습니다. 새 커밋은 필연적으로 새 캐시 항목을 생성할 것이기 때문입니다. 빌드 에이전트 전체에서 실행되더라도 단일 빌드는 항상 단일 커밋에 대해 실행되므로 이는 저희 목적에 잘 맞습니다.

또 다른 누락된 기능은 빌드 작업당 여러 캐시를 생성하는 기능이었습니다. 이제 저희는 두 개의 캐시(노드 모듈, 컴파일)를 다루어야 했고 각 캐시를 개별적으로 주소 지정할 방법이 없었습니다. 캐싱 작업은 `CacheRestored`라는 환경 변수를 출력하며, 이 변수를 사용하여 빌드 작업을 낙관적으로 건너뛸 수 있습니다. 이 환경 변수는 단일 캐시와 상호 작용하는 빌드에서는 잘 작동하지만, 여러 캐시에서는 그다지 좋지 않습니다. `CacheRestored`가 어떤 캐시를 참조하는지 궁금하게 만듭니다. 다시 말하지만, Azure Pipelines 아티팩트 캐싱 작업에 선택적 `alias` 매개변수가 추가되었습니다.

여기서 `alias` 매개변수를 사용하는 방법입니다.

- 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')

여기서 `Packages`라는 별칭이 환경 변수 출력에 추가되어 단일 빌드 작업에서 NPM 패키지 및 컴파일 출력을 캐싱할 수 있게 되었습니다. 마침내 CI 작업의 많은 부분을 중복 제거할 수 있게 되었고, 이제 한 번만 실행하고 플랫폼별 에이전트와 공유할 수 있게 되었습니다.

특정 사용 사례인 빌드 재제출의 경우 최종 최적화 여지가 여전히 있었습니다. 테스트가 불안정하거나 일부 에이전트가 무작위로 실패하는 경우 이전에 빌드된 커밋에 대해 VS Code 빌드를 다시 트리거해야 할 때가 있습니다. 이상적으로는 공유 에이전트가 일반 코드를 복원하거나 다시 컴파일하지 않고 플랫폼 종속 에이전트가 작업을 수행하도록 해야 합니다. 저희가 발견한 문제는 컴파일 캐시 패키지가 매우 커서 복원하는 데 약 8분이 걸린다는 것이었습니다. 모두 소용없는 일이었는데, 공유 에이전트는 해당 캐시가 존재하면 단순히 제어권을 양도할 것이기 때문입니다. 따라서 Azure Pipelines 아티팩트 캐싱 작업에 새로운 선택적 `dryRun` 매개변수가 다시 추가되었습니다. 이를 통해 복원하지 않고도 캐시 패키지의 존재 여부를 확인할 수 있어 빌드 재제출에서 8분이 절약됩니다.

빌드에서 `dryRun` 매개변수를 사용하는 방법은 다음과 같습니다.

- 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')

이것이 `dryRun` 매개변수와 함께 작동하는 새로운 `CacheExists` 변수를 도입했음을 알 수 있습니다.

결과

이러한 변경 사항이 구현된 후 총 빌드 시간에 획기적인 감소를 보였습니다. 다음 표는 VS Code가 타겟팅하는 각 플랫폼의 총 빌드 시간 변화를 보여줍니다.

플랫폼 이전 이후 시간 절약
Windows 58분 44분 24%
Windows 32 59분 46분 22%
Linux 38분 23분 39%
macOS 68분 42분 38%
Linux Arm 22분 21분 5%
Linux Alpine 23분 26분 -13%

VS Code before and after build times

Linux Arm 및 Linux Alpine 타겟은 VS Code 원격 서버 구성 요소만 빌드하므로 원래 빌드 시간이 충분히 좋았습니다. 그러나 표준 VS Code 클라이언트 플랫폼과 일부 공통 작업을 공유하기 때문에 공통 빌드 에이전트에 의존하도록 결정했습니다. 이로 인해 한 경우에 오버헤드가 증가하여 빌드 시간이 약간 늘어났습니다.

공유 에이전트 작업은 완전히 건너뛸 수 있으므로 빌드 재제출에서 획기적인 개선을 보았습니다. 예를 들어 macOS의 수치입니다.

플랫폼 이전 이후 시간 절약
macOS 68초 34초 50%

총체적으로, VS Code의 CI 빌드 시간을 약 50% 단축하는 것을 보고 매우 기뻤습니다! 가장 좋은 소식은 저희 빌드 정의에서 영감을 얻어 자체 빌드 시간 개선을 실현할 수 있다는 것입니다.

행복한 캐싱,

Ethan Dennis, Developer Services 수석 소프트웨어 엔지니어 @erdennis13

João Moreno, VS Code 수석 소프트웨어 엔지니어 @joaomoreno

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