홈시리즈멘토링

© 2026 정기창. All rights reserved.

본 블로그의 콘텐츠는 CC BY-NC-SA 4.0 라이선스를 따릅니다.

☕후원하기소개JSON Formatter러닝 대기질개인정보처리방침이용약관

© 2026 정기창. All rights reserved.

콘텐츠: CC BY-NC-SA 4.0

☕후원하기
소개|JSON Formatter|러닝 대기질|개인정보처리방침|이용약관

파일시스템이 범인이라던 제 진단도 절반만 맞았습니다 — 링크가 만든 곱셈

정기창·2026년 8월 16일

5편에서 저는 윈도우 러너가 느린 범인을 찾았다고 썼습니다. 네트워크가 아니라 파일시스템이라고요. 무선 랜을 의심했다가 재측정으로 뒤집고, 윈도우가 파일을 하나하나 만드는 데 느리다는 결론에 도착했습니다.

그 결론은 틀리지는 않았습니다. 다만 절반만 맞았습니다. 그리고 그 편에는 제가 설명하지 못한 채 그냥 넘어간 관측이 하나 남아 있었습니다.

진단을 뒤집는 데는 썼지만, 설명은 안 했습니다

5편에서 제 진단을 무너뜨린 것은 두 번째 측정이었습니다. 파일이 이미 다 받아진 상태에서 다시 돌리면 훨씬 빨라져야 하는데, 오히려 더 느렸습니다.

측정 결과
아무것도 없는 상태에서 설치 134초
이미 다 받아진 상태에서 검증 260초

저는 이 한 줄을 "그러니까 네트워크가 범인이 아니다"라는 반증으로만 썼습니다. 반증으로는 충분했고, 그래서 저는 만족하고 다음 결론으로 넘어갔습니다.

그런데 돌이켜 생각해보면 이 숫자는 반증 이상의 것이었습니다. 아무것도 안 바뀐 상태가 새로 만드는 것보다 두 배 비싸다면, 그건 그 자체로 이상한 일입니다. 저는 이상한 것을 발견하고, 그것을 다른 주장을 무너뜨리는 데 쓰고, 정작 그것이 왜 이상한지는 묻지 않았습니다.

설치와 검증은 다른 일을 합니다

이번에 다시 재면서 먼저 확인한 것은 이 두 단계가 실제로 무슨 일을 하는가였습니다.

아무것도 없는 상태에서의 설치는 만드는 일입니다. 목록을 보고 없는 것을 받아서 배치하면 끝입니다. 반면 이미 다 받아진 상태에서 하는 검증은 대조하는 일입니다. 목록에 적힌 파일이 정말 그 자리에 그 모양으로 있는지 경로 하나하나를 확인합니다.

즉 검증은 "만들 것이 없어서 빠른" 작업이 아니라 "확인할 것이 많아서 느린" 작업이었습니다. 만드는 쪽은 없는 것만 만들면 되지만, 대조하는 쪽은 전부를 봐야 합니다. 여기까지 오니 질문이 하나로 좁혀졌습니다. 그래서 확인해야 할 경로가 대체 몇 개인가.

16만 개짜리 폴더를 93만 개로 걷고 있었습니다

세어봤습니다.

항목 수
실제 파일 수 약 16만 개
순회하며 밟는 경로 수 약 93만 개
디렉토리 링크 수 8,412개

파일은 16만 개인데 걷는 경로는 93만 개입니다. 같은 파일을 여러 번 밟고 있다는 뜻입니다. 그 원인이 세 번째 줄입니다.

링크가 만든 곱셈

제가 쓰는 패키지 매니저는 같은 라이브러리를 프로젝트마다 복사해두지 않습니다. 한 곳에 한 번만 저장해두고 필요한 자리마다 링크를 걸어 연결합니다. 디스크를 아끼고, 동시에 "선언하지 않은 라이브러리는 보이지 않게" 만들어 의존성을 엄격하게 관리하려는 설계입니다. 좋은 설계이고, 맥과 리눅스에서는 실제로 잘 동작합니다.

문제는 이 링크를 따라 들어가면 그 안에 또 링크가 있다는 것입니다. 링크 8,412개를 통과하며 걷다 보면, 16만 개짜리 트리가 93만 개 경로로 불어납니다. 그리고 검증은 그 93만 개를 전부 확인합니다.

여기서 5편의 결론이 다시 등장합니다. 윈도우가 파일 하나를 확인하는 데 걸리는 시간은 맥에 비해 확실히 깁니다. 파일당 차이는 미세하지만, 그 미세한 차이에 93만이 곱해집니다.

파일당 확인 비용 밟는 경로
맥 빠름 많음
윈도우 느림 많음

맥은 경로가 똑같이 많지만 한 번의 확인이 싸서 티가 나지 않습니다. 같은 저장소를 12초에 끝냅니다. 윈도우에서만 두 조건이 겹치면서 폭발한 것입니다.

그러니까 정확한 문장은 이렇습니다. 파일시스템은 원인이 아니라 증폭기였습니다. 곱셈을 만든 것은 패키지 매니저의 레이아웃이고, 윈도우는 그 곱셈에 큰 계수를 붙였을 뿐입니다. 5편에서 저는 계수를 보고 원인이라고 불렀습니다.

고친 것은 그 기계의 설정 한 줄이었습니다

이 패키지 매니저는 링크 대신 평평하게 펼치는 방식도 지원합니다. 옛날 방식이고, 의존성 격리를 포기하는 대신 링크가 사라집니다. 그리고 이 설정은 사용자 설정 파일에서 읽히므로 저장소를 건드릴 필요가 없습니다.

# 그 러너 기계의 사용자 설정 파일에 한 줄
node-linker=hoisted

결과입니다.

측정 기존 펼친 뒤
검증 (실제 CI가 매번 하는 것) 260.4초 14.5초
처음부터 설치 134.2초 95.7초
폴더 삭제 129.5초 58.4초
디렉토리 링크 수 8,412 0

18배입니다. 그리고 14.5초는 GitHub이 빌려주는 기계의 17초, 제 맥북의 13초와 사실상 같은 수준입니다. 15배 느리다던 기계가 같은 줄에 섰습니다.

같이 확인한 두 가지 오답

범인을 좁히는 동안 그럴듯해서 검토한 가설이 둘 있었는데, 둘 다 아니었습니다. 5편에서 그럴듯함에 한 번 속았으니 이번에는 재보고 버렸습니다.

백신의 실시간 검사를 의심했습니다. 파일을 하나씩 볼 때마다 백신이 끼어드는 것 아니냐는 것이죠. 그런데 검사 제외 폴더와 그렇지 않은 폴더에서 순회 속도를 재보니 제외한 쪽이 오히려 조금 느렸습니다. 파일을 새로 만들 때는 차이가 났지만, 실제 작업은 만드는 것보다 걷는 것이 지배적이라 영향이 없었습니다.

링크 생성 권한 문제도 의심했습니다. 윈도우에서는 링크를 만드는 데 별도 권한이 필요해서, 권한이 없으면 다른 방식으로 우회한다고 알려져 있습니다. 확인해보니 그 권한은 이미 있었습니다. 그리고 어느 방식으로 만들든 걷는 비용은 같으니, 애초에 답이 될 수 없는 가설이었습니다.

공짜는 아닙니다

평평하게 펼치면 선언하지 않고 쓴 라이브러리가 잡히지 않습니다. 다른 패키지가 데려온 것을 몰래 가져다 쓰고 있어도 그냥 동작합니다. 링크 구조가 막아주던 것이 이 기계에서만 빠지는 셈입니다.

다만 이 설정은 그 러너 한 대에만 걸려 있고, 맥과 hosted 러너와 배포 이미지 빌드는 전부 엄격한 구조를 유지합니다. 마지막 관문은 남아 있는 셈이라 감수할 만하다고 판단했습니다. 저장소 전체 설정을 바꿔야 한다고 처음에 생각했는데, 그건 오판이었습니다.

무엇이 바뀌고 무엇이 그대로인가

기존 판정 상태
무선 랜이 병목이다 기각 유지
랜 어댑터는 살 필요 없다 유지
병목은 윈도우 파일시스템이다 기전 정정 — 증폭기이지 원인이 아님
이 노트북은 무거운 일을 못 맡는다 근거 소멸

마지막 줄이 이 편의 진짜 결과입니다. 5편과 6편에서 제가 그 노트북을 접어둔 이유는 "사양을 올려도 해결되지 않는 구조적 한계"였는데, 구조적 한계가 아니라 설정 하나였습니다.

그리고 병목이 자리를 옮겼습니다

설치가 14.5초가 되자 작업 시간의 구성이 완전히 달라졌습니다.

단계 시간
소스 내려받기 58초
패키지 매니저 준비 46초
의존성 검증 14.5초
테스트 실행 5초

이제 작업 시간의 절반 이상이 테스트를 시작하기도 전의 준비 과정입니다. 다음에 손댈 곳은 의존성이 아니라 그쪽이라는 뜻이고, 저는 이 표를 얻기 전까지 그것을 알 방법이 없었습니다. 가장 큰 것을 치우기 전에는 두 번째로 큰 것이 보이지 않습니다.

돌아보며

5편에서 저는 "무관하지만 참인 사실"을 경계해야 한다고 썼습니다. 공유기가 100Mbps인 것은 사실이었지만 원인이 아니었으니까요.

이번에 걸린 것은 조금 다른 종류였습니다. 관련은 있지만 원인이 아닌 사실입니다. 윈도우 파일시스템이 느린 것은 사실이고, 이 문제와 무관하지도 않았습니다. 실제로 곱셈의 한쪽 항이었습니다. 그래서 더 그럴듯했고, 저는 거기서 멈췄습니다.

돌이켜 생각해보면 멈춘 지점을 알려준 신호는 처음부터 제 표 안에 있었습니다. 워밍이 콜드보다 느리다는 그 한 줄입니다. 저는 그것을 남의 주장을 무너뜨리는 데 썼을 뿐, 내 주장에는 대보지 않았습니다. 남을 반증할 때 쓴 잣대를 자기 결론에도 대봤어야 했다는 생각이 들었습니다.

어쨌든 이제 그 노트북을 접어둘 이유가 사라졌습니다. 다음 편에서 6편에 껐던 노트북을 다시 켜고, 무거운 작업까지 맡길 수 있는지를 실제로 돌려봅니다. 거기서 제가 만난 것은 성능 문제가 아니었습니다.

pnpmWindowsNTFSself-hosted runner성능 분석CInode_modules

관련 글

러너가 느린 범인을 Wi-Fi로 지목했다가 틀렸습니다 — 워밍이 콜드보다 느렸던 이유

윈도우 러너가 hosted보다 15배 느렸습니다. 공유기가 100Mbps라는 증거까지 찾아내 네트워크를 범인으로 지목했는데, 캐시가 채워진 상태의 재측정이 콜드보다 느리면서 진단이 통째로 뒤집혔습니다.

관련도 95%

캐시를 껐더니 CI가 빨라졌습니다 — 최적화가 손해가 되는 조건

의존성 캐시를 끄자 작업당 4분이 사라졌습니다. 캐시는 매번 새로 만들어지는 기계를 전제로 설계된 장치인데, 제 러너는 사라지지 않는 기계였습니다. 전제가 다르면 최적화는 순손실이 됩니다.

관련도 95%

껐던 노트북을 다시 켰습니다 — 막고 있던 것은 성능이 아니었습니다

윈도우 러너에 무거운 작업을 맡길 수 없다고 믿었는데, 막고 있던 것은 성능이 아니라 코드베이스가 리눅스를 전제하고 쓴 문법이었습니다. 그리고 그날 내린 보류 결정은 지적 한 줄에 그날 무너졌습니다.

관련도 95%