npm을 (우연히) 망가트린 패키지 (번역)
원본: https://uncenter.dev/posts/npm-install-everything/
(원본은 2024년 3월 1일에 작성되었습니다.)
10년 전, PatrickJS(패트릭)는 NPM 레지스트리가 생긴 후 처음 5년 동안 등록되었던 모든 패키지를 담은 everything 패키지를 NPM에 만들었습니다. 이 패키지는 수년 동안 그대로 유지되었지만, 불과 며칠 전 트윗 하나로 모든 것이 바뀌었습니다.

완벽한 저장소는 존재하지 않는다…
저는 타임라인에서 해당 트윗을 보고 몇 가지 사항을 수정하고 저장소를 최신 상태로 유지하기 위해 빠르게 PR을 제출했습니다. 마침 그 무렵 패트릭은 패키지의 2.0.0 버전을 게시하려고 시도했지만, 압축 해제된 패키지 크기에 10MB 제한이 있다는 것을 발견했습니다. 저는 이 문제에 대해 댓글을 달았고, 우리는 곧바로 해결책을 모색하기 시작했습니다.
해결책 찾기
우리는 트위터 DM으로 대화를 옮겼고, 그 무렵 Trash의 트윗을 본 Hacksore와 Trash 본인을 포함한 다른 사람들도 참여하고 싶어했습니다. 우리는 약 250만 개의 패키지를 “범위”별로 그룹화하는 계획을 세웠습니다. 알파벳 “a”로 시작하는 패키지, “b”로 시작하는 패키지, 그리고 나머지 알파벳으로 시작하는 패키지, “0”부터 “9”까지 숫자로 시작하는 패키지, 마지막으로 그 외 모든 패키지를 “기타” 범주로 나누었습니다. 각 범위별 패키지는 전체 패키지의 일부만을 포함하므로 크기 제한을 쉽게 초과할 수 있으며, 모든 것을 포함하는 메인 패키지는 이러한 범위별 패키지 각각에 의존하도록 만들 수 있었습니다.

예상치 못한 문제
필요한 패키지를 생성하는 코드를 작성하기 시작했고, 몇 시간 후 모든 준비가 완료되었습니다. 그런데 한 가지 중요한 사실을 잊고 있었습니다. 아니, 정확히 말하면 NPM이 알려주지 않은 사실이었습니다. NPM에는 패키지가 가질 수 있는 의존성 개수에 제한이 있다는 것을 알게 되었습니다. 그리고 우리는 그 제한을 훨씬 초과했던 것입니다. NPM에는 이 제한에 대한 명확한 문서가 없었고, 공개된 소스 코드에서도 제한을 확인할 수 없었습니다(레지스트리는 비공개이기 때문입니다). 그래서 Hacksore가 직접 테스트를 진행한 결과, 제한이 800개라는 것을 발견했습니다. 현재 스코프 패키지당 9만 개에서 30만 개의 의존성을 가지고 있는 상황에서… 새로운 계획이 필요했습니다.
원점으로 돌아가야겠군
저는 아주 기본적인 새로운 계획을 제안했습니다. 즉, 종속성을 800개씩 “덩어리”(그룹)로 나누는 것입니다.

이렇게 하면 3246개의 그룹이 남게 되는데, 이 또한 저희의 메인 패키지에 담기에는 너무 많습니다. 그래서 800개씩 묶인 3246개의 그룹을 다시 800개씩 묶는 방식으로 처리합니다.

3… 2… 1… 시작!
새로운 계획을 확정하고 코드를 업데이트한 후 GitHub Actions 워크플로를 실행했습니다.

3… 2… 1…
성공했습니다! GitHub Actions 로그가 패키지가 천천히 게시됨에 따라 하나씩 차례로 기록되었습니다. GitHub Actions 작업과 워크플로에 최대 시간이 있다는 것을 알고 잠시 걱정했지만, 간단한 계산을 통해 걱정할 필요가 없다는 것을 알았습니다. 워크플로 작업은 6시간 후에 시간 초과되는데, 현재 약 4.5초마다 패키지 하나씩 게시되는 속도로는 그 시간 안에 4,800개 이상의 패키지를 충분히 게시할 수 있습니다.

우리는 모두 각자 다른 일을 하러 돌아갔고, 저는 가끔씩 로그를 확인했습니다. 그런데 30분 후, 다른 문제가 발생했습니다… 바로 속도 제한에 걸린 것이었습니다. 32분 만에 메인 패키지와 다섯 개의 “청크”를 포함해 총 454개의 패키지를 게시했지만, “서브 청크”는 448개밖에 게시하지 못했습니다. 이는 우리가 게시해야 할 전체 패키지의 극히 일부(약 14%)에 불과했습니다.
다음 단계는?
잠자리에 들기 전에 이미 게시한 패키지는 건너뛰도록 급하게 수정했지만, 속도 제한 문제를 해결할 계획은 여전히 없었습니다. 29일과 30일 사이, 우리는 새로운 계획을 세웠습니다. 주기적으로 가능한 한 많은 패키지를 게시하는 워크플로를 실행하고, 워크플로가 수행한 작업을 저장소에 저장하여 다음 실행 시 이전 실행에서 중단된 부분부터 이어서 진행할 수 있도록 하는 것입니다. 전날 밤의 어설픈 수동 작업을 게시된 패키지를 추적하는 제대로 된 published.json 파일로 교체하고 초기화했습니다. 각 패키지를 게시한 후 published.json 파일에 내용을 기록하는 릴리스 스크립트를 작성하고(물론, 더 나은 방법이 있을 수 있습니다), 워크플로 실행 후 변경 사항을 커밋하는 단계를 추가했습니다. 몇 가지 시행착오 끝에 마침내 제대로 작동했습니다!
그렇게 시작되었습니다. 저는 하루 종일 (매우 불규칙적으로) 수동으로 워크플로우를 진행했습니다. 한동안 우리는 앉아서 기다렸습니다. 심지어 npm install everything를 실행(정확히는 yarn add everything)하고 가상 머신에서 설치 과정을 트위치로 생중계하는 시도까지 했습니다.

그리고 웹사이트도 만들었어요! 지금까지 언급드린 다른 모든 분들께 감사드리지만, 특히 Evan Boehs가 주도적으로 이끌어주셨고 PickleNik이 멋지게 디자인해 주셨습니다.
피날레
마침내 오후 11시 27분, 최종 워크플로 실행이 완료되어 마지막 20개의 하위 청크가 게시되었습니다. 총 5개의 청크, 3246개의 하위 청크, 그리고 모든 것을 담은 everything 패키지까지 모두 게시되었습니다. 총 250만 개가 넘는 NPM 패키지가 포함된 작업입니다!
취약점인가요?
저희의 시도에 대한 초기 반응은… 좋지 않았습니다. 사람들이 저장소에 와서 게시 취소가 안 된다고 불평하기 시작했습니다. 이게 무슨 일이죠?! 조사해 보니, 문제는 “별표” 버전 사용 방식 때문이었습니다. 즉, 일반적인 의미론적 버전 지정 방식인 X.Y.Z 대신 *를 사용하여 버전을 지정한 것입니다. 별표는 패키지의 “모든” 버전을 의미하는데, 바로 여기에 문제가 있습니다. NPM은 다른 패키지가 해당 패키지 버전에 의존하는 경우 패키지 작성자가 패키지를 게시 취소하지 못하도록 차단합니다. 하지만 별표는 모든 버전을 의미하기 때문에 패키지의 모든 버전을 게시 취소할 수 없게 됩니다. 일반적으로는 문제가 없지만, 저희가 (의도치 않게) 대규모로 이 방식을 사용하면서 누구도 게시 취소를 할 수 없게 되었습니다. 저희는 즉시 GitHub에 연락했습니다. 패트릭은 자신의 네트워크와 연락처를 활용하여 GitHub 담당자들과 이야기를 나누었고, NPM의 지원팀과 보안팀에도 여러 차례 이메일을 보냈습니다. 하지만 안타깝게도 이 모든 일이 연휴 기간에 발생하여 NPM/GitHub 팀은 (아마도 휴가 중이었을 것입니다) 응답을 하지 않았습니다. 시간이 남아도는 듯한 낯선 사람들로부터 계속해서 거칠고 무례한 댓글이 쏟아졌습니다… 심지어 어떤 사람은 우리가 더 이상 할 수 있는 일이 없다고 여러 번 말했음에도 불구하고 게시 취소 문제에 대해 1400단어나 되는 장문의 글을 쓰기도 했습니다.
다행히 1월 2일 밤, GitHub에서 연락이 와서 문제를 인지하고 있다고 알려주었습니다. 1월 3일에는 저희 GitHub 조직이 “문제가 있는 것으로 표시”되었고 조직과 저장소가 숨겨졌다는 알림을 받았습니다. 원하던 결과는 아니지만, 어쨌든 진전이 있었습니다.

또한, 저희가 제안했던 대로 NPM에서 저희 조직의 스코프가 지정된 패키지를 제거하기 시작했습니다. 초기 문제는 해결되었지만, NPM이 향후 이러한 문제를 어떻게 방지할지 지켜보고 있습니다. 제 생각에는 NPM이 1. package.json에 별표 버전이 포함된 패키지 게시를 완전히 금지하거나, 2. 패키지 게시 취소 시 종속 패키지 수를 계산할 때 별표 버전을 사용하는 종속 패키지는 고려하지 않아야 합니다.
마지막으로, 저희에게 실망하거나, 불쾌하거나, 화가 나신 모든 분들께 사과드립니다. 저희가 실수를 했고, 그 책임을 인정합니다. 이 모든 것은 그저 장난으로 시작된 일이었고, 레지스트리를 훼손하거나, 악용하거나, 어떤 식으로든 피해를 줄 의도는 전혀 없었습니다. 간단히 말해서, 저희가… 괜히 일을 벌이다가 문제가 생긴 겁니다.

읽어주셔서 감사합니다. 좋은 하루 보내세요!
원본: https://uncenter.dev/posts/npm-install-everything/
업데이트된 내용은 원본에만 있습니다! 모든 이미지의 출처는 원본과 같습니다.