반복되는 작업 캐싱이 필요한 이유
CI/CD 작업을 하면 계속해서 반복되는 작업들이 있다. 만약 이런 작업들이 반복된다면 리소스가 낭비되며, 요금 청구가 늘어날 수 있다. 특히 GitHub Action의 경우 무료 사용자에게는 2000분의 시간만을 무료로 제공해주고 Pro 사용자에게는 3000분만을 무료로 제공해주기 때문에 리소스 사용을 아껴야 할 필요가 있다.
이러한 반복되는 작업들은 GitHub Actions에서 제공해주는 cache Action을 통해 해결이 가능하다. cache Action은 GitHub에서 공식적으로 지원하는 캐싱 방식이다.
참고 : https://github.com/actions/cache
cache Action이란?
cache Action은 특정한 path의 작업물들을 특정한 key 값을 통해 캐싱 해놓고 만약 같은 key가 사용된다면 캐싱된 파일을 꺼내와 사용할 수 있도록 만들어 놓은 Action이다. 따라서 cache Action의 대표적인 파라미터는 key와 path 두가지이다.
- key: 캐싱된 파일을 구분하는 Key
- path: 어떤 경로의 파일들이 모두 저장되고 복구될 것인지를 지정
Step은 다음과 같이 만든다.
- name: Cache Action
uses: actions/cache@v3
with:
path: [path]
key: [key]
Gradle 파일 캐싱하기
보통 안드로이드나 스프링에서는에서는 빌드에 gradle 파일을 사용하기 때문에 gradle을 캐싱한다. 경로는 ~/.gradle/caches 와 ~/.gradle/wrapper 로 잡으면 된다.
해당 경로의 파일들은 실행 os가 변경되었을 때 다시 설정되어야 하므로 runner.os 를 key에 추가한다. 또한 .gradle 파일 혹은 gradle-wrapper.properties 파일이 변경되었을 때도 ~/.gradle/caches 와 ~/.gradle/wrapper 경로의 파일들이 다시 설정되어야 하기 때문에 hashFiles 메서드들 통해 해당 파일들(.gradle, gradle-wrapper.properties)로 해시값을 생성해 key로 만든다.
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
그러면 .gradle 파일이 추가, 삭제, 변경되거나 gradle-wrapper.properties 파일이 추가, 삭제, 변경되거나 가상 머신의 os 가 변경되었을 때 gradle sync를 다시 하게 된다.
cache Action 사용해 성능 비교해보기
자 이제 cache를 사용할 때와 하지 않을 때 빌드 속도 차이를 비교해보자.
아래는 이전 글에서 사용했던 안드로이드 테스트 후 빌드해 Job Artifact를 만드는 Workflow 파일이다.
name: Job Artifacts - Make Android APK
on:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out Repository
uses: actions/checkout@v3
- name: set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
cache: gradle
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Test with Gradle
run: ./gradlew test
build:
needs: test
runs-on: ubuntu-latest
steps:
- name: Check out Repository
uses: actions/checkout@v3
- name: set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
cache: gradle
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew assembleRelease
- name: Upload Apk
uses: actions/upload-artifact@v3
with:
name: android-artifact
path: app/build/outputs/apk/release
우리는 여기에 캐싱을 하는 Action을 중간에 추가할 것이다.
name: Job Artifacts - Make Android APK - Cached
on:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out Repository
uses: actions/checkout@v3
# 캐싱 추가
- name: Cache Gradle
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
cache: gradle
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Test with Gradle
run: ./gradlew test
build:
needs: test
runs-on: ubuntu-latest
steps:
- name: Check out Repository
uses: actions/checkout@v3
- name: set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
cache: gradle
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew assembleRelease
- name: Upload Apk
uses: actions/upload-artifact@v3
with:
name: android-artifact
path: app/build/outputs/apk/release
이를 실행해보면 Action에 Gradle 이 성공적으로 캐싱 되었다는게 나온다.
성능 비교해보기
이전에 캐싱을 하지 않았을 때 build 단계에서 Build With Gradle Step이 걸린 시간은 1분 18초였다.
Gradle을 캐싱 하면 그 시간이 47초로 줄어든 것을 확인할 수 있다.
30초 정도가 줄어들었다. 여기서는 간단한 Gradle을 사용해서 성능 차이가 엄청나지는 않고, Gradle을 사용하는 Job이 두개 뿐이라서 많은 효과는 못본다. 하지만 만약 Gradle 이 복잡해져서 빌드에 시간이 15분씩 걸린다면? Job이 3개, 4개가 된다면 효과는 엄청나다. 만약 위 작업으로 1번 실행할 때마다 10분을 줄일 수 있고, 만약 하루 평균 100번씩 돌아간다고 하면 리눅스 서버 기준 하루에 8~256달러 정도의 비용을 아낄 수 있다.
다른 cache Action 스크립트 확인해보기
다른 언어와 시스템에 대한 스크립트들도 아래에서 확인할 수 있다. 한국에서 자주 사용되는 방식 위주로 종류를 선정했다. 만약 다른 언어와 빌드 시스템에 대한 캐싱이 필요하다면 문서를 확인해보도록 하자.
- C# - NuGet
- Go - Modules
- Java - Gradle
- Java - Maven
- Node - npm
- Node - Lerna
- Node - Yarn
- Python - pip
- Python - pipenv
- Ruby - Bundler
- Rust - Cargo
- Swift, Objective-C - Carthage
- Swift, Objective-C - CocoaPods
- Swift - Swift Package Manager
자 이제 GitHub Action에 cache를 도입해 최적화를 해보도록 하자.