본문 바로가기
C.W.K.
Stream
Lesson 02 of 05 · published

성능 프로파일링 — 시간이 실제로 어디 가는지

~12 min · production, profiling, performance, inspect

Level 0노드 입문자
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"프로파일링은 느릴 것 같은 코드를 찾는 일이 아니야. 실제 워크로드에서 시간이 어디에 쓰이고 무엇이 메모리에 남는지 측정하는 일이야."

먼저 어떤 자원을 볼지 정해

  1. CPU: JavaScript 실행 시간이 어느 함수에 모이는지, 긴 동기 작업이 이벤트 루프를 막는지 본다.
  2. 메모리: 무엇이 할당되고 계속 참조되는지, 할당 속도와 가비지 컬렉션이 지연에 영향을 주는지 본다.
  3. 이벤트 루프와 I/O: 루프 지연, 외부 서비스 대기, 파일·DNS·암호화 작업의 동시성처럼 CPU 프로파일만으로 설명되지 않는 시간을 본다.

도구를 켜기 전에 재현할 요청과 비교할 지표를 정해. 평균만 보지 말고 p95나 p99 지연, 처리량, 오류율도 함께 기록해야 해. 변경 뒤에 같은 조건으로 다시 측정할 수 있어야 최적화가 실제로 효과가 있었는지 판단할 수 있어.

Inspector와 내장 프로파일 플래그

node --inspect server.mjs로 Inspector를 열면 Chrome DevTools 같은 호환 클라이언트에서 프로세스에 연결할 수 있어. CPU 프로파일을 녹화하고 힙 스냅샷을 비교하며 실행 중인 코드를 디버깅할 수 있지. Inspector 포트는 코드 실행 권한으로 이어질 수 있으므로 외부 네트워크에 공개하면 안 돼.

대화형 도구가 필요 없다면 --cpu-prof로 샘플링 CPU 프로파일을 파일에 남길 수 있어. --heap-prof는 할당을 샘플링한 힙 프로파일을 만들며, 특정 시점의 모든 객체를 담는 힙 스냅샷과는 목적이 달라. 메모리 누수를 찾을 때는 시간 간격을 둔 스냅샷이나 할당 프로파일을 증상에 맞춰 골라.

이벤트 루프 지연도 함께 측정해

node:perf_hooksmonitorEventLoopDelay()는 이벤트 루프가 예정된 시점보다 얼마나 늦게 돌아왔는지 분포로 보여 줘. CPU 사용률이 높지 않아도 긴 동기 작업이나 과도한 콜백이 있으면 지연이 커질 수 있어. 한편 DB나 외부 API를 기다리는 시간은 이벤트 루프 지연이 아니라 트레이스와 외부 지표에서 드러날 수 있어. 한 신호만으로 원인을 단정하지 마.

도구가 알려 주는 것과 알려 주지 않는 것

Clinic.js 같은 진단 도구는 CPU, 이벤트 루프, 비동기 흐름, 메모리 할당을 보고서로 묶어 탐색을 도와줘. 편리하지만 보고서가 원인을 자동으로 확정해 주는 건 아니야. 사용 중인 Node 버전과 도구 버전이 맞는지 확인하고, 같은 증상을 내장 프로파일과 서비스 지표에서도 교차 확인해.

Flame graph의 넓은 프레임은 샘플이 많이 모인 실행 경로를 뜻해. 그것이 곧 잘못된 코드라는 뜻은 아니야. 서비스의 핵심 작업이라 원래 시간을 많이 쓸 수도 있어. 줄였을 때 처리량이나 꼬리 지연이 개선되는지 재측정해야 해.

자주 만나는 병목

  1. 메인 스레드의 긴 동기 작업: 큰 JSON 파싱, 동기 암호화, 복잡한 정규식, 긴 반복문은 다른 요청 처리를 늦춘다.
  2. 외부 시스템 왕복: N+1 쿼리와 직렬 네트워크 호출은 CPU 프로파일보다 트레이스와 DB 지표에서 더 잘 보인다.
  3. libuv 스레드 풀 경합: 일부 파일·DNS·암호화 작업이 기본 스레드 풀을 공유한다. 크기를 키우기 전에 작업 수와 병렬성부터 측정해야 한다.
  4. 계속 자라는 참조: 제한 없는 캐시나 해제되지 않은 리스너는 힙 사용량을 올린다. 무엇이 객체를 붙잡는지 스냅샷에서 확인한다.
  5. 높은 할당률: 짧게 사는 객체를 대량으로 만들면 가비지 컬렉션 비용이 커질 수 있다. 할당 프로파일로 먼저 확인한다.

운영 신호에서 재현 가능한 실험으로

운영 환경의 메트릭과 트레이스는 어느 경로와 조건에서 문제가 생기는지 알려 줘. 그 단서를 바탕으로 민감한 데이터를 제거한 재현 워크로드를 만들고, 로컬이나 격리된 환경에서 프로파일을 수집해. 운영 프로파일링이 꼭 필요하다면 짧은 시간과 제한된 대상에 샘플링 방식으로 적용하고, 오버헤드와 출력 파일의 민감성을 함께 관리해야 해.

Pippa의 고백

느린 화면을 보면 눈에 보이는 프런트엔드부터 의심하기 쉬워. 하지만 직렬화, 데이터베이스 왕복, 지나치게 큰 응답처럼 원인이 다른 층에 있을 수도 있어. 그래서 이제는 “어디가 느릴 것 같다”보다 “어느 요청의 어느 구간이 얼마나 느렸나”를 먼저 물어. 프로파일은 직감을 없애는 도구가 아니라, 직감을 검증 가능한 가설로 바꾸는 도구야.

Code

Node의 내장 프로파일링 도구·bash
# Inspector에 연결한다. 포트를 외부에 공개하지 않는다
node --inspect server.mjs

# 첫 줄에서 멈춰 시작 과정을 디버깅한다
node --inspect-brk server.mjs

# 샘플링 CPU 프로파일을 파일로 남긴다
node --cpu-prof --cpu-prof-dir=./profiles server.mjs

# 할당 샘플 기반 힙 프로파일을 남긴다
node --heap-prof --heap-prof-dir=./profiles server.mjs
Clinic.js로 진단 범위를 좁히는 예·bash
# 실제 요청과 비슷한 부하를 준비한 뒤 진단한다
npx clinic doctor --on-port \
  'autocannon -d 30 localhost:3000' -- node server.mjs

# CPU 실행 경로를 flame graph로 본다
npx clinic flame --on-port \
  'autocannon -d 30 localhost:3000' -- node server.mjs

# 비동기 작업 사이의 관계를 살핀다
npx clinic bubbleprof --on-port \
  'autocannon -d 30 localhost:3000' -- node server.mjs

External links

Exercise

작은 Node 서비스에 의도적으로 느린 경로를 하나 만들고 기준 지연과 처리량을 기록해. 같은 부하를 주면서 --cpu-prof 또는 Clinic.js로 프로파일을 수집한 뒤 가장 넓은 실행 경로를 찾아. 한 가지 변경만 적용하고 같은 조건으로 다시 측정해 개선 여부를 수치로 설명해.
Hint
CPU를 쓰는 느린 경로와 setTimeout이나 외부 요청을 기다리는 느린 경로는 프로파일에서 다르게 보여. CPU 프로파일에 뚜렷한 hot function이 없다면 이벤트 루프 지연, 트레이스, 외부 시스템 지표를 함께 살펴봐.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고
💛 by 똘이warm

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.