레넌의 개발 일기

Github Actions - Gradle 캐싱을 통한 CI 속도 향상 본문

DevOps

Github Actions - Gradle 캐싱을 통한 CI 속도 향상

brorae 2022. 11. 10. 19:59

CI 속도를 개선해야하는 이유

CI는 지속적 통합이라는 뜻으로, 개발을 진행하면서도 품질을 관리할 수 있도록 여러명이 하나의 코드에 대해서 수정을 진행해도 지속적으로 통합하면서 관리할 수 있음을 의미한다. 

지속적으로 빌드와 테스트를 돌림으로써 통합되는 코드에 대해 문제가 없는지 확인하는 것이 CI의 중요한 요소 중 하나라고 생각한다. 따라서 빌드와 테스트에 대한 피드백 시간 또한 중요하다. 빠르게 피드백을 받게 된다면 불과 몇 초일지라도 개발자 조금이나마 편하게 개발을 진행할 수 있을 것이다.

 

기존의 Workflow

처음 공식 프로젝트에서 적용한 workflow 는 아래와 같다.

name: gongseek backend CI

# Workflow를 실행시키기 위한 이벤트 목록 
# 모든 브랜치에 대한 PR이 열릴 때 마다
# backend 하위 패키지에 변경이 일어날 때만 작동
on:
  pull_request:
    branches:
      - '*'
    paths:
      - backend/**

jobs:
  # job의 이름
  build:
  	
    # 실행될 환경
    runs-on: ubuntu-22.04
	
    # 작업을 수행하기 위한 기본 경로
    defaults:
      run:
        working-directory: ./backend

    steps:
      # 소스코드로 체크아웃
      - uses: actions/checkout@v2
	  # 서버에 자바 버전 설치
      - name: Set up JDK 11
        uses: actions/setup-java@v1
        with:
          java-version: 11

	  # Gradle 권한 변경
      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew

	  # 빌드 진행
      - name: Test with Gradle
        run: ./gradlew build

	  # 테스트 결과를 커멘트로 등록
      - name: Register test results as comments in PR
        uses: EnricoMi/publish-unit-test-result-action@v1
        if: always()
        with:
          files: '**/build/test-results/test/TEST-*.xml'

	  # 테스트 실패 시, 코드라인에 체크 커멘트 등록
      - name: If the test fails, register a Check comment in the failed code line
        uses: mikepenz/action-junit-report@v3
        if: failure()
        with:
          report_paths: '**/build/test-results/test/TEST-*.xml'
          token: ${{ github.token }}

	  # 빌드 실패 시, 슬랙으로 알람
      - name: If build fails, notify Slack
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          author_name: 백엔드 빌드 실패 알림
          fields: repo, message, commit, author, action, eventName, ref, workflow, job, took
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
        if: failure()

 

Step 확인하기

각각의 Step들의 소요 시간을 확인해보았다. 예상대로 Gradle로 빌드/테스트를 돌리는 부분이 가장 많은 시간을 소요하고 있었다.

Test with Gradle Step을 확인해보니 매번 gradle을 다운받는 것을 확인할 수 있었다. 이는 Github Actions 특성상 매번 workflow를 새로운 가상 서버에서 실행하기 때문이었다. 이를 해결하기 위해 Gradle을 캐싱을 적용하였다.

 

캐싱 적용하기

name: gongseek backend CI

# Workflow를 실행시키기 위한 이벤트 목록 
# 모든 브랜치에 대한 PR이 열릴 때 마다
# backend 하위 패키지에 변경이 일어날 때만 작동
on:
  pull_request:
    branches:
      - '*'
    paths:
      - backend/**

jobs:
  # job의 이름
  build:
  	
    # 실행될 환경
    runs-on: ubuntu-22.04
	
    # 작업을 수행하기 위한 기본 경로
    defaults:
      run:
        working-directory: ./backend

    steps:
      # 소스코드로 체크아웃
      - uses: actions/checkout@v2
	  # 서버에 자바 버전 설치
      - name: Set up JDK 11
        uses: actions/setup-java@v1
        with:
          java-version: 11
          
      # Gradle 캐싱
      - name: Gradle Caching
        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 권한 변경
      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew

	  # 빌드 진행
      - name: Test with Gradle
        run: ./gradlew build

	  # 테스트 결과를 커멘트로 등록
      - name: Register test results as comments in PR
        uses: EnricoMi/publish-unit-test-result-action@v1
        if: always()
        with:
          files: '**/build/test-results/test/TEST-*.xml'

	  # 테스트 실패 시, 코드라인에 체크 커멘트 등록
      - name: If the test fails, register a Check comment in the failed code line
        uses: mikepenz/action-junit-report@v3
        if: failure()
        with:
          report_paths: '**/build/test-results/test/TEST-*.xml'
          token: ${{ github.token }}

	  # 빌드 실패 시, 슬랙으로 알람
      - name: If build fails, notify Slack
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          author_name: 백엔드 빌드 실패 알림
          fields: repo, message, commit, author, action, eventName, ref, workflow, job, took
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
        if: failure()

캐싱을 적용한 코드는 위와 같다. 너무 길어서 잘 안보일 수 있으니 아래에서 자세히 살펴보도록 하자.

# Gradle 캐싱
- name: Gradle Caching
uses: actions/cache@v3
with:
  path: |
    ~/.gradle/caches
    ~/.gradle/wrapper
  key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
  restore-keys: |
    ${{ runner.os }}-gradle-
  • path : 캐시의 복원에 사용되는 runner 내 파일 경로다.
  • key : 캐시를 저장, 복원에 사용되는 키. 여러 값들을 조합해서 512자 제한으로 생성할 수 있다.
  • restore-keys : 내가 설정한 key로 cache miss가 발생할때 사용할 수 있는 후보군 키들이다.

Github 레포지토리의 Actions에 접근해보면 다음과 같이 Gradle 캐시가 되어있는 것을 확인할 수 있다.

중요한 점은 PR마다 Gradle 캐싱이 발생한다는 것이다. 따라서 PR을 올린 첫 순간에는 Gradle 캐시를 불러올 수 없다. 하지만 해당 PR의 코드리뷰를 통한 수정사항에 대해서는 캐싱을 통해 Gradle을 불러올 수 있다.

Test with Gradle Step의 수행 속도만 확인해 보면 대략 30초 정도 성능향상을 이루어낼 수 있었다.

 

 

참고

https://devjem.tistory.com/76

https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows