이 리눅스 트릭이 Next.js 충돌을 막습니다.

다음.js 빌드 실패를 해결하기 위해 클라우드 서버 업그레이드를 중단하세요. 잊혀진 단 하나의 리눅스 명령어가 배포를 안정화하고 수백을 절약할 수 있습니다.

Stork.AI
Hero image for: 이 리눅스 트릭이 Next.js 충돌을 막습니다.
💡

TL;DR / Key Takeaways

다음.js 빌드 실패를 해결하기 위해 클라우드 서버 업그레이드를 중단하세요. 잊혀진 단 하나의 리눅스 명령어가 배포를 안정화하고 수백을 절약할 수 있습니다.

당신의 Next.js 배포의 조용한 암살자

새로운 우분투 드롭렛에서 `next build`를 실행하면 처음 10초 동안은 모든 것이 괜찮아 보입니다. 그런데 그 후 부하 평균이 급증하고 CPU 사용률이 100%에 도달하며 SSH 세션에서 문자가 떨어지기 시작합니다, 마치 형편없는 줌 통화처럼요. 1분 후 터미널이 다시 돌아오는데, 한 마디로 화가 나는 단어가 나타납니다: “Killed.”

스택 트레이스도, 깔끔한 오류 코드도 없이, 그저 죽은 빌드만 있습니다. 다시 실행하면 같은 패턴이 반복됩니다: 팬이 돌아가고, 상단에는 노드와 CPU를 소모하는 작업자들이 대거 등장한 뒤 정적입니다. 이후 제공자의 그래프를 확인하면 CPU와 메모리에 급격한 스파이크가 나타난 뒤, 프로세스가 사라지는 절벽이 이어집니다.

이 실패 모드는 클라우드의 저가 섹터에 가장 큰 타격을 미칩니다. DigitalOcean, Vultr, Linode 또는 Lightsail의 월 $5~$7, 1GB RAM 인스턴스는 기본 Node 애플리케이션에 적합합니다—하지만 현대의 Next.js 빌드를 실행하라고 요구하면 이야기가 달라집니다. 그 빌드 창 동안, 당신의 "작지만 강력한" 드롭릿은 갑자기 크롬을 컴파일하려는 라즈베리 파이처럼 행동하게 됩니다.

개발자들은 일반적으로 클라우드 컴퓨팅에서 가장 비싼 반응으로 이 문제에 접근합니다: 하드웨어가 문제라고 가정하는 것입니다. 이야기는 매번 같은 방식으로 전개됩니다. 빌드가 중단되고, 서버가 얼어붙은 듯한 느낌이 드는 순간, 본능적으로 “이 박스는 Next.js를 감당할 수 없어. 2GB, 어쩌면 4GB가 필요해.”라는 반응을 보이게 됩니다.

클라우드 대시보드는 심지어 당신을 그곳으로 유도합니다. 메모리가 꽉 찼고, CPU가 최대에 달하며, 로그에서 빨간색 “메모리 부족” 이벤트가 보입니다. 업그레이드 버튼은 단 한 번의 클릭 거리에서 기다리고 있으며, 더 큰 인스턴스 계층이 문제를 사라지게 해줄 것이라고 약속합니다. 많은 팀들에게 그 클릭은 배포 매뉴얼의 일부가 됩니다.

현실은 덜 화려하고 훨씬 저렴합니다. 이러한 충돌은 일반적으로 앱이 지속적으로 더 큰 기계를 필요로 한다는 의미가 아니라, 빌드 단계가 잠시 동안 드롭릿이 사용할 수 있는 메모리보다 더 많은 메모리를 필요로 한다는 의미입니다. 기본 우분투 이미지에서 스왑이 비활성화되어 있을 경우, 커널은 이러한 급증에 대처하는 신뢰할 수 있는 유일한 방법: 빌드를 종료하는 것입니다.

왜 당신의 1GB 서버는 `next build`를 싫어할까요?

일러스트: 왜 당신의 1GB 서버가 `next build`를 싫어하는가
일러스트: 왜 당신의 1GB 서버가 `next build`를 싫어하는가

Next.js는 `htop`에서 단일 `node` 프로세스처럼 보이지만, `next build`는 하나의 바이너리에 압축된 작은 분산 시스템처럼 작동합니다. 내부적으로 Next.js는 여러 Node 워커, TypeScript 도구 체인, 번들러 및 자산 파이프라인을 조정하여 모두 동일하게 제한된 1GB의 RAM에서 경쟁하고 있습니다.

Node 자체부터 시작하세요. 주요 프로세스는 페이지 컴파일을 병렬화하기 위해 여러 워커 스레드 또는 자식 프로세스를 실행합니다. 각 워커는 자신의 의존성 그래프 청크, V8 힙 및 빌드 메타데이터를 로드합니다. 하나의 200~300MB 프로세스 대신 여러 개를 잠시 동안 생성하며, 이들 각각의 피크가 쌓입니다.

다음은 TypeScript 이야기입니다. TypeScript 프로젝트에서 `next build`를 실행하면 툴체인이 TypeScript 컴파일러를 로드하고, 전체 코드베이스를 파싱하며, 타입 검사를 수행합니다. 이는 여러 개의 큰 AST, 심볼 테이블, 캐시가 동시에 메모리에 상주하게 되며, 중간 규모의 프로젝트에서는 종종 수백 메가바이트까지 메모리 사용량이 급증하게 됩니다.

그 외에도, Next.js는 클라이언트서버 번들을 생성하기 위해 번들러(웹팩 또는 터보팩)를 호출합니다. 각 대상은 고유한 의존성 그래프, 최적화 단계 및 소스 맵이 필요합니다. 대규모 구성 요소 라이브러리, UI 프레임워크 및 디자인 시스템은 이러한 그래프를 부풀리기 때문에, 프로덕션에서 300–400MB로 잘 작동하는 프로젝트가 빌드 중에는 800–900MB 또는 그 이상에 이를 수 있습니다.

그 다음은 이미지와 정적 자산입니다. next/image를 활성화하거나 대용량 미디어를 처리하면 빌드 파이프라인이 파일을 디코딩, 크기 조정 및 재압축합니다. 이미지 작업은 메모리를 많이 사용합니다: 몇 개의 4K 히어로 이미지나 스프라이트 시트는 디스크에 다시 쓰여지기 전, 잠시 동안 작업자당 수십 또는 수백 메가바이트를 차지할 수 있습니다.

모든 것은 몇 초에서 몇 분이라는 짧은 시간 안에 일어납니다. 20석의 커피숍이 갑자기 60명의 플래시 몹을 수용하는 상황을 상상해 보세요. 정상적인 고객 수는 괜찮지만, 그 짧고 혼란스러운 폭풍이 입구를 막고, 직원들을 압도하며, 단골들은 바깥에 갇히게 만듭니다. `next build`는 1GB 드롭렛에서 바로 그런 시간적 과부하를 생성합니다.

1 GB의 스왑이 없는 우분투 서버에서 그 플래시 몹은 메모리 사용량을 물리적 한계를 초과하게 만듭니다. 커널은 공격적으로 메모리를 회수하기 시작하고, 캐시는 사라지며, 여전히 충분한 RAM을 찾지 못하면 OOM 킬러가 개입하여 가장 무거운 프로세스를 종료합니다. 당신의 `다음 빌드`는 한 단어의 묘비명과 함께 죽게 됩니다: `Killed`.

커널의 냉혹한 최후의 수단: OOM 킬러

OOM 킬러라는 이름은 극적하게 들리지만, 실제로 그렇습니다. 리눅스 시스템에서 물리적 RAM이 부족해지면, 커널의 메모리 부족 (OOM) 킬러가 마지막 수단으로 개입하여 실행 중인 모든 프로세스를 스캔하고, 전체 시스템이 멈추지 않도록 희생할 프로세스를 결정합니다. 이것이 없으면 1GB 우분투 드롭릿은 메모리 압박에 시달리며 단순히 멈추게 되고, SSH 세션이 끊기고 여러분은 죽은 터미널과 강제 재부팅만 남게 됩니다.

Next.js 빌드는 완벽한 타겟을 만듭니다. `next build` 중에 Node는 여러 작업 프로세스를 생성하고 TypeScript와 같은 컴파일러를 로드하며 번들을 생성하고 때때로 이미지를 처리하여 메모리 사용량이 단기간에 기준치보다 수백 메가바이트 이상 증가합니다. 커널에게는 이러한 과정이 크고 최근의 비필수 프로세스로 보여져 “시스템 전반”에 미치는 영향이 최소화된 채로 종료될 수 있습니다.

리눅스는 oom_score (및 oom_score_adj)라는 휴리스틱을 사용하여 희생자들을 순위 매깁니다. 최근에 많은 메모리를 할당한 큰 프로세스는 루트 권한을 갖지 않으며, 핵심 시스템 서비스에 속하지 않는 경우 상위로 떠오릅니다. 1GB 드롭렛에서 Next.js 빌드는 이미 nginx, sshd 및 작은 데이터베이스와 함께 위치해 있을 때, 종종 RAM에서 가장 부풀어 오르고 폐기하기 쉬운 존재가 됩니다.

스왑이 없으면 상황이 완전히 달라집니다. RAM이 100%에 도달하고 스왑이 전혀 구성되지 않은 경우, 커널은 페이지를 필사적으로 회수하며 멈추거나 OOM 킬러를 호출하는 두 가지 선택지만 있습니다. 이 이분법적 선택이 여러분의 터미널이 잠시 멈춘 후 단어 하나—“Killed”—를 출력하는 이유를 설명합니다. 스택 트레이스도 없고, Next.js 오류도 없으며, Node에서 유용한 힌트는 없죠.

그 "Killed" 메시지는 npm이 무례한 것이 아니며, 이는 커널의 서명입니다. OOM 킬러가 프로세스를 중단할 때 발생하는 `pnpm install`, `npm install` 또는 `next build` 실행 실패 시 확인할 수 있습니다. 시스템 로그(`dmesg` 또는 `journalctl -k`)는 일반적으로 “메모리가 부족합니다: 프로세스 1234(node)를 종료하거나 자식을 희생합니다.”와 같은 항목으로 결정적인 단서를 드러냅니다.

스왑은 커널에 또 다른 동작을 부여합니다. 1-2 GB의 스왑 파일만으로도 시스템은 비활성 데몬, 캐시 페이지, 자주 사용되지 않는 라이브러리와 같은 차가운 페이지를 디스크로 밀어낼 수 있어 RAM을 확보하고 빌드를 완료할 수 있습니다. 단계별 가이드를 원한다면, Ubuntu VPS에서 NextJS 및 Docker 앱을 배포하기 위한 스왑 파일 생성 방법과 같은 자료가 프로덕션 친화적인 설정을 안내합니다.

서버의 비밀 무기: 스왑 파일을 만나보세요

스왑 공간은 작은 서버의 메모리에 대한 압력 밸브 역할을 합니다. RAM이 100%에 도달했을 때 `next build`를 즉시 종료하는 대신, 리눅스 커널은 초과 데이터를 전용 디스크 공간으로 흘려보내고 계속 진행할 수 있습니다. 그 공간이 바로 당신의 스왑 파일입니다.

스왑을 실리콘 대신 저장소에 살아있는 "오버플로우 RAM"으로 생각해 보세요. 리눅스는 디스크에 파일이나 파티션을 할당하고 이를 물리적 메모리의 확장으로 취급합니다. 이는 일반 RAM이 사용하는 것과 동일한 4KB 페이지로 측정됩니다.

1GB 드롭렛의 RAM이 Next.js 빌드 중에 부족해지면, 커널이 우선 순위를 조정하기 시작합니다. 유휴 데몬, 오래된 캐시 또는 백그라운드 서비스에 속하는 페이지가 RAM에서 스왑 파일로 이동하여 빌드의 핫 코드 경로를 위해 실제 메모리를 확보합니다.

커널은 가상 메모리 관리자를 통해 이를 자동으로 수행합니다. 애플리케이션을 다시 작성하거나 Node 플래그를 수정할 필요가 없습니다. 스왑이 존재하고 활성화되면 시스템은 사용 빈도가 낮은 페이지를 조용히 이동시키고 현재 가장 많은 작업을 하고 있는 작업을 위해 가장 빠른 메모리를 예약합니다.

디스크는 RAM에 비해 느리며, 밀리초 단위 대신 나노초 단위로 작동합니다. 따라서 스왑을 사용하면 항상 대기 시간이 추가됩니다. 그러나 짧은 생애의 빌드에서는 안정성이 속도보다 중요합니다: 스왑 서버에서 90초에 완료되는 `next build`는 20초 만에 “Killed”라는 단어와 함께 종료되는 것보다 훨씬 낫습니다.

스왑이 설정된 서버에서는 그 같은 극단적인 메모리 급증이 지루하게 보입니다. CPU는 여전히 상승하고, 팬은 계속 돌지만, SSH 세션은 반응을 유지하며, `top`에서는 스왑 사용량이 증가하고 있고, 빌드는 프로세스 테이블이 파괴되는 대신 차근차근 진행되고 있습니다.

스왑이 없는 서버에서는 커널이 탈출 경로가 없습니다. RAM이 가득 차면, 재사용 가능한 페이지를 찾기 위해 고군분투하다가 멈추거나 OOM 킬러를 호출하여 Node, 패키지 관리자인 노드 또는 생존하기 위해 화제를 불러일으킬 수 있는 기타 모든 것을 종료합니다.

그 대비는 뚜렷하다: 스왑이 있을 때는 빌드가 묵직하지만 신뢰할 수 있으며; 스왑이 없을 경우 동일한 작업 부하가 셸을 멈추게 하고, 배포를 망치며, 재부팅된 드롭렛을 계속 살펴봐야 할 수 있다.

단계별 가이드: 우분투에서 스왑 파일 만들기

일러스트레이션: 단계별: 우분투에서 스왑 파일 만들기
일러스트레이션: 단계별: 우분투에서 스왑 파일 만들기

우선, Ubuntu 박스에 스왑이 있는지 확인하세요. `sudo swapon --show` 명령어를 실행합니다. 결과가 비어 있다면 활성 스왑 장치가 없는 것입니다. 그 다음 `free -h` 명령어를 사용해 총 RAM과 현재 스왑 상태를 확인하고, `df -h`로 디스크 사용량을 점검합니다. 일반적인 25GB 드롭렛에서는 20% 미만이 사용되는 것을 보통 확인할 수 있으며, 이는 2GB 스왑 파일을 위한 충분한 공간을 남겨둡니다.

디스크 공간이 확인되면 스왑 파일을 할당합니다. 1GB RAM 서버에서는 2GB 파일이 Next.js 빌드에 실제로 여유 공간을 제공하여 디스크를 과도하게 사용하지 않게 합니다. `sudo fallocate -l 2G /swapfile` 명령어를 사용하세요. 이 명령은 실제로 공백을 쓰지 않고 즉시 2GB를 예약합니다. `ls -lh /swapfile`로 확인하면 크기 열에서 `2.0G`를 볼 수 있어야 합니다.

현재 `/swapfile`은 누구나 접근할 수 있는 일반 파일입니다. 루트만 읽거나 쓸 수 있도록 잠궈주세요. `sudo chmod 600 /swapfile`를 실행하세요. 다시 권한을 확인하려면 `ls -lh /swapfile`를 실행하면 됩니다. 그러면 줄의 시작 부분에 `-rw-------`가 표시되어 해당 파일이 루트 전용임을 확인할 수 있습니다.

다음으로, 그 평범한 파일을 실제 스왑 공간으로 변환합니다. `sudo mkswap /swapfile`를 사용하세요. 우분투는 `setting up swapspace version 1, size = 2 GiB`와 같은 메시지를 응답할 것입니다. 이 메시지는 커널이 이제 `/swapfile`을 유효한 스왑 영역으로 인식하지만, 명시적으로 활성화할 때까지는 여전히 비활성 상태임을 의미합니다.

단일 명령으로 스왑을 활성화합니다: `sudo swapon /swapfile`. `sudo swapon --show`를 다시 실행하면 이제 `/swapfile`, 그 크기(약 `2G`) 및 우선순위가 나열된 표를 볼 수 있어야 합니다. `free -h` 명령은 요약에 `Swap: 2.0Gi`를 표시하여 Next.js 빌드가 급증할 때 커널이 이제 메모리 페이지를 오프로드할 수 있음을 확인합니다.

현재 이 구성은 영구적으로 설정하지 않으면 재부팅 시 유지되지 않습니다. `sudo nano /etc/fstab` 명령어로 `/etc/fstab` 파일을 편집하고, 맨 아래에 한 줄을 추가하세요:

- `/swapfile 없음 스왑 sw 0 0`

저장하고 종료하면 완료됩니다. 다음 재부팅 시, 우분투는 자동으로 `/swapfile`을 활성화하므로 커널 업데이트, 재부팅 또는 예기치 않은 시스템 중단 이후에도 Next.js 빌드가 계속 작동합니다.

리부트 생존하기: 당신의 교환을 영구적이게 만들기

방금 생성한 스왑은 현재 부팅 동안만 작동합니다. `swapon /swapfile` 명령은 런타임 스위치를 전환하지만, 커널이 재시작되면 그 상태가 사라지고 Next.js 빌드는 다시 `Killed`로 종료됩니다.

재부팅 시 스왑을 유지하려면 `/etc/fstab` 파일에 등록해야 합니다. 이 파일은 리눅스가 부팅할 때 읽는 파일 시스템 테이블입니다. 이 파일은 커널에 어떤 디스크, 파티션 및 스왑 영역을 자동으로 마운트할지 알려줍니다.

터치하기 전에 백업을 만들어 두세요. 손상된 `/etc/fstab` 파일은 서버가 부팅되지 않게 할 수 있으며, 이로 인해 클라우드 대시보드에서 복구 콘솔을 찾는 데 허둥대게 될 수 있습니다.

달리기:

- `sudo cp /etc/fstab /etc/fstab.bak`

이제 루트 권한이 있는 편집기로 파일을 열어보세요. 예를 들어:

- `sudo nano /etc/fstab`

맨 아래로 스크롤하고 이 정확한 문구를 추가하십시오:

`/swapfile 없음 스왑 sw 0 0`

모든 필드는 중요합니다. `/swapfile`는 당신의 스왑 파일 경로입니다. `none`은 마운트 포인트를 나타내며, 스왑은 디렉토리 트리에 마운트되지 않기 때문입니다.

`swap`은 파일 시스템 유형을 정의하며, 이는 커널에 이 항목이 ext4나 xfs가 아닌 가상 메모리임을 알려줍니다. `sw`는 마운트 옵션으로 설정되며, 기본 동작으로 이 항목을 스왑으로 처리하라는 의미의 약어입니다.

마지막 두 개의 0은 `dump`와 `fsck` 동작을 제어합니다. `0 0`은 시스템이 이 파일에 대해 덤프를 시도하거나 파일 시스템 검사를 실행하지 않겠다는 의미로, 스왑에 대해 원하는 정확한 설정입니다.

저장한 후, 다음과 같이 문법을 검증하세요:

- `sudo mount -a`

출력이 없다는 것은 대개 성공을 의미합니다. `sudo reboot`로 재부팅한 후, `free -h` 또는 `swapon --show`를 사용하여 지속성을 확인하세요. 스왑 성능에 대한 더 깊은 조정과 배경 정보는 스왑 공간으로 리눅스 시스템 최적화 - Kite Metric를 참조하세요.

스왑의 양날의 검: 도움이 될 때와 해가 될 때

스왑은 메모리를 위한 압력 밸브처럼 작용하며, 무료 성능 업그레이드가 아닙니다. 신중하게 사용하면, Next.js 빌드가 끝날 때까지 1GB의 소량 메모리를 유지할 수 있습니다. 부주의하게 사용하면, 프로덕션 앱을 무너뜨릴 수 있습니다.

스왑을 사용해야 할 때: 짧고 날카로운 메모리 급증. 2-5분 동안 실행되는 `다음 빌드`는 사용량을 700-800MB에서 1.3-1.5GB로 잠시 증가시키며 완벽합니다. 커널은 차가운 페이지를 디스크로 내보내어 몇 백 메가바이트를 확보할 수 있고, 빌드는 "강제 종료"되는 대신 완료됩니다.

이러한 동일한 규칙은 드물게 실행되고 실시간 트래픽을 제공하지 않는 다른 버스티 작업에도 적용됩니다. 좋은 후보는 다음과 같습니다: - `npm install`, `pnpm install`, 또는 `yarn install` - 데이터베이스 마이그레이션 또는 일회성 데이터 가져오기 - 가끔 실행되는 관리 또는 유지보수 스크립트 - 컨테이너 또는 CI 에이전트에서의 배포 시간 단계

이런 경우, 귀하의 앱은 물리적 RAM 아래에서 잘 작동합니다—예를 들어, 1GB 서버에서 300–500MB 정도—그리고 빌드나 설치 중에만 추가 공간이 필요합니다. 속도와 신뢰성을 맞바꾸는 셈입니다: 빌드는 스왑을 사용하면 20–40% 느리게 실행될 수 있지만 실제로 완료됩니다. 많은 팀들에게, 더 작은 드롭렛을 사용하는 것이 즉시 그 비용을 상쇄합니다.

스왑을 사용하지 말아야 할 때: 핵심 앱에서의 안정적인 메모리 압박. 만약 여러분의 Next.js 서버와 데이터베이스가 하루 종일 1GB 인스턴스에서 1.4GB의 메모리를 원한다면, 커널은 RAM과 디스크 사이에서 메모리 페이지를 지속적으로 이동시킵니다. 이러한 스래싱은 성능을 파괴하는데, 왜냐하면 디스크는, 심지어 SSD조차도, RAM보다 수십 배 느리기 때문입니다.

유해한 스와핑은 몇 가지 구체적인 증상으로 확인할 수 있습니다: - 낮은 요청량에도 불구하고 높은 디스크 I/O (`iostat`, `iotop`) - 소수의 사용자만으로도 느린 HTTP 응답 또는 타임아웃 - `free -h`에서 수백 메가바이트의 스왑이 사용되고 있어 유휴 상태에서도 거의 떨어지지 않음 - CPU 사용량이 이상하게 낮은 반면 부하 평균이 급증함

그 빨간 신호가 나타나면 스왑은 총상에 반창고를 붙이는 것처럼 행동합니다. 실제 해결책은 더 많은 RAM이나 더 엄격한 메모리 예산입니다: 노드 작업자를 줄이고, 캐시 크기를 줄이며, 서비스를 분리하거나 데이터베이스를 별도의 서버로 옮기는 것입니다. 스왑은 드문 급증을 처리해야지, 24시간 내내 여러분의 앱을 유지하는 역할을 해서는 안 됩니다.

최종 조각: Node.js 메모리 한계 극복하기

일러스트: 최종 조각: Node.js 메모리 한계 극복하기
일러스트: 최종 조각: Node.js 메모리 한계 극복하기

스왑은 여유 공간을 제공하지만, 또 다른 조용한 방해꾼이 당신의 빌드를 무너뜨릴 수 있습니다: Node.js 자체입니다. 만약 Node가 1GB 서버에서 8GB의 힙이 "필요하다"고 결정한다면, 아무리 스왑 마법을 사용해도 OOM 킬러로부터 당신을 구해줄 수는 없습니다. 바로 여기서 하나의 불명확한 플래그가 모든 것을 바꿉니다.

Node의 `--max-old-space-size` 플래그는 V8 엔진이 주 JavaScript 힙에 사용할 수 있는 메모리 용량을 메가바이트 단위로 제어합니다. 이 한도가 너무 높으면 Node.js는 기계에 실제로 없는 메모리를 지나치게 예약하고, 그 결과 커널은 RAM과 스왑이 바닥나면 프로세스를 종료하게 됩니다.

Next.js 프로젝트는 종종 `package.json` 내에서 이 위험 요소를 숨깁니다. `scripts` 섹션에 묻혀 있는 빌드 명령은 다음과 같을 수 있습니다: - `"build": "NODE_OPTIONS='--max-old-space-size=8192' next build"`

1GB 드롭렛에서 8192MB 힙 목표는 환상입니다. Node는 기꺼이 그곳에 도달하려고 시도하지만, 여러분의 1GB RAM과 아마도 1–2GB의 스왑은 증발하고, 여러분의 빌드는 시작할 때와 동일한 순둥한 `Killed` 메시지를 내며 종료될 것입니다.

첫 번째 단계: 프로젝트의 `package.json`을 열고 모든 빌드 관련 스크립트를 확인하세요. `NODE_OPTIONS`를 설정하거나 `node` 또는 `next`에 직접 `--max-old-space-size`를 전달하는 항목을 찾아보세요. 예를 들어: - `"build": "NODE_OPTIONS='--max-old-space-size=4096' next build"` - `"build": "node --max-old-space-size=6144 node_modules/next/dist/bin/next build"`

그런 다음 해당 숫자를 실제 예산과 맞추십시오: 물리적 RAM + 스왑, 운영 체제, 데이터베이스 및 백그라운드 서비스에 대한 오버헤드를 뺀 값입니다. 1GB 서버에 2GB 스왑 파일이 있는 경우(대략 3GB 총합) `--max-old-space-size=2048` 제한을 설정하는 것이 일반적으로 합리적이며 나머지 모든 것을 위한 여유 공간을 확보합니다.

스크립트를 업데이트하고, 재설치 또는 재배포한 후 `next build`를 다시 실행하세요. 스왑이 활성화되고 Node의 힙이 현실적인 수준으로 제한되면, 빌드는 64GB 워크스테이션에서 실행되는 척을 멈추고 비좁고 저렴한 드롭릿에서 살아가는 것처럼 동작하기 시작합니다.

비욘드 빌드: 스왑이 당신을 구할 수 있는 다른 순간들

Swap은 불안정한 Next.js 빌드 이상의 문제를 조용히 해결합니다. 소형 VPS나 개발 박스에서 가끔 메모리 사용량이 급증하는 모든 작업은 OOM 킬러에 의해 충돌하기보다는 추가 기가바이트의 가상 메모리를 활용하는 것이 유리합니다.

패키지 관리자는 여기서 반복적으로 문제를 일으킵니다. 현대 모노레포에서 단일 `npm install`, `pnpm install`, 또는 `yarn install`을 실행하면 수십 개의 Node 작업자가 생성되고, 수천 개의 tarball이 압축 해제되며, 메모리에서 의존성 트리가 계산될 수 있습니다. 1GB 서버에서는 사용량이 몇 분 동안 90-100% RAM을 쉽게 초과할 수 있습니다.

무거운 데이터 가져오기마이그레이션 스크립트는 같은 방식으로 작동합니다. 몇 백 메가바이트의 JSON 또는 CSV를 메모리로 흡수하는 ETL 작업, 대규모 스키마에 데이터를 불러오는 Prisma나 TypeORM 마이그레이션, 또는 사용자 기록을 배치 처리하는 애드혹 관리 스크립트 모두 짧고 가혹한 메모리 스파이크를 생성합니다. 스왑이 활성화되어 있을 경우, 이러한 스파이크는 SSH 세션을 폭발시키는 대신 느려지게 만듭니다.

데이터베이스 도구는 또한 RAM에 의존합니다. 수 기가바이트에 이르는 PostgreSQL 덤프에서 `pg_restore`를 실행하거나 MySQL 스냅샷을 가져오거나 Elasticsearch 재색인을 수행할 때는 잠시 수백 메가바이트의 버퍼와 캐시를 할당할 수 있습니다. 1~2 GB의 스왑 파일은 커널이 비활성 페이지를 주차하고 핫 코드 경로가 실제 RAM에 유지될 수 있는 여유 공간을 제공합니다.

컨테이너화된 환경은 또 다른 취약성을 더합니다. Next.js 애플리케이션을 빌드하거나, 네이티브 모듈을 컴파일하거나, 테스트를 실행하는 Docker 컨테이너는 호스트보다 훨씬 이전에 cgroup 메모리 한도에 도달할 수 있습니다. 호스트 수준의 스왑 공간은 종종 커널이 빌드 중인 컨테이너를 종료하지 않도록 보호하는 마지막 버퍼 역할을 합니다.

로컬 개발도 예외는 아닙니다. 8GB 노트북에서 `next dev`, Storybook, 데이터베이스, 그리고 여러 탭이 열려 있는 브라우저를 실행하면 메모리를 빠르게 소모할 수 있습니다. 작은 스왑 파일과 로컬 개발 환경 최적화 방법 - Next.js에서 제시하는 방법들을 함께 적용하면 모든 것이 컴파일되는 동안 기계를 반응성 있게 유지할 수 있습니다.

과도한 지출을 멈추세요: 스마트 개발자를 위한 확장 가이드

소형 서버에서의 메모리 문제는 큰 신용카드가 필요하지 않습니다. 진단이 필요합니다. 월 $5에 1GB 드롭렛에서 월 $20 인스턴스로 뛰어들기 전에 Next.js 빌드가 짧고 불규칙한 메모리 사용으로 폭증하는 것인지, 아니면 지속적이고 느린 소모로 인해 발생하는 것인지를 확인하세요.

간단한 사고 흐름도가 당신을 정직하게 유지해 줍니다:

- 충돌이 짧고 드문 작업(Next.js 빌드, `npm install`, 마이그레이션) 중에만 발생합니까? → 먼저 스왑을 추가한 후 작업을 다시 실행하십시오. - 정상적인 트래픽 중에 서버가 느리게 느껴지고, 스왑 사용량이 높으며 지연 시간이 증가합니까? → 메모리를 프로파일링하고, 쿼리와 캐시를 최적화하거나 RAM을 업그레이드하십시오. - 애플리케이션이 "유휴" 상태일 때도 스왑 사용량이 높습니까? → 이는 실제 용량 문제를 가리고 있을 뿐이며, 해결하지 않는 것입니다.

간헐적인 작업 부하의 경우, 1GB RAM 박스에 1-2GB 스왑 파일을 설정하면 OOM 킬러가 빌드를 종료하는 것을 방지할 수 있습니다. 실제로 빌드가 완료되도록 몇 초 또는 몇 분의 추가 빌드 시간을 거래하는 것입니다. 하루에 몇 번의 배포가 이루어질 때, 이는 초당 수천 번의 배포가 아닌 좋은 거래입니다.

비용 계산은 주장을 명확하고 철저하게 만든다. $5/월 인스턴스를 유지하는 대신 $15로 뛰어오르면 매달 $10, 연간으로는 서버당 $120를 절약할 수 있다. 이걸 5개의 소규모 서비스에 적용하면, 한 번의 `fallocate`와 `/etc/fstab`에 줄 하나만 추가하는 대가로 연간 $600를 절약할 수 있다.

스마트 스케일링은 단순히 더 큰 박스를 구매하는 것이 아니라 도구를 쌓는 것을 의미합니다. 스왑을 사용하여 드문 스파이크를 처리하고, 빌드가 여전히 잘 작동하지 않을 때는 Node.js 메모리 플래그를 조정한 다음, 안정적인 사용량이 실제로 필요하다는 것을 입증할 때만 티어를 업그레이드하세요.

당신은 두려운 인프라가 아닌 이해하는 인프라를 갖게 됩니다. 빌드가 “Killed”로 종료될 때, 당신은 클라우드 공급자의 가격 페이지를 열기 전에 메모리, 스왑 및 Node.js 제한을 확인해야 한다는 것을 알고 있습니다. 그 지식은 스케일링을 당황스러운 선택이 아니라 신중하고 비용 효율적인 선택으로 바꾸어 줍니다.

자주 묻는 질문들

Next.js 빌드가 왜 이렇게 많은 메모리를 사용하는 걸까요?

Next.js 빌드는 메모리를 많이 소모합니다. 이는 TypeScript를 컴파일하고 클라이언트 및 서버 코드를 번들링하며 이미지와 같은 자산을 동시에 처리하기 위해 여러 워커 프로세스를 생성하기 때문입니다. 이러한 짧지만 강렬한 활동의 폭발은 1GB 클라우드 드롭렛과 같은 제한된 RAM을 가진 서버를 쉽게 압도할 수 있습니다.

리눅스 스왑 파일 사용이 성능에 나쁜가요?

스왑은 RAM보다 훨씬 느리기 때문에, 애플리케이션이 일상적인 작업을 위해 지속적으로 스왑에 의존하면 성능에 악영향을 미칠 수 있습니다. 하지만 빌드 과정과 같은 짧고 드문 메모리 급증의 경우, 약간의 지연은 안정성을 위한 가치 있는 양보로 볼 수 있습니다. 이는 빌드가 충돌하는 대신 성공적으로 완료될 수 있게 해주기 때문입니다.

Next.js 빌드를 위해 얼마나 많은 스왑 공간을 추가해야 하나요?

소형 서버(1-2GB RAM)의 좋은 규칙은 물리적 RAM의 양과 같거나 두 배에 해당하는 스왑 공간을 추가하는 것입니다. 1GB 드롭릿의 경우, Next.js 빌드 중 메모리 급증을 처리하기 위해 1-2GB의 스왑 파일을 만드는 것이 종종 충분합니다.

서버의 RAM을 업그레이드하는 대신 스왑을 사용할 수 있을까요?

임시적인 메모리 스파이크(예: 빌드나 패키지 설치)로 인해 메모리 문제가 발생하는 경우 스왑을 사용하여 업그레이드를 피할 수 있습니다. 그러나 애플리케이션의 일상적인 메모리 사용량이 서버의 RAM을 지속적으로 초과한다면, RAM을 업그레이드해야 합니다. 생산 트래픽에 대해 스왑에 의존하는 것은 성능 저하로 이어질 것입니다.

Frequently Asked Questions

왜 당신의 1GB 서버는 `next build`를 싫어할까요?
See article for details.
Next.js 빌드가 왜 이렇게 많은 메모리를 사용하는 걸까요?
Next.js 빌드는 메모리를 많이 소모합니다. 이는 TypeScript를 컴파일하고 클라이언트 및 서버 코드를 번들링하며 이미지와 같은 자산을 동시에 처리하기 위해 여러 워커 프로세스를 생성하기 때문입니다. 이러한 짧지만 강렬한 활동의 폭발은 1GB 클라우드 드롭렛과 같은 제한된 RAM을 가진 서버를 쉽게 압도할 수 있습니다.
리눅스 스왑 파일 사용이 성능에 나쁜가요?
스왑은 RAM보다 훨씬 느리기 때문에, 애플리케이션이 일상적인 작업을 위해 지속적으로 스왑에 의존하면 성능에 악영향을 미칠 수 있습니다. 하지만 빌드 과정과 같은 짧고 드문 메모리 급증의 경우, 약간의 지연은 안정성을 위한 가치 있는 양보로 볼 수 있습니다. 이는 빌드가 충돌하는 대신 성공적으로 완료될 수 있게 해주기 때문입니다.
Next.js 빌드를 위해 얼마나 많은 스왑 공간을 추가해야 하나요?
소형 서버의 좋은 규칙은 물리적 RAM의 양과 같거나 두 배에 해당하는 스왑 공간을 추가하는 것입니다. 1GB 드롭릿의 경우, Next.js 빌드 중 메모리 급증을 처리하기 위해 1-2GB의 스왑 파일을 만드는 것이 종종 충분합니다.
서버의 RAM을 업그레이드하는 대신 스왑을 사용할 수 있을까요?
임시적인 메모리 스파이크로 인해 메모리 문제가 발생하는 경우 스왑을 사용하여 업그레이드를 피할 수 있습니다. 그러나 애플리케이션의 일상적인 메모리 사용량이 서버의 RAM을 지속적으로 초과한다면, RAM을 업그레이드해야 합니다. 생산 트래픽에 대해 스왑에 의존하는 것은 성능 저하로 이어질 것입니다.
🚀Discover More

Stay Ahead of the AI Curve

Discover the best AI tools, agents, and MCP servers curated by Stork.AI. Find the right solutions to supercharge your workflow.

Back to all posts