TIL

Docker stats로 보는 CPU 사용률 + Node.js 싱글스레드 병목 판단법

하얀잔디 2025. 12. 8. 19:23

1. docker stats로 CPU 보는 법

  • docker stats 치면 컨테이너별 CPU%, 메모리, 네트워크 등 실시간 리소스 나옴
  • 여기서 CPU%는 호스트 기준
  • 예: 4코어 서버라면 CPU 400%까지가 최대치
  • 컨테이너가 120% 찍혀 있으면 → 1.2 코어 먹는 중이라는 뜻임
  • docker는 cgroup으로 CPU 제한 걸지 않으면 컨테이너가 여러 코어 쓸 수 있음
  • 즉, CPU%가 100% 넘는다고 “오류”는 아님. 코어 여러 개 사용 가능해서 그럼

 

 

2. Node.js는 싱글스레드라 코어 하나밖에 못쓰는 거 아님?

  • Node의 JavaScript 실행부(V8)는 싱글스레드 맞음
  • 근데 운영 환경에서는 다음들 때문에 CPU가 100% 넘어가는 듯 보일 수 있음
    • libuv thread pool (파일 IO 등은 thread pool에서 돌아감 → CPU% 추가됨)
    • Node 프로세스가 여러 워커를 만들었을 경우 (cluster, PM2 cluster mode)
    • 컨테이너가 다른 작업까지 함께 잡아먹는 경우
  • 하지만 핵심 JS 이벤트루프 자체는 코어 하나만 전담함

 

그래서 중요한 기준

Node.js JS 스레드는 CPU 한 코어를 100% 가까이 먹기 시작하면 사실상 병목 의심 단계임

docker에서 CPU가 180% 찍히더라도
그 중 JS 스레드가 100%를 쓰고 있는 순간 병목이 나타날 수 있음.

 

 

3. CPU 100% 이상이면 병목이라는 말의 의미

  • Node는 JS 실행이 메인스레드 1개라서
    그 스레드가 100% 경계에 닿으면 이벤트루프가 밀림 → 응답 지연 발생
  • docker stats의 CPU는 총합이므로 “100% 넘는 것 자체”는 문제 아님

 

 

🔥 병목 판단 기준

  • 이벤트루프 지연(Event loop delay)이 50ms 이상 꾸준하면 병목 가능성 매우 높음
  • docker stats에서 100% 근처에서 계속 흔들리면 JS 메인스레드가 바쁠 확률 높음
  • 요청 처리 중 지연(log, socket emit, API 호출 등)이 자주 생기면 CPU 병목 의심

 

4. 이벤트루프 딜레이 측정하는 법

 
node -e "setInterval(()=>{const d=Date.now();setImmediate(()=>console.log('Delay:',Date.now()-d,'ms'))},1000)"
  • 이 코드는 ‘기존 Node 프로세스의 딜레이’를 보는 게 아니라
    새로운 Node 프로세스를 띄워서 이벤트루프 지연 측정하는 도구임

즉:

  • 실제 서버의 event loop delay를 보는게 아니라
    측정용 Node 인스턴스를 띄워서 CPU 여유를 보는 것에 가까움
  • 컨테이너 전체 부하가 높으면 이것도 delay가 커짐
     환경 부하 측정용으로는 의미 있음

 

5. “기존 실행 중인 Node 프로세스의 event loop delay는 어떻게 보나?”

이미 실행 중인 서버에서 event loop delay를 직접 보는 방법들은 다음임:

1) 직접 프로파일링: clinic doctor

 
npm install -g clinic clinic doctor -- node server.js
  • CPU 프로파일, block되는 부분, heavy 함수 등을 보여줌
  • 단점: 실행 중인 프로세스에 attach 불가 → 재실행해야 함

 

2) Node.js 내에서 event loop delay 측정

server.js에 추가:

 
const { monitorEventLoopDelay } = require('perf_hooks'); const h = monitorEventLoopDelay(); h.enable(); setInterval(() => { console.log('Loop delay', h.mean / 1e6, 'ms'); }, 1000);​
 
 

이러면 현재 서버의 실제 이벤트루프가 얼마나 밀리는지 바로 나옴

6. CPU 병목이 실제로 발생하면 증상

  • Socket 이벤트 응답 느려짐
  • ping/pong 타임아웃 자주 발생
  • API 응답 딜레이 증가
  • Redis 응답은 정상인데 Node만 느려짐
  • 컨테이너 재배포·재시작 시 빨라짐 (일시적인 GC + 이벤트루프 해소)

 

7. 결론

docker stats CPU%는 호스트 전체 기준이라 100% 넘어도 괜찮음

  • Node.js는 JS 메인스레드가 100% 부근 도달하면 병목 가능성 매우 높음
  • 병목 판단은 이벤트루프 딜레이를 기준으로 보는 게 정답
  • delay 50~100ms 이상 지속 시 → 코드 최적화 or 스케일아웃 필요