
며칠 전에는 Front쪽 Build CI의 속도를 개선해봤습니다.
해당 글을 참조 해주시면 됩니다.
오늘은 CD쪽의 속도를 개선해보겠습니다.
npm의 node_modules 캐싱하기workflow의 로그를 살펴보니, 며칠전에 개선한 npm의 의존성 캐싱이 적용이 되어있지 않았습니다.
그래서 제일 먼저, npm의 node_modules에 캐싱을 적용 해주겠습니다.
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
... 생략 # Set up JDK 17, # Grant execute permission for gradlew
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ec92e829475ac0c2315ea8f9eced72db85bb337a # v3.0.0
- name: Build with Gradle Wrapper
run: ./gradlew build
working-directory: ./my-garden-be
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
... 생략 # Set up JDK 17, # Grant execute permission for gradlew
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ec92e829475ac0c2315ea8f9eced72db85bb337a # v3.0.0
# 캐시 추가
- name: Check node modules cache
id: cache
uses: actions/cache@v4
with:
path: '**/node_modules'
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
# 캐시가 존재하므로, npmInstall 과정을 생략
- name: Build with Gradle Wrapper (skip npm install)
if: steps.cache.outputs.cache-hit == 'true'
run: ./gradlew build -x npmInstall
working-directory: ./my-garden-be
# 캐시가 존재하지않으므로, npmInstall 진행
- name: Build with Gradle Wrapper (npm install)
if: steps.cache.outputs.cache-hit != 'true'
run: ./gradlew build
working-directory: ./my-garden-be
이전 글에서 설명드렸던 내용과 거의 비슷합니다.
node_modules를 찾아서 캐싱을 합니다. (캐시 키는 package-lock.json)node_modules를 load하고 cache-hit을 true로 설정 // 키가 없으면, cache-hit을 false로 설정cache-hit에 따라서 npmInstall을 생략하거나 포함해서 gradle build가 진행됩니다.캐싱하기 전에는 보시는 것처럼 vite Build 전에, 무조건 npmInstall이 진행이 되었고 약 10초 정도의 시간이 소모됐습니다.

저장된 캐시가 없다면 캐시를 저장하는 과정에서 시간이 약 6초 정도 더 걸리긴 하지만,
한번 캐싱이 되면 보시는 것처럼 약 10초 정도 되던 npmInstall 과정이 생략됩니다.

캐시가 없을 때, 새로
save하는step(약 6초소요)
캐시가 존재할 때,
load하는step(약 2초소요)
캐시 load시에 약 2초 정도 걸리긴 하지만,
npmInstall을 skip함으로써 약 10초의 시간을 아꼈기 때문에 대략 약 7초 ~ 8초의 시간을 절약할 수 있었습니다.
Docker 캐시 삭제하기CD workflow에서 docker image를 빌드하는 순서는 먼저 docker buildx를 설치하고, 해당 buildx를 이용하여 docker image를 build 합니다.

여기서 Build and push를 살펴보면, 조금 특이한 점이 있습니다.
로그를 살펴보면서 설명을 드리겠습니다.
아래의 이미지는 Build and push의 로그입니다.

1번 내용이 docker image를 build하는 부분이고,
2번은 해당 image를 캐싱하기 위해 준비하고 추출하는 과정입니다.
image를 build 하는 시간 (3.3 + 1.0 = 4.3s) 보다, 캐싱을 하는 시간 (5.2s)이 더 긴 것을 보실 수 있습니다.
제 프로젝트에서는 완성된 이미지를 캐싱해도, 다시 재사용할 일이 없기 때문에 캐싱을 사용하지 않는 것이 시간 절약에 더 좋다는 판단을 내렸습니다.
그래서 해당 캐싱을 제거하여 시간을 조금이라도 단축시켜보겠습니다.
완성된 이미지를 재사용 하지 않는 이유에 대해서도 설명을 드리겠습니다.
현재 제 프로젝트는
docker image를 사용하여CD를 진행하고 있으며, 해당docker image는jdk17-alpine위에jar파일을 복사하여 실행시키는 구조입니다.
(※jar파일은GitHub Action Runner Instance가docker image를 만들기 전step에서gradle로build를 합니다.)매번
CD를 진행할 때마다 새로jar를build를 해서 배포하기 때문에, 캐싱된 데이터가 재사용 되지 않습니다.
# docker buildx 설치
- name: Set up docker buildx
uses: docker/setup-buildx-action@v3
... 생략 # GitHub Container Registry에 로그인
# Docker 빌드 & GitHub Container Registry에 푸시
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ env.DOCKER_IMAGE }}:latest
cache-from: type=gha
cache-to: type=gha, mode=min
# docker buildx 설치
- name: Set up docker buildx
uses: docker/setup-buildx-action@v3
... 생략 # GitHub Container Registry에 로그인
# Docker 빌드 & GitHub Container Registry에 푸시
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ env.DOCKER_IMAGE }}:latest
build-push-action 액션의 옵션인 cache-from와 cache-to를 제거하여, 캐시를 사용하지 않도록 했습니다.
캐시를 추출하던 과정이 사라진 것을 보실 수 있습니다.

캐시를 제거함으로써 캐시를 추출하던 시간을 아꼈기 때문에 대략 약 5초의 시간을 절약할 수 있었습니다.

속도를 개선하기 전과 개선을 완료한 후를 비교하면, 1분 40초 → 1분 25초 (약 15%의 성능 향상)
docker image를 build하는 job 기준 (deploy 하는 시간은 고정적이므로 제외)
| 상황 | npm 의존성 캐싱 X | npm 의존성 캐싱 O |
|---|---|---|
| 도커 캐싱 O | 1분 40초 | 1분 31초 |
| 도커 캐싱 X | 측정 안함 | 1분 25초 |