Next.js Sharp에 대한 고찰

TIL

이 글은 2024-07-21에 작성되었어요.

빌드가 너무 느려요


사내 빌드 시스템이 Dockerfile 내부에서 sharp 라이브러리를 새로 설치하면서 install 시간이 굉장히 길어지며, 빌드가 오래 걸리는 문제가 있었습니다. 굳이 왜 Dockerfile 내부에서 sharp 라이브러리를 새로 설치하는지가 이상하실텐데요. 기존 사내 빌드 시스템이 과거부터 로컬 환경에서 빌드된 파일을 Dockerfile로 copy하는 방식으로 진행되고 있었고, 그 과정에서 sharp 에러가 발생함에 따라 sharp만 새로 설치하는 선택을 한 것입니다.

그렇다면 로컬 환경에서 빌드한 파일을 copy 했다고 해서 왜 sharp 에러가 발생 했을까요?

Sharp는 예민해


어떤 문제였는지 결론부터 정리하면, sharp 라이브러리는 기본적으로 현재 chipset 환경을 기반으로 빌드가 진행됩니다. 그래서 로컬 환경(arm)에서 빌드한 파일을 buildx로 플랫폼을 변경한 local Dockerfile(amd)로 옮겨 amd 환경에서 이를 실행해도 빌드한 환경(os)과 실행되는 환경이 달라서 sharp를 찾을 수 없다는 에러가 발생했었어요.

처음에는 sharp 라이브러리가 chipset에 영향을 받는다는 점을 인지하지 못했어요. 그 이유는 처음 본 경고 메세지는 sharp' is required to be installed in standalone mode for the image optimization to function correctly. 였거든요. 이와 관련된 Docs도 연결되어 있어 내용대로 해결을 시도 해봤는데도 안되더라고요.

끙끙 앓으면서 해결을 위한 여러가지 테스트를 해봤는데 좀 처럼 잘 되지가 않았어요. 뒤 늦게 깨달았던 점은 ‘되는 환경’과 ‘안 되는 환경’의 차이점이 뭔지 생각해보고, 이를 기반으로 접근했으면 더 좋았을 것 같은데 이 점을 늦게 깨달았어요. 시야를 달리 보고 난 뒤 보였던 것은 ‘로컬(arm)에 설치된 node_module/sharp’의 내용과 ‘docker(amd) 환경에서 설치한 node_module/sharp’의 내용이 다르다는 점이었어요.

자세히 확인해보니 내부적으로 C++로 이루어진 바이너리 파일들이 존재했고, 이게 영향이 있다는 점을 알게 되었어요.

이 과정에서 곁가지 처럼 학습할게 아니라 하나하나 깊이 있게 알아야 한다는 점을 또 한번 배웠습니다. ‘sharp’ 라는 라이브러리가 어떤 원리로 동작하는지 얕게라도 알았다면 쉽게 원인을 찾았을 거에요.

어떻게 개선할까?


애초에 로컬 환경에서 빌드한 파일을 Dockerfile로 옮기는 자체가 사실 좋지 않은 방법이었습니다. 실행될 환경에 맞춰서 빌드가 되어야 하는데, 그렇지 않다면 문제가 생길 여지가 다분하기 때문이었습니다. 이 참에 로컬 환경에서 빌드하면 매번 새롭게 빌드하는 과정을 기다리기도 했어야 했기 때문에, buildx로 플랫폼을 배포 환경에 맞춘 dockerfile 내부에서 install, build 등을 진행하게 수정하고, 최대한 Cache가 히트하도록 개선했습니다.

변명이지만 사실 진즉 이 기반으로 진행했어야 했는데, 최근 사내에서도 굉장히 발빠르게 신규 서비스를 내고있는 상황이라 건드리기 쉽지 않았어요.

사내에서는 Turborepo를 사용하고 있는데요. turbo에서 제공하는 prune 옵션을 사용해서 의존성 파일과 배포가 필요한 최소한의 파일들을 분리시켜 주어서 이를 활용해 배포 환경을 구축했습니다.

한가지 문제가 더 남았다…


다만, 아직 한 가지 문제가 더 있는데 사내 사정으로 인해 프론트엔드 팀은 ‘수동 빌드, 배포’를 원칙으로 하고 있어서 여전히 로컬 환경에서 Docker image를 생성하고 있습니다.

파악한 바에 따르면, buildx를 사용해 다른 환경으로 이미지를 생성하려고 하면 QEMU 라는 에뮬레이터를 사용하게 되는데요. 이 에뮬레이터는 네이티브 환경에 비해 꽤 느리기 때문에 여전히 빌드 속도가 개선 되지 않는다는 점이 있었습니다.

그래서 이를 GitHub Actions나 Circle CI와 같은 VM 환경에서 구축을 고려했으나, CTO께서 제안해주신 K8s의 kluster 환경을 이용해 dind(docker-in-docker) 방식으로 진행을 우선 진행해 볼 예정입니다.

아직 진행은 못했지만 업무 중간중간 시도해서 꼭 성공적으로 마무리 지은 뒤 또 한번 재미있는 역량을 키워보도록 하겠습니다!