GitHub Actions 캐싱처리 꼭 해야할까?

@JiHongKim98 · January 03, 2024 · 4 min read

Django 기준으로 구현한 코드는 GitHub에서 확인 가능합니다.

Cache를 사용해야하는 이유

GitHub Cache Action은 매 Workflow에서 build에 필요한 의존성 패키지 설치 작업을 Cache 처리해 build 속도를 향상시키기 위해 사용한다.

예를 들어, DRF를 통해 REST API를 구현하고 GitHub Action을 통해 CI를 구현했다고 했을 때 설정한 Event trigger 즉, push 혹은 pull request 이벤트가 발생할 때마다 Runner는 계속해서 requirements.txt에 작성된 의존성 패키지를 다운받아 build 완료까지 오랜 시간이 소모되고, 이로 인해 실제 test를 진행및 완료되기까지 오랜 시간이 걸리게 된다.

이를 개선하려면, 의존성 패키지 설치 작업을 캐싱처리하여 CI 테스트시 캐싱된 작업이 있는지 검사하고 있다면 해당 작업을 가져오고, 없을 때는 의존성 패키지 설치 작업을 진행 후 다시 캐싱 처리하는 방식을 사용하면 빠르게 build하고 테스트까지 진행이 가능하다.

한번 캐싱처리 전과 후의 시간 차이를 확인해보자.

Cache 하기전의 build및 test 완료까지 걸린 시간은 1분10초, 캐싱 처리 후의 소모 시간은 46초로 약 25초가량 CI 테스트 시간이 줄어들었다. (둘다 오래걸리진 않았지만 대략 52% 의 성능 속도 향상..!)

지금은 테스트를 위해서 간단하게 구현한 것이라 캐싱처리 전에도 1분대로 엄청 느리지는 않지만, 만약 프로젝트가 커지게 된다면 그에 따라 의존성 패키지도 많아지게 되어 build 시간이 더 커지게 될 것이다.

그럼 캐싱처리는 어떻게 구현 해야할까?

Cache 구현

먼저 Actions 탭에서 Django workflow를 선택했을 때 제공해주는 django.yml 파일에서 steps 부분을 살펴보자.

# .github/workflow/django.yml

jobs:
  # ...
  steps:
    - name: Check out repository code
      uses: actions/checkout@v3

    - name: Set up Python ${{ matrix.python-version }}
      id: setup_python
      uses: actions/setup-python@v3
      with:
        python-version: ${{ matrix.python-version }}

    - name: Install dependencies
      shell: bash
      run: |
        pip install --upgrade pip
        python -m pip install -r requirements.txt

    - name: Run Tests
      run: |
        python manage.py test

위 처럼 매 CI test가 진행될 때마다 requirements.txt 파일에 작성된 의존성 패키지들을 설치하고 test를 진행한다.

이제 우리가 고려해야할 부분은 "어디를 캐싱처리 할 것인가?" 이다.

나는 의존성 패키지를 가상환경에 설치하고 해당 가상환경 자체를 Cache에 저장해 Cache가 유효할 때까지 pip install 작업 즉, 의존성 패키지 설치 작업을 생략하도록 구현했다.

로직은 다음과 같다.

  • IF 캐싱된 가상환경이 있고, requirements.txt 파일이 변경되지 않았는가?
  • True:

    • 해당 가상환경을 가져오고 의존성 패키지 설치 작업 Skip.
  • False:

    • 가상환경 생성 및 의존성 패키지 설치 작업 Start.

위 로직을 구현하기 위해 첫번째로 캐싱된 가상환경이 있는지 확인하는 steps을 구현했다.

- name: cache virtualenv
  uses: actions/cache@v3
  id: cache-venv # Step 식별을 위한 고유 식별자
  with:
    path: ./.venv/
    key: |
      ${{ runner.os }}-${{ steps.setup_python.outputs.python-version }}-venv-${{ hashFiles('requirements.txt') }
    restore-keys: |
      ${{ runner.os }}-${{ steps.setup_python.outputs.python-version }}-venv-

Runner가 실행되는 환경에서 의존성 패키지들의 정보가 작성된 requirements.txt 이 변경되지 않을 경우에만 캐싱된 가상환경을 가져오기 위해 Cache Key 명을 "{실행환경}-{python-version}-venv-{해시값}" 으로 지정하였다.

따라서 requirements.txt 파일이 변경되지 않았고, Cache가 유지되는 기간동안에는 Cache에서 가상환경을 가져올 수 있다.

두번째로, 만약 Cache에 일치하는 Key가 없는 경우 의존성 패키지를 설치하고 일치하는 Key가 있는 경우 의존성 패키지 설치 작업을 건너뛰는 steps을 구현했다.

- name: Install dependencies
  shell: bash
  run: |
    python -m venv ./.venv
    source ./.venv/bin/activate
    pip install --upgrade pip
    python -m pip install -r requirements.txt
  if: steps.cache-venv.outputs.cache-hit != 'true'

첫번째 구현 로직의 고유 식별자(id)인 cache-venv 를 통해 if문으로 캐시를 가져오지 못했을 경우에만 가상환경내 의존성 패키지 설치 작업을 진행하도록 구현했다.

이로써, 불필요하게 계속해서 의존성 패키지를 설치하는 과정을 Skip 할 수 있다.

마지막으로, Cache에 저장된 가상환경이 없는 경우 가상환경에 진입 후 패키지를 설치하기 때문에 문제가 없지만, 만약 Cache에 저장된 가상환경이 있을 경우 가상환경 진입과정을 건너뛰게 되므로 test 시작 전에는 꼭 가상환경에 진입하도록 구현해야한다.

- name: Run Tests
  shell: bash
  run: |
    source ./.venv/bin/activate  # 가상환경 진입 필수!
    python manage.py test
  env:
  # ...

Logic Test

Cache 기능을 적용하고 다시 Action을 실행했을 때

위 사진과 같이 cache virtualenv 작업에서 캐시를 찾지 못했다는 문구가 뜨는데 이는, 지금 새로 Cache 기능을 적용한 뒤 처음 Action을 실행하는 것이라 찾지 못한 것이다.

따라서, 의존성 패키지 설치 작업을 마친 후, CI 테스트를 진행한다. 가상환경을 Cache내에 저장하는 것은 steps의 모든 작업을 완료한 뒤에 이뤄진다. (actions/cache@v3 의 작업)

한번, Actions의 Cache 탭을 확인해보자.

2개의 테스트 실행환경에서 가상환경을 캐싱하여 저장된 것을 볼 수 있다.

이제, 다시 push 이벤트를 발생시켜 저장된 Cache를 정상적으로 가져오는지 확인 해보자!

cache virtualenv 작업에서 캐시를 찾아서 Install dependencies 작업을 건너뛰고, CI 테스트를 진행하는 것을 볼 수 있다.

이로써, Cache 적용 후 처음 Actions 실행시 진행했던 의존성 패키지 설치 단계를 건너뛰어 약 20초의 시간을 단축할 수 있게 되었다.

참고할 점은 Cache의 유효기간과 최대 용량은 GitHub 무료티어 기준으로 유효기간 7일, 최대 용량 2GB이다.

Ref.

https://mckornfield.medium.com/caching-python-installs-in-github-actions-8309e12a15e6

@JiHongKim98
Hello :) This is JiHongKim98's tech blog