로깅에 `console.log`를 사용하지 마세요: Vercel 창업자가 경고한 내용
요약
Vercel 창업자가 성능 저하를 유발하는 `console.log` 사용을 경고하며, 프로덕션 환경에서의 로깅 오버헤드를 설명합니다. 특히 타임스탬프 재계산과 반복적인 쓰기 호출이 심각한 성능 병목 현상을 초래할 수 있습니다.
핵심 포인트
- 프로덕션 환경에서 `console.log` 사용은 성능 저하를 유발합니다.
- 타임스탬프는 캐싱하고, 요청 로깅을 배치(batch)하여 오버헤드를 줄여야 합니다.
- 로깅의 속도와 안정성 사이에는 트레이드오프가 존재합니다 (즉시 쓰기 vs. 버퍼링).
저는 AI를 사용하여 형식과 문법을 교정했습니다.
최근 Pooya Parsa(@_pi0_)의 X 스레드를 접하게 되었습니다. 그는 nitrojsdev, unjs 등 여러 라이브러리의 오픈 소스 개발자입니다. 그는 자바스크립트 웹 개발 생태계가 직면한 병목 현상 문제를 해결하려고 노력하고 있습니다. 많은 프레임워크들이 이러한 고유 효용성 라이브러리를 구축하는 데 이 오픈 소스 개발자들에게 의존합니다.
// Detect dark theme var iframe = document.getElementById('tweet-2078164967367295257-77'); if (document.body.className.includes('dark-theme')) { iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=2078164967367295257&theme=dark" }
사용하는 API는 빠릅니다. 데이터베이스가 최적화되어 있고, 응답은 캐시되며, 각 요청은 밀리초 내에 완료됩니다. 그러다가 하찮게 보이는 한 줄을 추가합니다:
console.log(`${req.method} ${req.url}`);
이것이 얼마나 나쁠 수 있을까요?
srvx의 오래된 요청 로거는 로그를 /dev/null에 작성할 때 처리량을 약 21% 감소시켰습니다. 이 결과가 중요한 이유는 /dev/null은 모든 것을 폐기하기 때문입니다. 느린 디스크, 원격 로깅 서비스 또는 화려한 터미널 출력이 관련되어 있지 않습니다. 애플리케이션이 단순히 타임스탬프를 생성하고, 문자열을 구축하며, 출력 스트림을 호출하는 것만으로 성능 손실을 겪고 있었습니다.
터미널에 직접 로깅할 때는 성능 손실이 34%에 달했습니다.
타임스탬프가 너무 많은 작업을 수행함
오래된 로거는 지역화(locale-aware) 날짜 포맷팅을 사용하여 모든 요청에 대해 형식화된 타임스탬프를 생성했습니다. 서버가 초당 수천 개의 요청을 처리하는 경우까지는 사소해 보입니다.
이러한 요청들 중 대부분은 동일한 표시되는 초를 공유하지만, 타임스탬프는 계속해서 재계산됩니다.
새로운 로거는 1초 동안 형식화된 시간을 캐시합니다. 자바스크립트의 Intl 메커니즘에 같은 값을 수천 번 다시 만들라고 요청하는 대신, 한 번 계산하고 재사용합니다.
작은 변화가 큰 회복을 가져옵니다.
20개보다 1번 쓰는 것이 더 저렴함
이 로거는 또한 쓰기 전에 라인을 배치(batch)합니다.
다음과 같은 방식 대신:
요청 → 쓰기
요청 → 쓰기
요청 → 쓰기
...
이것은 다음과 같은 방식으로 작동합니다:
요청 ─┐
요청 ─┤
요청 ─┼── 버퍼(buffer) ── 한 번의 쓰기
...
지연 시간은 단지 하나의 이벤트 루프(event-loop) 주기 정도이지만, 출력 스트림에 대한 호출을 크게 줄여줍니다. 따라서 타임스탬프 캐싱과 배치 쓰기(batched writes)를 결합하면 로그가 전송되는 위치에 따라 로깅 오버헤드를 약 60–85%까지 절감할 수 있습니다.
더 빠른 로깅은 어색한 비용을 가집니다
길레르모 라우흐(Guillermo Rauch): 만약 로그가 메모리에 남아있는 동안 프로세스가 충돌하면 어떻게 될까요?
정상적인 종료 중에는 로거가 버퍼를 비울 수 있습니다. 하지만 SIGKILL이나 OOM kill이 발생하면 JavaScript는 정리할 시간(cleanup window)을 얻지 못합니다. 마지막 몇 개의 로그가 사라질 수 있으며—그리고 짜증스럽게도, 그것들이 충돌 원인을 설명하는 데 필요한 정확한 로그일 수 있습니다.
이것이 트레이드오프입니다: 즉각적인 쓰기(immediate writes)는 더 안전하지만 느리고; 버퍼링된 쓰기(buffered writes)는 더 빠르지만 마지막 이벤트 루프 틱을 잃을 수 있습니다.
console.log()는 로컬 디버깅이나 작은 스크립트에는 괜찮습니다. 프로덕션 로깅은 다릅니다. 구조화된 필드(structured fields), 로그 레벨(log levels), 마스킹(redaction), 백프레셔 처리(backpressure handling), 버퍼링, 그리고 명확한 실패 보장(clear failure guarantees)이 필요합니다.
진짜 교훈은 console.log()가 만능으로 나쁘다는 것이 아닙니다. 그것은 핫 패스(hot path) 내부의 코드가 얼마나 자주 실행되는지에 따라 평가되어야 한다는 것입니다. 초당 20,000번 반복되는 저렴한 한 줄도 더 이상 저렴하지 않습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기