FEInterview Prep

Velog

Node.js 애플리케이션 프로파일링

Node.js CPU 사용률이 100%로 튈 때, --prof·Chrome DevTools·Inspector API·perf + FlameGraph 네 가지 도구로 핫스팟을 찾는 방법. CPU bound vs I/O bound 구분이 출발점.

2025-08-18·8분 읽기
Node.js성능
원문 보기 ↗

핵심 요약

Fastify /register 엔드포인트에서 crypto.pbkdf2Sync 동기 호출이 이벤트 루프를 막는 시나리오로, 4가지 프로파일링 도구를 단계적으로 사용한다. (1) node --prof 가 만드는 V8 로그 → --prof-process 로 사람이 읽을 수 있는 텍스트 변환, (2) node --inspect + Chrome DevTools 로 Performance 탭 FlameChart, (3) node:inspector/promises API 로 SIGUSR1/SIGUSR2 시그널 기반 프로파일링, (4) Linux perf record + Brendan Gregg FlameGraph 로 SVG 시각화. 모든 결과가 "pbkdf2Sync 가 CPU 시간 99%"로 수렴한다.

Node.js 성능 디버깅의 핵심은 "CPU bound인가 I/O bound인가" 를 먼저 가르는 것이다. CPU bound면 단일 스레드 이벤트 루프가 막혀서 다른 요청이 줄줄이 대기한다. 프로파일링은 "어느 함수가 이벤트 루프를 잡고 있는가"를 시각적으로 답해주는 도구다.

면접에서 "Node.js 성능을 어떻게 보냐"고 물었을 때 "CPU 모니터링한다"는 답은 약하다. 샘플링 프로파일러가 어떻게 동작하는지, 콜 스택을 bottom-up 으로 읽는 법, FlameGraph 의 가로/세로 의미를 설명할 수 있어야 한다. 이 글은 도구 4가지를 동일한 데모(pbkdf2Sync 핫스팟)로 비교해 학습 곡선을 짧게 만들어 준다.

학습 포인트

면접 답변으로 연결할 학습 포인트입니다.

CPU bound vs I/O bound 구분이 출발점

I/O bound (네트워크·디스크·DB) 는 비동기로 위임되어 이벤트 루프를 막지 않지만, CPU bound (해시·이미지·암호화·복잡 연산) 는 직접 이벤트 루프 위에서 돈다. CPU 100% 가 곧 "내 자바스크립트 코드가 막고 있다"는 신호.

event loopCPU boundI/O bound이벤트 루프 차단
자주 하는 오해

CPU 사용률 모니터링만 보고 끝내기. 어떤 함수인지까지 가야 진짜 원인을 안다.

`--prof` 와 `--prof-process` 의 흐름

node --prof index.js 는 V8 샘플링 프로파일러를 켠다. 종료 후 isolate-0x...-v8.log 파일이 생성되며, node --prof-process <log> 로 사람이 읽을 수 있는 형태로 변환한다. [Summary] / [JavaScript] / [Bottom up] 세 섹션을 차례로 본다.

V8 sampling profiler--prof-processBottom up profile
자주 하는 오해

isolate-*.log 를 그대로 열어 "읽을 수 없다"고 포기하기. 반드시 --prof-process 로 후처리해야 의미가 보인다.

Chrome DevTools 로 빠르게 시각화

node --inspect index.jschrome://inspect → Performance 탭. FlameChart 의 가로축은 시간, 세로축은 호출 깊이. "Bottom-Up" 탭으로 보면 함수별 누적 시간 순으로 정렬되어 핫스팟을 즉시 찾는다.

chrome://inspectFlameChartBottom-Up
자주 하는 오해

FlameChart의 "가장 넓은 박스"가 항상 문제라고 단정. 실제로는 호출 깊이와 누적 시간을 함께 봐야 정확하다.

Inspector API + 시그널 기반 프로파일링

프로덕션 프로세스를 재시작하지 않고 SIGUSR1/SIGUSR2 로 프로파일을 시작/중단. 결과는 .cpuprofile 로 떨어져 Chrome "Load profile"로 읽을 수 있다.

import * as inspector from 'node:inspector/promises';
const session = new inspector.Session();
session.connect();
await session.post('Profiler.enable');
await session.post('Profiler.start');
// ... 부하 ...
const { profile } = await session.post('Profiler.stop');
await fs.writeFile('./profile.cpuprofile', JSON.stringify(profile));
node:inspector/promisesSIGUSR1.cpuprofile
자주 하는 오해

프로덕션에서 --inspect 를 켠 채 두는 것. 디버깅 포트 노출은 보안 사고의 시작이 된다 — 시그널 방식이 안전한 이유.

`perf` + FlameGraph 로 시스템 레벨까지

node --perf-basic-prof index.js & 로 컴파일된 함수 이름 매핑을 만들고, sudo perf record -F 99 -p <pid> -g. 결과를 perf script | stackcollapse-perf.pl | flamegraph.pl > out.svg 로 시각화하면 JS + V8 + libc + 커널 까지 한 그림에 보인다.

perf record--perf-basic-profFlameGraph SVG
자주 하는 오해

FlameGraph 가로폭을 "실행 시간"으로 오해. 실제로는 "샘플 채취 횟수"이고 결국 비슷하지만, 셈플레이트에 따라 해석이 달라진다.

읽는 순서

  1. 1이론

    이벤트 루프 페이즈, 샘플링 vs 인스트루멘테이션 프로파일링, FlameGraph 의 축 의미를 정리.

  2. 2구현

    글의 데모 저장소를 클론해 (1) --prof, (2) Chrome DevTools, (3) Inspector API + 시그널, (4) perf + FlameGraph 네 도구를 모두 돌려보고 결과를 비교.

  3. 3실무

    사내 서비스에 perf_hooks.monitorEventLoopDelay 모니터링을 붙이고, p99 지연이 50ms 이상일 때 Inspector API 로 자동 프로파일을 찍는 운영 루프를 만든다.

  4. 4설명

    "pbkdf2Sync 가 99% CPU" 같은 결과를 동료에게 5분 안에 설명할 수 있도록 FlameGraph 캡처 + 콜 스택 해석을 정리.

면접 연결 질문

hardNode.js 서버 응답이 느려졌습니다. 어떻게 원인을 좁혀가나요?
힌트

[감점 답변] "console.log 로 시간 찍어본다". [좋은 답변] (1) CPU vs I/O 구분: CPU 100% 면 동기 작업 의심, 낮으면 외부 의존성/락 의심. (2) 샘플링 프로파일링: --prof 또는 --inspect 로 핫 함수 찾기. (3) 이벤트 루프 지연 측정: perf_hooks.monitorEventLoopDelay. (4) GC 압력: --trace-gc. 단계별 도구를 결합해 답하면 깊이가 보인다.

medium샘플링 프로파일러와 인스트루멘테이션 프로파일러의 차이는 무엇인가요?
힌트

[감점 답변] "하나는 가볍고 하나는 무겁다". [좋은 답변] 샘플링(V8 --prof, perf)은 일정 주기로 콜 스택을 캡처해 통계적 추정 → 오버헤드 작고 프로덕션에서도 가능. 인스트루멘테이션(console.time, APM 트레이스)은 모든 호출을 기록 → 정확하지만 오버헤드 큼. 어떤 도구가 어떤 상황에 맞는지 트레이드오프로 답한다.

medium`crypto.pbkdf2Sync` 같은 동기 함수가 이벤트 루프에 미치는 영향과 해결책은?
힌트

[감점 답변] "비동기로 바꾸면 된다". [좋은 답변] 동기 함수는 호출 동안 다른 요청 모두 블로킹 → 처리량 급락. 해결: (1) crypto.pbkdf2(콜백/Promise) 또는 argon2/bcrypt async 버전, (2) Worker Threads 로 CPU 작업 격리, (3) 너무 무거우면 별도 워커 큐(BullMQ 등)로 분리. "왜 비동기로 바꿔야 하는가"를 이벤트 루프 단일 스레드 사실과 연결해 설명.

자기 점검

FlameChart 에서 "가장 위에 있는 박스"가 의미하는 것은?
가장 안쪽 호출최종 실행 함수현재 실행 중
자주 하는 오해

"가장 위 = 가장 비싼"이라는 오해. 위쪽은 호출 깊이, 폭이 비용을 나타낸다.

왜 프로덕션에서 `node --inspect` 를 항상 켜두면 안 되나요?
디버거 포트원격 코드 실행보안성능 오버헤드
자주 하는 오해

"읽기 전용"이라고 안전하다고 생각. 디버거 프로토콜은 임의 코드 실행이 가능해 노출 자체가 사고.