
저는 요즘에도 혼자서 MyGarden 프로젝트를 계속해서 업데이트 하고 있습니다.
코드를 업데이트 하면서 매번 느끼는 점은 CI 및 CD는 정말 귀찮은 일이라는 것입니다.
그래서 저는 MyGarden 프로젝트에 GitHub Actions랑 Docker를 활용해서 CI 및 CD를 도입했습니다.
제 프로젝트에
CI및CD를 도입한 관련 글은 [Vue + Spring] Github Actions를 이용한 CI/CD 구축하기 (+ Jacoco PR Comment 기능)를 봐주시면 됩니다.😊
CI및CD를 도입하고 나서는 코드 수정만 하면Test,Build모두 알아서 진행이 되니, 생산성이 많이 올라갔습니다.※ 개인 프로젝트라서 아직 도입을 안 하셨더라도, 꼭 도입해보시기를 추천드립니다!!
CI/CD를 도입하고 나서는, PR을 머지하고 배포가 완료될 때까지 기다리기만 하면 됩니다.
근데 가끔씩 지금 내가 수정한 기능이 정상적으로 동작 하는지를, 직접 눈으로 결과를 보고 싶을 때가 있습니다. 그럴 때는 어쩔 수 없이 모니터 앞에서 CI/CD가 끝날 때까지 기다려야 했습니다.
이를 기다리는 시간은 정말 지루하고 아깝습니다.
그래서 이 시간을 지금보다 더 줄여보기로 했습니다.
시간을 줄이기 위해서 GitHub Actions의 Workflow들을 살펴봤습니다.
Workflow에 대해서 살펴보기 전에, 현재 프로젝트에 적용중인CI/CD에 관해서 간단히 설명드리겠습니다.
CI
Front(vue)쪽 코드를 수정했을 때,node를 사용하여npm run build가 정상적으로 되는지 확인합니다.Back(spring)쪽 코드를 수정했을 떄,gradle을 통해Test및Build가 정상적으로 되는지 확인합니다.
CD
PR이 정상적으로master 브랜치에 머지가 되면, 다음의 로직을 따라서배포가 됩니다.
gradle을 통해BuildDocker로 이미지를 만든 후ghcr.io (Github Action Container Registry)에PushGitHub Actions Runner를 통해AWS EC2에Docker이미지를Pull하고 실행
제일 먼저 CI에서 Front쪽 코드를 수정했을 때, 진행되는 Workflow를 점검 했습니다.
해당 전체 과정이 총 17초 정도 걸리는 것을 볼 수 있으며, Install Dependencies에서 무려 7초나 걸린다는 사실을 알 수 있습니다.

Install Dependencies를 해결하면, 적어도 약30%이상의 성능 향상이 있을 것 같습니다.
아무래도 Front쪽 Dependencies는 자주 변경이 되지 않을 것 같아서, 캐시를 활용하면 좋을 것 같다는 생각이 들었습니다.
GitHub Actions의 npm cache 관련해서 검색을 하다보니, actions/cache를 알게 되었습니다.
GitHub Actions에서는 쉽게 cache를 사용할 수 있도록 cache관련 action이 있습니다. (actions/cache GitHub)
매번 Install Dependencies 하는 것 대신에, 의존성의 변경이 있을 때만 딱 1번 의존성을 설치하고 해당 의존성을 캐싱해두면 문제를 해결할 수 있을 것 같습니다.
(※의존성을 캐싱하기 위해서는 node_modules폴더를 캐싱하면 되겠습니다.)
사용 방법은 간단했습니다.
해당 step만 추가해주면 됩니다.
주석으로 설명을 달아뒀습니다.
- name: Cache dependencies
id: cache
uses: actions/cache@v4
with:
# 캐싱할 대상을 정합니다. (대상의 path를 지정)
# 여기서는 npm에서 의존성이 설치되는 디렉터리인 node_modules를 지정
path: '**/node_modules'
# 캐싱의 기준은 key를 기준으로 합니다.
# 의존성이 변경되면, 함께 변경되는 파일인 package-lock.json을 key로 잡아줍니다.
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
# key가 유효하지 않은 경우 runner의 운영체제 값과 node라는 suffix를 key로 복구합니다.
# 결과적으로 package-lock.json이 변경되지 않았다면 캐싱된 node_modules를 사용합니다.
# 만약 복구될 캐시가 없다면 아래에서 사용할 cache-hit는 false가 됩니다.
restore-keys: |
${{ runner.os }}-node-
cache action을 추가하고 나서는, 다음 step에서 cache hit를 기준으로 Install Dependencies를 할지 말지 결정하면 됩니다.
- name: Install Dependencies
# cache-hit이 true가 아니면 의존성을 설치합니다.
if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
- name: Build with npm
run: npm run build
해당 workflow가 정상적으로 동작하게 된다면, Install Dependencies를 매번 하지 않아도 되기 때문에 큰 성능 향상이 있을 것 같습니다.
name: Vue build test
on:
pull_request:
branches:
- master
paths:
- my-garden-fe/** # 해당 경로의 파일이 수정되었을 때만, 해당 workflow 실행
env:
NODE_VERSION: 20
jobs:
front-build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./my-garden-fe # npm을 실행할 기본 경로 지정
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm # npm 캐싱
cache-dependency-path: my-garden-fe/package-lock.json
- name: Install Dependencies
run: npm install
- name: Build with npm
run: npm run build
name: Vue build test
on:
pull_request:
branches:
- master
paths:
- my-garden-fe/** # 해당 경로의 파일이 수정되었을 때만, 해당 workflow 실행
env:
NODE_VERSION: 20
jobs:
front-build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./my-garden-fe # npm을 실행할 기본 경로 지정
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Cache node modules
id: cache
uses: actions/cache@v4
with:
path: '**/node_modules'
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
- name: Build with npm
run: npm run build
setup-node action의 cache 기능 제거# 변경 전
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm # npm 캐싱
cache-dependency-path: my-garden-fe/package-lock.json
# ========================================================================
# 변경 후
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
setup-node에서 cache 기능을 추가하게 되면, /home/runner/.npm를 캐싱합니다. (.npm 안에는 패키지 캐시와 관련된 정보만을 담고 있습니다.)
패키지 캐시란 ?
패키지 캐시는 패키지 매니저가 인터넷에서 패키지를 다운로드하거나 로컬에서 패키지를 설치할 때, 이미 다운로드한 패키지를 로컬에 저장하여 같은 패키지를 다시 다운로드하지 않고 이전에 다운로드한 것을 사용함으로써 시간과 대역폭을 절약할 수 있게 도와준다.- 패키지 캐시는 주로
.npm또는.yarn디렉토리에 저장되며, 이 디렉토리는 패키지 매니저가 자동으로 생성하고 관리- 캐시된 패키지는 해당 패키지의 버전과 메타데이터를 포함하며, 필요할 때 패키지 매니저가 이를 사용하여 종속성을 설치하거나 업데이트
- 패키지 캐시의 존재는 패키지 매니저가 빌드 시간을 줄이고 패키지를 효율적으로 관리할 수 있도록 도와줌
.npm 폴더가 있으면 의존성을 설치할 때 시간을 줄여주긴 하지만, 저희는 의존성 폴더 자체를 캐싱해서 사용할 예정이기 때문에 그다지 필요가 없습니다.
.npm 캐싱 key는 package-lock.json 입니다.
의존성이 추가될 때마다 package-lock.json가 변경되기 때문에, 해당 캐시는 초기화가 됩니다.
저희는 의존성이 추가되면, 바로 해당 의존성 폴더를 캐싱해서 사용합니다.
캐싱된 의존성 폴더가 있으면 의존성을 재설치 하지 않아도 됩니다.
그러므로 의존성을 설치할 때 시간을 줄일 필요가 없으며, 해당 .npm 캐시를 내려 받는 시간도 손해입니다.
(약 40MB를 내려받는데, 1초 정도 걸리는 것 같습니다. → 1초를 손해봄)

node_modules 폴더 캐싱 추가 & cache-hit에 따라서 Install Dependencies 진행# 변경 전
- name: Install Dependencies
run: npm install
# =========================================================================
# 변경 후
- name: Cache node modules
id: cache
uses: actions/cache@v4
with:
path: '**/node_modules'
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
cache action을 추가했습니다.cache hit를 기준으로 Install Dependencies를 할지 말지 결정합니다.npm install 대신에 npm ci 사용- name: Install Dependencies
run: npm install
# =========================================================================
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
CI/CD 에서는 npm ci가 더 옳은 명령어라고 해서 수정했습니다.npm install 과 npm ci 차이npm install은 종속성을 추가하거나 업데이트하기 위해 사용npm ci는 package-lock.json에 정의된 정확한 종속성으로 빠르고 일관된 설치를 보장하기 위해 CI 환경에서 사용node modules 의존성이 캐싱되는 첫 workflow를 제외하고 비교하면, 17s → 12s (약 30%의 성능 향상)workflow 기준| 상황 | 설명 | 시간 (초) |
|---|---|---|
| cache action 추가 전 | npm 의존성 캐싱 X, .npm 캐시 적용 | 17 |
| cache action 추가 후 첫 빌드 | npm 의존성 캐싱 X, .npm 캐시 적용 | 27 |
| cache action 추가 후 두번째 빌드 | npm 의존성 캐싱 O, .npm 캐시 적용 | 13 |
| cache action 추가 후 세번째 빌드 | npm 의존성 캐싱 O, .npm 캐시 제거 | 12 |
cache action 추가 전 (npm 의존성 캐싱 X, .npm 캐시 적용) → 17초
cache action 추가 후 첫 빌드(npm 의존성 캐싱 X, .npm 캐시 적용) → 27초node_modules를 캐싱하는 과정이 있습니다.
Post Cache node modules → node moudle을 캐시로 등록하기 위해 업로드하는 시간이 7초 걸림 
cache action 추가 후 두번째 빌드 (npm 의존성 캐싱 O, .npm 캐시 적용) → 13초node module을 캐시로 가져왔기 때문에 Install Dependencies가 진행되지 않은 것을 보실 수 있습니다.
Cache node modules → node module을 캐시로 가져오는 시간이 2초 걸림
(cache-hit이 true라서, Install Dependencies가 진행되지 않음)

여기서 Post Cache node modules는 진행한 job에 대해서 cleanup을 합니다.
cache action 추가 후 세번째 빌드 (npm 의존성 캐싱 O, .npm 캐시 제거) → 12초.npm 폴더에 대한 캐싱을 삭제했더니, 해당 폴더를 다운로드 하는 시간이 줄어들어 1초 더 빨라졌습니다.