제가 참여하고 있는 프로젝트 중에서 프로젝트의 프로덕션 빌드 확인을 위한 간단한 CI 설정을 한 프로젝트가 있습니다. 저희 CI 는 Github actions 를 통해 실행하고 있었습니다. 이번에 다른 팀원 분이 문제를 해결한 것을 코드리뷰 하는 중에 Github actions 와 actions pnpm 에 대해 오해하던 부분이 있어서 오개념을 바로 잡았고, 이를 정리하려고 합니다.
문제의 에러 메시지
FATAL ERROR: v8::Object::GetCreationContextChecked No creation context available ----- Native stack trace ----- 1: 0xe22fc9 node::OnFatalError(char const*, char const*) [node] 2: 0x122b176 v8::Utils::ReportApiFailure(char const*, char const*) [node] 3: 0x12405ac v8::Object::GetCreationContextChecked() [node] 4: 0xf7d90c [node] 5: 0x7fd0f2432afc # # Fatal error in , line 0 # Missing deoptimization information for OptimizedFrame::Summarize. # # # #FailureMessage Object: 0x7ffd7d17c800 ----- Native stack trace ----- 1: 0xffe461 [node] 2: 0x28c276b V8_Fatal(char const*, ...) [node] 3: 0x139cff3 v8::internal::OptimizedFrame::Summarize(std::vector<v8::internal::FrameSummary, std::allocator<v8::internal::FrameSummary> >*) const [node] 4: 0x13a5116 [node] 5: 0x13a931c v8::internal::Isolate::CaptureDetailedStackTrace(int, v8::StackTrace::StackTraceOptions) [node] 6: 0x12361f3 v8::StackTrace::CurrentStackTrace(v8::Isolate*, int, v8::StackTrace::StackTraceOptions) [node] 7: 0xf76c96 node::GetCurrentStackTrace(v8::Isolate*, int) [node] 8: 0xec549b node::DumpJavaScriptBacktrace(_IO_FILE*) [node] 9: 0xe22fd5 node::OnFatalError(char const*, char const*) [node] 10: 0x122b176 v8::Utils::ReportApiFailure(char const*, char const*) [node] 11: 0x12405ac v8::Object::GetCreationContextChecked() [node] 12: 0xf7d90c [node] 13: 0x7fd0f2432afc Trace/breakpoint trap (core dumped) ELIFECYCLE Command failed with exit code 133.
아주 생소한 에러메시지였습니다. actions workflow 가
next.js
프로젝트의 테스트 빌드 도중에 이러한 에러였습니다. 이 에러메시지를 검색 했을 때, 따끈따끈 일주일도 안 된 node.js
버그 수정 패치 공지를 확인 할 수 있었습니다. 그리고, 이를 통해 저희도 CI 또한 node 버전 설정 시에 22.5.0 버전을 사용하기에 생겼던 오류라는 것을 알 수 있었습니다. 실제로 CI 에러가 났을 때는 22.5.0 버전이었고, CI 에서 사용하는 node version 이 22.5.1 버전으로 업데이트 되자 해결되었습니다.
이는 저희쪽에 문제가 생긴 것도, 저희가 해결한 것도 아닙니다. 최신 버전의 node 쪽에서 문제가 생겼고, 그것을 node 쪽에서 해결했던 것 이었습니다.
이번 일을 통해서 알게된 것은 실제 production 이나 좀 더 안정적인 서비스가 필요하다면 node 등 중요한 엔진 요소는 LTS 버전을 사용하는 것이 좋을 것 같다는 것입니다. 저희가 디버깅 하기 어렵고, 결국 해결방안을 찾더라도 그것이 적용되기엔 많은 시간이 걸릴 수 있기 때문입니다.
하지만 사실 이런 비하인드가 있었습니다.
사실 제가 처음에 인터넷 검색 보다 먼저 한 것이 있습니다. 바로 GPT 에 물어본 것입니다. 답으로 돌아온 원인이 될 수 있는 상황은
1. 시스템 메모리가 부족하여 V8 엔진이 필요한 데이터를 로드하거나 유지할 수 없음 2. 사용 중인 node.js 버전과 사용 의존성 간의 비호환성 문제 3. node.js or V8 엔진 자체 버그
그리고 해당 이슈를 팀원 분도 저와 같이 우선적으로 1번, 2번 문제를 해결하려고 한 것 같습니다. (실은 3번이 문제 였는데, 보통 그걸 의심하진 않으니까요.) 그리고 문제 해결을 위해
pnpm store prune
을 CI 에 추가하는 과정을 넣자 해결되었습니다. 그런데 사실
pnpm store prune
의 추가가 문제를 해결해준 것이 아니었습니다. 정말 우연히도 해당 커밋에 node version 이 22.5.1 버전으로 업데이트 되도록 수정되어 (왜냐하면 노드버전을 22 버전 메이저만 설정을 해두고 하위 버전들은 그냥 최신을 쓰도록 설정 해두었습니다.) 해결이 된 것이었습니다. 직접적인 문제 해결은 22.5.1 버전을 사용할 수 있도록 버전만 지정해두면 됐을 것 입니다. 하지만 이런 사실을 알기 전에 ChatGPT 에 물어봤던 저는 처음에
pnpm store prune
을 통한 Global store 정리를 한 것이 직접적인 해결을 해준 것인 줄 알았습니다. 그래도, 저에게 의문은 있었습니다. 저희 CI 는 복잡한 절차를 거치거나 하지는 않고있습니다. 슈도코드로 보면1. 브랜치 체크아웃 2. pnpm 사용 적용 3. 노드 설치 4. pnpm install 로 의존성 설치 5. 테스트를 위한 mock server 실행 6. unit test 실행 7. build test 실행 8. 이후 1, 2, 3 과정의 후처리
다음과 같은 과정을 거쳤는데, 이 과정에서 글로벌 스토어 프루닝 하는 과정이 추가되었다고 문제가 해결된 것이 이상하다고 느꼈습니다. 왜냐하면 제가 알고 있던 개념 자체가 CI 를 위해 Actions 에서 환경을 docker 컨테이너로 제공되고 있고(이것은 제가 잘 못 알고 있던 개념입니다. VM 으로 제공해요.), 그렇다면 항상 새로 시작할 때 텅 비어있는 환경일텐데,
pnpm store prune
명령이 의미가 있나? 라는 의문이 들었기 때문입니다. 하지만 로그를 확인해보니 실제로 pnpm store prune
을 통해 지워지는 dependencies 들이 존재했습니다. 그래서 여러모로 actions 를 제가 잘 모르고 사용하고있었다는 것을 깨닫고 아주 약간 더 알아보았습니다.
오해 1. Github Actions 에서 제공하는 Github hosted runner 는 VM 으로 제공된다.
결과적으로 새로운 CI 가 실행될 때 마다 새로운 환경이 제공되는 것은 맞지만, docker 컨테이너와 VM 은 크게 다릅니다. docker 는 OS 의 환경 설정을 저장하고 제공하는 것이고, VM 은 아예 host 와 다른 새로운 OS 를 제공해주는 것입니다. CI 하는 수준에서는 크게 달라질 것이 없긴 하지만, 그래도 명확하게 아는 것이 중요하다고 생각하였습니다.
오해 2. node 설치 시 pnpm 글로벌 스토어를 캐시하고 있었다.
actions 를 통해 CI 중 node 설치 시 cache 설정을 할 수 있습니다.
- name: install node version ${{ matrix.node-version }} uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} cache: 'pnpm'
이 캐시 설정이 무엇을 캐시하는 것인지 잘 몰랐었는데, docs 를 보고 알게 되었습니다.
노드 버전이 달라져도 글로벌 패키지 데이터를 재사용 할 수 있도록 노드 설치 step 에서 글로벌 패키지 데이터를 캐시 할 수 있도록 되어있습니다. 추가적으로 pnpm 을 사용할 때는 캐시 기능을 켜는 것에 유의할 점이 있습니다.
- 글로벌 패키지 데이터는 npm 사용 시에는
node_modules
를 만들 때에는 직접적인 도움이 되지 않습니다.
- pnpm 사용 시에는 글로벌 패키지 데이터가
node_modules
를 만들 때, 직접적인 도움이 됩니다.
pnpm 을 사용할 때는 글로벌 패키지 스토어에 소프트 링크로
node_modules
를 만들기 때문에, 글로벌 패키지 스토어 자체를 캐시하면 추후 pnpm install
할 때 성능 상 이점이 있습니다. pnpm store prune
명령어는 어떤 프로젝트도 참조되지 않는 글로벌 패키지 스토어에 있는 패키지를 지우는 명령어로, 프로젝트의 package.json
파일과 pnpm-lock.yaml
파일을 기준으로 확인합니다. 즉, 글로벌 스토어를 캐시한다면 pnpm store prune
은 의미가 있는 step 이 될 수 있습니다.결론
- Github actions의 github hosted runner 는 VM 으로 제공된다.
- Github actions의 node version 을 세팅하는 actions 를 사용 할 때 옵션으로 들어가는 캐시는 패키지매니저의 글로벌 패키지 데이터를 캐시한다.
- ChatGPT 로 잘 모르는 문제를 해결 할 때는 확실하게 인과를 파악하는 것이 중요하다!
- node 와 같은 JS 런타임 같은 것은 production 일 경우에는 안정화된 버전을 사용하는 것이 좋겠다.