Quick Reference
import { performance, PerformanceObserver } from "node:perf_hooks";
const observer = new PerformanceObserver((items) => {
for (const entry of items.getEntries()) {
console.log(entry.name, entry.duration);
}
});
observer.observe({ entryTypes: ["measure"] });
async function measure(name, task) {
const start = `${name}:start`;
const end = `${name}:end`;
performance.mark(start);
try {
return await task();
} finally {
performance.mark(end);
performance.measure(name, start, end);
performance.clearMarks(start);
performance.clearMarks(end);
}
}
const config = await measure("load config", async () => ({ port: 3000 }));
console.log(config);한두 구간의 경과 시간만 보면 performance.now(), 이름 붙은 구간을 수집하려면 mark()와 measure(), 이미 분리된 함수 경계를 계측하려면 timerify()를 씁니다. 고빈도 경로에서는 mark와 measure가 쌓이지 않게 정리하고, metric에는 사용자 ID나 URL 전체처럼 cardinality가 무한히 늘어나는 값을 넣지 않습니다.
측정 단위
performance.now()는 프로세스 안에서 단조 증가하는 고해상도 시간을 제공합니다. 벽시계 시간인 Date.now()와 달리 시스템 시간이 바뀌는 문제를 덜 받으므로 짧은 구간 성능 측정에는 performance.now()가 더 적합합니다.
const startedAt = performance.now();
await runJob();
const durationMs = performance.now() - startedAt;반복 측정과 관측성 연결이 필요하면 mark와 measure가 낫습니다. 시작점과 종료점을 이름으로 남기고, 측정 결과를 observer나 로깅 계층에서 처리할 수 있습니다.
performance.mark("query:start");
await queryDatabase();
performance.mark("query:end");
performance.measure("db query", "query:start", "query:end");언제 쓸까
부팅 단계, 설정 로딩, DB query wrapper, 외부 API 호출 wrapper처럼 병목 후보가 명확한 곳에 먼저 넣습니다. 모든 함수에 무작정 측정을 넣기보다 "느린 요청의 어느 단계가 느린가"를 분리할 수 있는 경계에 붙이는 편이 효과적입니다.
async function timed(name, task) {
const startedAt = performance.now();
try {
return await task();
} finally {
console.log(name, performance.now() - startedAt);
}
}관측성 시스템과 연결하려면 PerformanceObserver에서 entry를 모아 로그, metric, diagnostics channel로 넘길 수 있습니다. 측정 코드는 비즈니스 로직의 반환값을 바꾸지 않아야 하며, 오래 사는 observer가 필요 없어진 시점에는 disconnect()로 해제합니다.
timerify
performance.timerify(fn)은 함수 호출 시간을 performance entry로 남깁니다. 이미 함수 경계가 분명하고 observer 기반으로 수집하고 싶을 때 적합합니다.
import { performance, PerformanceObserver } from "node:perf_hooks";
const observer = new PerformanceObserver((items) => {
console.log(items.getEntries());
});
observer.observe({ entryTypes: ["function"] });
const measured = performance.timerify(async function loadUser(id) {
return findUser(id);
});
await measured("u1");단순한 한두 구간은 performance.now()가 더 읽기 쉽습니다. 반복적인 계측과 통합 수집이 필요할 때 mark, measure, observer 쪽으로 확장합니다.
주의할 점
성능 측정 코드는 비용이 0이 아닙니다. hot path에 과도하게 mark를 남기면 측정 자체가 지연과 메모리 사용을 만들 수 있습니다. 샘플링, 경계 축소, observer 정리를 함께 설계하십시오.
측정값은 원인 그 자체가 아니라 단서입니다. CPU 바운드 지연, event loop 지연, 네트워크 대기, DB 대기 시간이 같은 숫자로 보일 수 있으므로 구간 이름을 구체적으로 나누고, 필요한 경우 event loop delay나 외부 metric과 함께 봐야 합니다.
참고 링크
1 sources