Next.js 15가 출시되면서 가장 기대했던 것은 PPR (Partial Prerendering) 입니다. 정적 렌더링(Static)의 속도와 동적 렌더링(Dynamic)의 유연함을 합쳤다는 이 기술, 과연 얼마나 빠를까요?
이를 검증하기 위해 단순히 로컬에서 npm run dev를 켜놓고 새로고침을 누르는 것은 의미가 없습니다. 통제된 환경, 극한의 부하, 그리고 측정이 필요합니다.
이번 글에서는 Next.js 15의 진가를 확인하기 위해 구축한 'Docker + PM2 + Monitoring' 테스트 환경 아키텍처를 구축해보겠습니다.
성능 테스트에서 가장 중요한 것은 '변수 통제' 입니다. 내 컴퓨터에서는 백그라운드에서 카카오톡이 돌아가고, 동료 컴퓨터에서는 OS가 다르면 공정한 비교가 불가능합니다.
🐳 Docker의 역할
환경 일치: 어느 컴퓨터에서 실행하든 똑같은 OS(Alpine Linux), 똑같은 Node.js 버전을 보장합니다.
리소스 제한: 테스트를 위해 정확히 4 vCPU, 4GB RAM을 할당했습니다. 무한한 자원이 아닌, 한정된 자원 속에서 Next.js가 얼마나 버티는지 보기 위함입니다.
🐙 Docker Compose의 역할 테스트 환경은 단순히 Next.js 하나로 끝나지 않습니다. 비교군 서버, 모니터링 도구 등 여러 컨테이너가 유기적으로 연결되어야 합니다.
Target 서버 (Next.js 15 + PPR)
Compare 서버 (Next.js 15 일반)
모니터링 도구 (Prometheus, Grafana, cAdvisor)
이 모든 것을 설정 파일 하나(docker-compose.yml)로 정의하여, 명령어 한 번에 손쉽게 프로젝트를 올리고 내리는 환경을 구착했습니다.
부하 테스트 도구(k6)를 돌리면 "초당 1,000개 요청 성공" 같은 텍스트 결과는 나올 것으로 추측되지만. 하지만 그 과정에서 서버 내부가 어떤 상태였는지는 알 수 없습니다.
"메모리가 부족해서 터지기 직전이었나?"
"CPU가 튀어서 응답이 느려진 건가?"
이 궁금증을 해소하기 위해 시각화 도구를 도입했습니다.
🔭 Prometheus cAdvisor는 도커 컨테이너들의 실제 리소스 사용량(CPU, Memory) 을 감시하는 CCTV입니다. 그리고 Prometheus는 이 CCTV 영상(데이터)을 1초마다 수집해서 저장하는 기록 장치입니다. 특히 PM2를 사용할 경우, Node.js 내부에서 보는 메모리와 실제 컨테이너가 사용하는 메모리가 다르기 때문에 cAdvisor를 통한 외부 감시가 필수적이었습니다.
📊 Grafana (대시보드) 수집된 데이터를 보기 좋게 그려주는 역할입니다. 우리는 Grafana를 통해 스트레스 테스트가 진행되는 동안 실시간으로 CPU가 100%를 치는지, 메모리 누수(Memory Leak)가 발생하지 않는지 그래프로 확인했습니다.
├── compare (비교 대상 PPR이 적용된 Next.js 프로젝트)
│ ├── public/...
│ ├── src/...
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── ecosystem.config.js
├── target/ (PPR 적용전 Next.js 프로젝트)
│ ├── public/...
│ ├── src/...
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── ecosystem.config.js
├── prometheus/
│ └── prometheus.yml
├── .gitignore
├── docker-compose.yml
├── README.md
└── stress-test.js
compare 라는 Nextjs PPR(cacheComponents 옵션)이 적용된 프로젝트와 target 이라는 적용전 프로젝트 2개를 설치합니다
이후 두 프로젝트에서 각각 docker파일을 만들어 도커라이징 준비를 합니다
해당 Dockerfile 에서 사용되는 pm2 스크립트 파일 역시 준비합니다. (ecosystem.config.js)
Prometheus 는 그라파나가 시작화 자료를 그리기 위해 데이터를 수집하는 유용한 오픈소스 툴이기때문에 필수적으로 필요하며 PPR 도입 전과 후를 비교하기 위하여 job 설정을 진행합니다 prometheus.yml
그리고 핵심적인 dock-compose.yml 파일을 만들어 compare 와 target 폴더의 Dockerfile 을 읽어오도록 설정합니다.
사실 이 분야는 제 전문적인 분야가 아니기때문에 어떻게 원하는 결과값을 얻기 위해 무슨 툴을 사용해야하는지는 알지만 그 방대한 도큐먼트를 읽기 힘들기때문에 AI와 함께 개발을 진행으며 아래와 같은 이슈들이 존재했습니다
이를 위해 pm2 를 도입했으며 그 결과물이 ecosystem.config.js 파일입니다.
pm2가 도입되기 이전에는 정상적으로 보이는 사용률을 보였지만 cpu 사용률이 25% 이상을 못 넘는 이슈가 발생했습니다
실제로는 25%를 못 넘는게 아니라 4개의 코어중 1개의 코어를 전부 사용중이라 25%만 표현되었던것이지요

이를 해결 하기 위해서 cadvisor 를 도입했습니다
당연한 이야기지만 서버를 재 실행할때마다 도커 프로세스는 기존 내용을 모두 잊고 새로운 서비스로 띄어지게됩니다Compare 서버 (Next.js 15 일반)
모니터링 도구 (Prometheus, Grafana, cAdvisor)
이 모든 것을 설정 파일 하나(docker-compose.yml)로 정의하여, 명령어 한 번에 손쉽게 프로젝트를 올리고 내리는 환경을 구착했습니다.
부하 테스트 도구(k6)를 돌리면 "초당 1,000개 요청 성공" 같은 텍스트 결과는 나올 것으로 추측되지만. 하지만 그 과정에서 서버 내부가 어떤 상태였는지는 알 수 없습니다.
"메모리가 부족해서 터지기 직전이었나?"
"CPU가 튀어서 응답이 느려진 건가?"
이 궁금증을 해소하기 위해 시각화 도구를 도입했습니다.
🔭 Prometheus cAdvisor는 도커 컨테이너들의 실제 리소스 사용량(CPU, Memory) 을 감시하는 CCTV입니다. 그리고 Prometheus는 이 CCTV 영상(데이터)을 1초마다 수집해서 저장하는 기록 장치입니다. 특히 PM2를 사용할 경우, Node.js 내부에서 보는 메모리와 실제 컨테이너가 사용하는 메모리가 다르기 때문에 cAdvisor를 통한 외부 감시가 필수적이었습니다.
📊 Grafana (대시보드) 수집된 데이터를 보기 좋게 그려주는 역할입니다. 우리는 Grafana를 통해 스트레스 테스트가 진행되는 동안 실시간으로 CPU가 100%를 치는지, 메모리 누수(Memory Leak)가 발생하지 않는지 그래프로 확인했습니다.
├── compare (비교 대상 PPR이 적용된 Next.js 프로젝트)
│ ├── public/...
│ ├── src/...
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── ecosystem.config.js
├── target/ (PPR 적용전 Next.js 프로젝트)
│ ├── public/...
│ ├── src/...
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── ecosystem.config.js
├── prometheus/
│ └── prometheus.yml
├── .gitignore
├── docker-compose.yml
├── README.md
└── stress-test.js
compare 라는 Nextjs PPR(cacheComponents 옵션)이 적용된 프로젝트와 target 이라는 적용전 프로젝트 2개를 설치합니다
이후 두 프로젝트에서 각각 docker파일을 만들어 도커라이징 준비를 합니다
해당 Dockerfile 에서 사용되는 pm2 스크립트 파일 역시 준비합니다. (ecosystem.config.js)
Prometheus 는 그라파나가 시작화 자료를 그리기 위해 데이터를 수집하는 유용한 오픈소스 툴이기때문에 필수적으로 필요하며 PPR 도입 전과 후를 비교하기 위하여 job 설정을 진행합니다 prometheus.yml
그리고 핵심적인 dock-compose.yml 파일을 만들어 compare 와 target 폴더의 Dockerfile 을 읽어오도록 설정합니다.
사실 이 분야는 제 전문적인 분야가 아니기때문에 어떻게 원하는 결과값을 얻기 위해 무슨 툴을 사용해야하는지는 알지만 그 방대한 도큐먼트를 읽기 힘들기때문에 AI와 함께 개발을 진행으며 아래와 같은 이슈들이 존재했습니다
이를 위해 pm2 를 도입했으며 그 결과물이 ecosystem.config.js 파일입니다.
pm2가 도입되기 이전에는 정상적으로 보이는 사용률을 보였지만 cpu 사용률이 25% 이상을 못 넘는 이슈가 발생했습니다
실제로는 25%를 못 넘는게 아니라 4개의 코어중 1개의 코어를 전부 사용중이라 25%만 표현되었던것이지요

이를 해결 하기 위해서 cadvisor 를 도입했습니다
당연한 이야기지만 서버를 재 실행할때마다 도커 프로세스는 기존 내용을 모두 잊고 새로운 서비스로 실행되기때문에 이를 방지하기 위해서 volumes 설정이 필요하다는 사실도 알았습니다.
코어를 하나밖에 활용못하는 노드를 위해 설치하 사용했지만 그만큼 서비스가 많이 실행됨에 따라 메모리 사용량 역시 수배로 늘어났습니다. (150메가 -> 700메가) 멀티쓰레드를 잘 활용 가능한 언어들이 얼마나 좋은 언어이며. 앞으로 다가올 bun 이 얼마나 자바스크립트 진영에 필요한 툴인지를 체감했습니다.!
해당 설정을 기준으로
docker-compose --compatibility up -d --build target
명령어를 입력하게 되면


도커데스크탑에서 정상적으로 실행되는 프로세스들을 확인 가능합니다