JavaScript 툴링 분야의 대부분의 성능 주장은 우유의 유통기한만큼이나 짧습니다. 누군가 한가한 화요일 아침에 패키지 몇 개를 설치하고, 터미널 출력을 캡처하여 극적인 막대 그래프를 게시합니다. 다음 스프린트가 되면, 도구 중 하나가 패치 버전을 출시하며 그 결과 전체를 무효화해 버립니다. 하지만 해당 게시물은 검색 엔진에 계속 인덱싱되어 있고, 차트는 계속 공유됩니다. 그러나 그 수치는 이미 당신을 속이고 있습니다.
이것이 거의 모든 패키지 매니저 벤치마크를 좀먹는 부패입니다. 우리가 필요로 하는 것은 실시간 피드인데, 이들은 사건 현장 사진만을 찍고 있습니다.
depjs/canary라는 프로젝트는 이 문제를 콘텐츠 캘린더 작업이 아닌 기계의 책임으로 다룹니다. 이는 npm, pnpm, Yarn, dep를 모니터링하다가, 이 중 어느 하나라도 새 버전을 게시하는 즉시 전체 테스트 스위트를 다시 실행하는 살아있는 벤치마크입니다. 결과는 공개적이고, 지속적이며, 피할 수 없습니다. 무언가 잘못되면 저장소는 문제가 해결될 때까지 빨간색(실패) 상태를 유지합니다. 체리피킹도 없고, 오래된 블로그 게시물 뒤에 숨는 것도 없으며, 지난달의 승자가 여전히 왕좌를 지키고 있을 것이라는 가정도 하지 않습니다.
속도 주장에 유통기한이 필요한 이유
JavaScript 패키지 매니저는 멈춰 있지 않습니다. 마이너 버전 간의 차이에는 재작성된 해결(resolution) 알고리즘, 변경된 호이스팅(hoisting) 전략, 또는 글로벌 캐시 키 지정 방식의 변경 등이 포함될 수 있습니다. npm 10.2.1과 pnpm 8.11.0을 캡처한 벤치마크는 두 번의 릴리스 후에 동일한 도구들이 어떻게 동작하는지에 대해 거의 아무것도 알려주지 않습니다. 그럼에도 웹에는 정확히 그런 고정된 스냅샷을 기반으로 "도구 X가 세 배 더 빠르다"와 같은 확정적인 문구들이 넘쳐납니다.
설상가상으로, 많은 테스트가 실제 개발자가 겪는 고충을 정의하는 조건들을 무시합니다. 패키지 매니저가 최적화된(warm) 환경에서는 설치를 순식간에 끝낼 수 있지만, 빈 디스크로 시작하는 CI 러너에서는 거북이걸음을 할 수도 있습니다. 두 가지 극단적인 상황을 모두 테스트하지 않는다면, 그 벤치마크는 유용한 엔지니어링 데이터가 아닌 보도 자료에 불과하게 됩니다.
Canary가 비교를 자동화하는 방법
2시간마다 작업이 npm 레지스트리를 폴링합니다. npm, pnpm, Yarn 또는 dep의 새 버전이 나타나면 canary가 깨어납니다. 사람이 변경 로그를 확인할 때까지 기다리지 않습니다. 즉시 네 가지 매니저를 다섯 가지 인기 있는 실제 패키지와 대조하는 전체 테스트 매트릭스를 실행합니다. 선택된 패키지에는 실제 개발자들이 매일 설치하는 React, Next.js, Vite와 같은 주요 라이브러리가 포함됩니다. 이는 특정 도구를 돋보이게 하기 위해 설계된 인위적인 마이크로 프로젝트가 아닙니다.
이러한 릴리스 트리거 방식이 중요한 이유는 측정을 변화와 직접적으로 연결하기 때문입니다. 만약 벤치마크가 야간 스케줄로만 실행된다면, 낮 시간의 핫픽스를 놓치거나 몇 시간 동안 회귀(regression) 문제를 방치할 수 있습니다. 새 버전에 맞춰 실행함으로써, canary는 매번 직접적인 질문을 던집니다. "이번 릴리스가 상황을 더 좋게 만들었는가, 아니면 나쁘게 만들었는가?"
서로 다른 역량을 시험하는 네 가지 시나리오
테스트 매트릭스는 여러분이 익히 알고 있는 워크플로우에 직접 대응하는 네 가지 별개의 설정으로 구성됩니다.
- 콜드 캐시, 락파일 없음 (Cold cache, no lockfile): 새 노트북에서 처음 클론을 받거나,
node_modules를 삭제한 후 첫 설치를 하는 상황입니다. 캐시된 것도 없고, 고정된 것도 없습니다. 패키지 매니저는 모든 것을 처음부터 해결(resolve), 가져오기(fetch), 쓰기(write) 해야 합니다. - 웜 캐시, 락파일 있음 (Warm cache, with lockfile): 모든 것이 제대로 작동할 때의 CI를 위한 이상적인 경로(happy path)입니다. 로컬에 락파일이 존재하고, 캐시에는 이전 실행에서 사용한 tarball이 남아 있습니다. 대부분의 결정이 이미 내려져 있으므로 도구는 빠르게 움직여야 합니다.
- 콜드 캐시, 락파일 있음 (Cold cache, with lockfile): 락파일은 존재하지만 캐시는 삭제된 상태입니다. 매니저는 의존성 해결(dependency resolution) 단계는 건너뛸 수 있지만, 네트워크를 통해 모든 바이트를 여전히 다운로드해야 합니다. 이는 네트워크 속도와 해결 속도를 분리하여 측정합니다.
- 웜 캐시, 락파일 없음 (Warm cache, no lockfile): 캐시는 준비되어 있지만 락파일이 없습니다. 패키지 매니저는 파일을 추출하기 시작하기도 전에 의존성 트리를 다시 해결해야 합니다. 이는 이상적인 네트워크 조건에서 솔버(solver)와 메타데이터 파서의 효율성을 테스트합니다.
각 시나리오는 다섯 번씩 실행되며, canary는 그중 중앙값(median)을 기록합니다. 이 단 하나의 선택이 수많은 노이즈를 제거합니다. 일시적인 네트워크 장애나 레지스트리 지연 시간의 짧은 급증이 전체 흐름을 왜곡할 수 없게 만듭니다. 이상치는 무시되고, 전형적인 경험만이 기록됩니다.
스모크 테스트가 빈 타이머보다 낫다
결과를 검증하지 않는다면 단순한 속도는 속이기 쉽습니다. 패키지 매니저가 postinstall 단계를 건너뛰거나, 몇 개의 심볼릭 링크를 손상시키거나, 잘못된 버전을 설치하고도 인상적인 타임스탬프를 기록할 수 있기 때문입니다. canary는 단순히 타이머에서 멈추지 않습니다. 설치가 완료된 후, 실제로 설치된 코드를 실행하여 테스트합니다.
예를 들어, Express 애플리케이션을 구동하고 서버가 예상된 포트에서 리스닝을 시작하는지 확인합니다. 코드가 실행되지 않으면 벤치마크는 즉시 실패합니다. 이러한 스모크 테스트는 이 스위트를 단순한 경주가 아닌 검증(audit)의 과정으로 탈바꿈시킵니다. 이는 속도만으로는 답할 수 없는 질문, 즉 "설치가 실제로 제대로 작동하는가?"에 대한 답을 제공합니다.
기능으로서의 철저한 정직함
canary의 저자는 대부분의 벤치마크 작성자들이 선택 사항으로 취급하는 세 가지 규칙을 프로세스에 포함했습니다.
동일한 운동장. 도구 간의 동작을 표준화하기 위해 플래그(flags)를 사용합니다. 만약 어떤 패키지 매니저가 기본 설정을 통해 성능 결함을 숨긴다면, 이 벤치마크는 해당 도구가 우연히 좋아 보이게 두는 대신 그 결함을 드러냅니다.
진정한 콜드 스타트. 첫 번째 실행뿐만 아니라 매 반복 실행 전마다 npm 캐시와 pnpm 스토어를 삭제합니다. 여기서 "매번(every)"이라는 단어는 매우 중요한 역할을 합니다. 많은 벤치마크가 캐시를 한 번만 비운 뒤 다섯 번의 설치를 연속으로 실행합니다. 이 경우 두 번째부터 다섯 번째 실행은 진정한 콜드 스타트가 아니며, 수치는 그에 따라 부풀려집니다. canary는 매번 제로 상태에서 시작합니다.
공개된 실패 상태. 새로운 릴리스로 인해 문제가 발생하면, 저장소는 빨간색 실패 상태로 유지됩니다. 수정 사항이 배포될 때까지 메인 페이지에 해결되지 않은 상태로 그대로 노출됩니다. 대시보드를 초록색으로 유지하기 위해 실패를 조용히 숨기는 일은 없습니다. 이 정책은 가시성을 강제합니다. 도구를 평가하는 사용자는 어떤 도구가 가장 빠른지뿐만 아니라, 어떤 도구가 시간이 지나도 신뢰성을 유지하는지도 확인할 수 있습니다.
검사 가능성은 타협할 수 없는 요소입니다
재현할 수 없는 벤치마크는 선거 슬로건에 불과합니다. canary는 누구나 스위트의 특정 부분을 로컬에서 실행할 수 있는 단일 bash 스크립트를 통해 이 문제를 해결합니다. 클라우드 제공업체의 네트워크나 관리자가 수동으로 조정한 환경을 신뢰할 필요가 없습니다. 수치가 잘못되었다고 의심된다면, 직접 수치를 생성할 수 있습니다.
이러한 투명성은 프로젝트를 관리자들에게도 유용하게 만듭니다. 회귀(regression) 문제가 발생하면, 다운스트림 개발자는 스크립트를 가져와 도구의 릴리스를 이분 탐색(bisect)하여 업스트림 팀에
