2025년 5월 31일, 블로그가 망가졌다
5월 말 들어 여행 블로그 때문에 이미지가 늘어났다. 로컬 빌드는 빨리 되는데 vercel에서 deploy하는 시간이 2분에서 6분으로, 6분에서 45분 이상으로 갑자기 길어졌다. 로그를 보면 [vite] transforming...
항상 이 줄에서 멈춘다. vercel은 deploy 시간을 45분으로 제한하기 때문에 글을 새로 써도 배포를 더이상 못하는 상태에 봉착하고 말았다.
그리고 요즘 이런저런 이유로 Astro를 버리고 싶던 참이었다. 그래서 곧바로 Next.js와 Sanity를 이용한 새로운 블로그 solmee.xyz 2.0.0를 만들기 시작하려 했다.
근데 생각해보니 이전 웹사이트가 망가진 상태로 방치하고 옮겨가는 건 도망치는 것 같잖아…! 그래서 문제는 해결하고 가기로 했다.
Emma Goto의 블로그를 보다가 나랑 비슷하게 Astro와 Obsidian을 사용한다는 것을 알게 되었는데, 나보다 Astro를 훨씬 창의적이고 효율적으로 쓰고 있는 것을 발견했다. 무엇보다 ‘등산’ 카테고리에 이미지가 많은데 블로그가 망가지지 않는다는 건 내 에러를 해결할 결정적인 단서가 숨어있다는 것을 의미했다! 이 글에 그분이 이미지를 최적화한 방법이 나와 있어서 나도 따라해 보기로 했다.
저분은 이미지가 3000개 이상인데 Netlify로 빌드가 2분 얼마밖에 안걸린다고 한다. 난 이미지 약 1000개에 45분 이상이 걸리니 확실히 비정상적이긴 한거다…
목표: 배포 시간 2분 이내로 줄이기
결과: 🎉 40초 🎉
디버깅 시작
챗지피티(이하 G)에게 나의 상황을 자세하게 설명하고 타임스탬프와 함께 배포 로그를 정리해서 보여줬다. 그랬더니 npx vercel build
로 Vercel의 환경과 똑같이 로컬에서 빌드해보라는 조언을 했다. 그러면 ram 사용량 등 Vercel이 안 알려주는 추가 정보를 찍어볼 수 있다고.
해봤는데 로그가 너무 길어서 vscode 터미널에서 앞부분이 잘렸다. G가 로그를 파일로 내보내는 법을 알려줬다. npx vercel build > vercel-build.log 2>&1
>
는stdout
으로 내보내는 것이고,2>&1
은stderr
로 에러와 경고까지 포함하라는 것이다.
그렇게 하니 code vercel-build.log
해서 출력된 로그를 볼 수 있었다.
- 이때
zsh: command not found: code
가 나온다면Cmd+Shift+P
>Shell Command: Install ‘code’ command in PATH
해서 설치하면 된다.
09:02:50 \[build\] Collecting build info...
09:02:50 \[build\] ✓ Completed in 31ms.
09:02:50 \[build\] Building static entrypoints...
09:02:57 \[vite\] ✓ built in 7.04s
09:02:57 \[build\] ✓ Completed in 7.07s.
출력된 로그에서는 이렇게 6, 7초 안에 끝나는 과정이 실제 Vercel deploy할때는 1분이나 걸렸다.
07:10:57 \[build\] Collecting build info...
07:10:57 \[build\] ✓ Completed in 61ms.
07:10:57 \[build\] Building static entrypoints...
07:11:55 \[vite\] ✓ built in 58.34s
07:11:55 \[build\] ✓ Completed in 58.49s.
딱히 에러는 안뜨는데 특정 줄에서 멈추는 상황… 의심해 본 경고들은 이런 게 있었다.
- client:only 경고는 내가 그래프 컴포넌트에
client:only="react"
라고 써야 할 것을 잘못 써서 나왔던 것으로 수정하니 사라졌다. - 멈추는 줄 직후에 나오는 externalized 어쩌고 경고는 빌드가 잘 되든 못 되든 상관없이 떴다. 이걸 없애보려고 여러 삽질을 해봤지만 못 없앤 끝에 중요한 경고는 아니라고 판단했다.
그럼 대체 [vite] transforming...
이 한 줄 이후에는 무슨 작업이 일어나길래 내 컴퓨터는 1분안에 끝내고 Vercel은 45분 줘도 못 끝내는 걸까? 혹시 Vercel이 일을 못해서?는 아닐까 해서 Netlify에다가 배포해봤다.
Netlify의 빌드는 17초로 Vercel보다 빨랐지만, 똑같은 곳에서 고장났다. 하지만 Netlify의 로그는 결정적인 단서를 제공해 주었다. Command failed with exit code 137: npm run build
이건 메모리를 너무 많이 썼다는 뜻이라고 한다. 얘는 문제 생기면 kill하고 에러 코드 주는데 Vercel은 문제가 뭔지, 문제가 있는지조차 안 알려주고 가만히 시간만 끄는구나.
문제는 메모리 초과이다
Vercel이나 Netlify나 배포 시 쓸 수 있는 메모리에 한계가 있어서 내 컴퓨터에서 할때와 달리 에러를 낸다는 것 같다. 이로써 내 빌드가 메모리를 너무 많이 쓴다는 것이 확실해졌다. 그러면 어떻게 해야 빌드할 때 메모리를 덜 쓰지? 이미지 용량이 큰 것이 문제인가?
어떤 라이브러리가 메모리를 잡아먹는지 로그를 출력해볼 수 있냐는 내 질문에 G가 whyIsNodeRunning
이라는 툴을 사용해보라고 제안했다. debug-build.cjs
스크립트를 만들어서 돌려봤다. 먼저 로컬 빌드로는 아무것도 안 찍혔고, Vercel에서는 걍 또 밑도 끝도 없이 멈춰서 로그 따위 안 찍혔다. 문제가 있으면 말을 하라고 답답아~ Netlify에서는 아래와 같이 나왔는데, 얜 또 너무 빨리 kill해버려서 의미있는 건 안 찍힌 것 같다.
❌ Build failed: Error: Command failed: astro build
10:35:25 AM: Killed
10:35:25 AM: at genericNodeError (node:internal/errors:983:15)
10:35:25 AM: at wrappedFn (node:internal/errors:537:14)
10:35:25 AM: at ChildProcess.exithandler (node:child_process:414:12)
10:35:25 AM: at ChildProcess.emit (node:events:518:28)
10:35:25 AM: at maybeClose (node:internal/child_process:1101:16)
10:35:25 AM: at Socket.<anonymous> (node:internal/child_process:456:11)
10:35:25 AM: at Socket.emit (node:events:518:28)
10:35:25 AM: at Pipe.<anonymous> (node:net:351:12) {
10:35:25 AM: code: 137,
10:35:25 AM: killed: false,
10:35:25 AM: signal: null,
10:35:25 AM: cmd: 'astro build'
10:35:25 AM: }
10:35:25 AM: Waiting to see if open handles are present...
10:35:30 AM: 🧪 Done checking open handles.
메모리를 먹는 범인을 잘못 짚음
어떤 놈의 파일 또는 라이브러리가 메모리를 잡아먹고 있는 건지 알아내야 했다. G가 rollup-plugin-visualizer
라는걸 알려줘서 써봤더니 여기서 아래와 같은 트리 맵이 브라우저에 띄워졌다. 라이브러리나 이미지가 클 거란 예측과 달리 마크다운 파일들이 거대한 공간을 차지하고 있었다.
파일들의 실제 크기는 고작해야 18kb정도인데 G가 말하기를 html로 파싱하고 프론트매터 넣고 마크다운 내 컴포넌트 번들하고 JS 모듈로 바꾸는 등등의 일을 할때 메모리를 쓴다고 한다. 그래서 md파일 내 프론트매터의 크기, 임베드된 컴포넌트의 갯수, 포맷팅, 내부링크, code fence(```) 등이 메모리 사용량에 영향을 미친다고.
마크다운이 빌드 때 한꺼번에 html로 파싱되는 것을 한번 막아보려고 eager: false
로 바꾸고, 그 글 가져오는 함수를 async로 바꾸고, 가져온 글 데이터들을 쓰는 모든 컴포넌트들을 클라이언트 컴포넌트로 바꿔 보았지만 헛수고였다. visualizer의 트리 그래프에서는 사라졌지만 메모리는 여전히 초과됐다.
firebase를 사용하는 뉴스레터 컴포넌트, 그리고 RSS도 그래프에서 비중이 크길래 주석처리해 봤더니 역시나 그래프에선 사라졌지만 메모리는 초과됐다.
빌드 시간이 갑자기 45분으로 껑충 뛰기 전의 트리그래프와 현재의 그래프를 비교해보면 눈에 띄게 큰 놈을 검거할 수 있지 않을까 해서 git checkout
해서 그래프를 띄워 봤다. 근데 진짜 마크다운 몇 개 더 추가된 것 빼곤 차이가 없었다. 아무리 생각해도 이상했다. 며칠새 고작 이미지 몇십 장이랑 마크다운 몇 개 추가했다고 메모리 한계를 넘어서 블로그가 망가졌는데, 그래프의 절반 이상을 차지하던 걸 없앴는데도 여전히 망가진다? 그 그래프가 정말 의미 있는 지표이긴 한 건가?
범인은 이미지들이었다.
역시 맨 처음 생각했던 대로 용량이 큰 이미지들 때문인가 해서 설마 설마 하며 G가 추천한 라이브러리인 imagemagick
을 사용해서 폴더째로 리사이징해봄. 3.84GB
에서 293.7MB
로 무려 92.5% 줄였다. 내 사진들이 크긴 컸구나…
그랬더니 Vercel, Netlify 배포 둘다 성공~!! 아 역시 문제는 이미지였는데 내가 엄청 빙빙 돌아서 왔구나~~ 그래도 도는 과정에서 옛날에 거지같이 쓴 코드 리팩터링도 좀 하고 아스트로 문법도 상기했다. 트리 그래프 같은 재밌는 툴도 알게되고! 다시 죄 없는 마크다운 SSG와 뉴스레터, RSS를 되돌렸다.
아니 근데? Netlify 왜케 빨라? Vercel은 3분 걸리는 걸 얘는 40초만에 해 버리네? 뭔가 캐싱을 잘해주는건가? 이참에 도메인도 걍 Netlify로 옮겨버림.
스크립트 추가
imagemagick
를 사용해서, 빌드할 때 이미지들이 들어있는 폴더에서 큰 이미지만 골라서 리사이징하는 스크립트 optimize-images
를 추가했다. 이미 작은 이미지면 스킵한다.