의존성 관리의 이중고

"yarn과 uv pip의 조화"

by 제임스

11년차 QA 엔지니어로서 "의존성 충돌"이라는 말을 수도 없이 들어왔습니다. 개발자들이 스탠드업 미팅에서 "어제 패키지 버전이 꼬여서 3시간 삽질했어요"라고 할 때마다, 속으로는 '그게 그렇게 어려운 일인가?' 싶었죠. 패키지 설치가 뭐가 그리 복잡하다고.


그런데 제임스컴퍼니 웹페이지를 직접 개발하기 시작하면서, 제가 얼마나 순진했는지 깨달았습니다. 의존성 관리는 마치 3차원 바둑 또는 체스 같았어요. 한 수를 두면 예상치 못한 곳에서 문제가 터지고, 그걸 해결하면 또 다른 곳에서 에러가 나고... 끝이 없더라고요.


시작은 단순했다: "npm install이면 끝 아니야?"

처음 React 프로젝트를 시작할 때의 자신감을 아직도 기억합니다. 튜토리얼 따라서 create-react-app 하고, npm install 몇 번 치니까 뭔가 돌아가더라고요. "어? 생각보다 쉽네?"

터미널에 뜨는 초록색 메시지들을 보며 뿌듯해했습니다. 하지만 이게 시작일 뿐이라는 걸 그때는 몰랐죠.


"왜 로컬에서는 되는데 배포하면 안 될까?" - 첫 번째 삽질

제 맥북에서 완벽하게 돌아가는 코드를 자신만만하게 Vercel에 배포했습니다. 그런데 빌드 실패. 에러 로그를 보니,

Error: Cannot find module 'react-query'


"아니, 나 분명히 설치했는데?"

당황해서 로컬 터미널 히스토리를 뒤져봤습니다. 분명 npm install react-query가 있었어요. 그런데 왜 Vercel은 못 찾는 거지?

답은 의외로 간단했습니다. 제가 .gitignore에 뭘 추가했는지 확인해보니,


"어차피 package.json에 다 있잖아?"라는 안일한 생각이 화근이었습니다. package.json에는 이렇게 적혀있었거든요..


^ 기호가 뭘 의미하는지 제대로 몰랐던 거죠. "3.39.0 이상, 4.0.0 미만"을 의미한다는 걸 나중에 알았습니다. 제 로컬에서는 3.39.3이 설치됐고, Vercel에서는 그 시점의 최신 버전인 3.39.5가 설치됐던 겁니다.

미묘한 차이지만, 어떤 내부 API가 바뀌었나 봅니다. 제 코드는 3.39.3에서만 돌아가는 코드였던 거죠.


npm에서 yarn으로: 더 빠르다는 유혹

어느 날 유튜브에서 "yarn이 npm보다 3배 빠름!"이라는 영상을 봤습니다. 개발자라면 누구나 그렇듯, 더 빠른 것에 끌렸죠. 바로 설치했습니다.

brew install yarn


그리고 기존 프로젝트에서,

yarn install


오, 정말 빠르더라고요! 문제는 제가 정리를 안 했다는 겁니다. 프로젝트 폴더를 보니,

더 웃긴 건, 저는 이후로 yarn을 쓴다고 마음먹었는데, 습관이 무서운 거죠. 급할 때는 무의식적으로 npm을 치고 있더라고요,


이 혼돈의 결과는? CI/CD 파이프라인에서 터졌습니다.

warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. error Your lockfile needs to be updated, but yarn was run with `--frozen-lockfile`.


Vercel은 어느 lock 파일을 믿어야 할지 몰라서 헤맸고, 빌드 시간은 평소의 2배가 됐습니다.


node_modules: 블랙홀의 탄생

프로젝트 초기, node_modules 폴더 크기를 확인했을 때,


"음, 좀 크긴 하네"라고 생각했습니다. 6개월 후...

"뭐?! 1.2 기가?"

충격을 받고 리뷰를 시작했습니다. 제가 직접 설치한 패키지는 20개 정도인데, 왜 이렇게 커진 거지?


react-query 하나만 47MB라니! 더 파고들어보니,



27139_2019112114494917.jpg 러시아 인형


의존성의 의존성의 의존성... 러시아 인형 같았습니다. 더 충격적인 건 개발용 패키지들을 실수로 dependencies에 넣었다는 거였어요..


운영 빌드에 ESLint가 왜 포함돼야 하죠? 이것만 정리해도 번들 크기가 30% 줄어들더라고요.


Backend의 세계: Python 패키지 관리자 춘추전국시대

Frontend가 npm vs yarn 정도라면, Python은 정말 선택지가 많았습니다.

pip (기본)

pip3 (Python 3용)

pipenv (Pipfile 도입)

poetry (더 현대적)

conda (데이터 사이언스 쪽)

uv (최신, Rust로 만든 초고속)

저는 당연히(?) 가장 최신이고 빠르다는 uv를 선택했죠.


"와, 10배나 빠르다!" 감탄했습니다. 하지만...


requirements.txt의 진화: 버전 명시의 중요성

처음에는 이렇게 단순하게 작성했습니다.


로컬에서는 잘 돌았습니다. 하지만 일주일 후 새로운 환경에서 설치하니 에러가 났어요. 왜? 그 사이에 FastAPI가 0.104에서 0.105로 업데이트됐고, 뭔가 바뀐 모양이더라고요.

그래서 버전을 명시하기 시작했습니다.


하지만 이것도 완벽하지 않았습니다. 왜냐하면...


플랫폼의 저주: "내 맥북에서는 되는데요"

가장 악명 높은 에러를 만났습니다.


"대체 pg_config가 뭐야?" 구글링 시작. 알고 보니 PostgreSQL 개발 도구가 필요하다는 거였어요. Mac에서는 brew로 설치한 PostgreSQL이 알아서 처리해줬지만, Linux 컨테이너에서는 별도 설치가 필요했습니다.


해결책을 찾는 데 반나절이 걸렸습니다.


하지만 이게 끝이 아니었습니다. 다른 패키지에서도 비슷한 일이...


결국 Dockerfile이 이렇게 길어졌습니다.


Docker: 의존성 지옥의 새로운 차원

"Docker를 쓰면 'Works on my machine' 문제가 해결된다"고 들었습니다. 하지만 현실은...

첫 번째 Dockerfile (순진했던 시절)


문제점

코드 한 줄만 바꿔도 전체 npm install 다시 실행 (10분)

node_modules까지 복사되면서 이미지 크기 2GB

npm? yarn? 뭘 써야 하지?

개선된 버전


Backend도 마찬가지였습니다.


하지만 또 다른 함정이 있었습니다...


아키텍처의 배신: M1 맥북 vs Intel 서버

제 M1 맥북에서 빌드한 이미지를 EC2에 배포했더니,

exec format error


"뭐야 이게?"

알고 보니 M1은 ARM64 아키텍처, EC2는 AMD64 아키텍처. 완전히 다른 CPU 명령어 세트를 사용한다는 거였어요.

해결책


빌드 시간은 2배가 됐지만, 적어도 돌아가긴 했습니다.


보안 취약점: Dependabot의 빨간 알림 폭탄

평화로운 월요일 아침, GitHub를 열었더니,

https://gist.github.com/jamescompany/1c3ac1c9801af214ee646dcb3a42661b


"이게 다 뭐야..."

가장 무서웠던 건 axios 취약점이었습니다.


직접 axios를 쓰는 것도 아닌데, 내가 쓰는 패키지가 axios를 쓰고, 그 패키지가 오래된 버전을 고집하고 있었던 거죠.

선택지

취약점을 안고 간다 (❌ 절대 안 됨)

해당 패키지를 다른 것으로 교체 (� 대공사)

Fork해서 직접 패치 (� 유지보수 지옥)

Monkey patching (� 위험하지만...)

결국 주말을 반납하고 react-admin을 다른 라이브러리로 교체했습니다.


의존성 업데이트 자동화의 함정

"Dependabot이 알아서 PR 만들어주니까 편하겠네"라고 생각했죠.


월요일 아침

� 10 new pull requests from Dependabot

- Bump eslint from 8.44.0 to 8.45.0

- Bump @types/react from 18.2.14 to 18.2.15

- Bump prettier from 2.8.8 to 3.0.0

- ... (7 more)


"오, 다 머지하면 되겠네!"

[Merge all] 클릭. 30분 후...



알고 보니 prettier 2.x에서 3.0으로 가면서 설정 포맷이 완전히 바뀌었고, 일부 패키지는 메이저 버전이 올라가면서 API가 바뀌었던 거였어요.

교훈: 마이너 버전도 함부로 올리면 안 된다. 특히 "금요일"에는...


실전 의존성 관리 전략 (삽질 끝에 얻은 지혜)

1. Lock 파일은 신성하다


Lock 파일을 커밋하지 않는 건 "우리 팀은 문서 작성 안 해요"라고 말하는 것과 같습니다.


2. 패키지 매니저는 하나만


이렇게 하면 누군가 실수로 npm을 쓰려고 할 때 에러가 납니다.


3. 정기적인 의존성 감사


매주 금요일 오후는 "의존성 점검의 시간"으로 정했습니다. (금요일 배포는 안 하니까)


4. 의존성 다이어트

번들 크기 분석


충격적인 발견들

moment.js: 모든 로케일 포함해서 700KB → date-fns로 교체

lodash: 전체 import로 600KB → 개별 import로 50KB

source-map 포함된 프로덕션 빌드: 5MB → 1.2MB


5. 환경별 의존성 분리


QA 엔지니어의 시각으로 본 의존성 관리

11년간 QA를 하면서 "버전 차이로 인한 버그"를 수없이 봤지만, 직접 경험하니 차원이 다르더군요. 이제는 이런 이슈들을 더 잘 찾아낼 수 있게 됐습니다.


환경별 체크리스트

Lock 파일이 모든 환경에 동일하게 적용되는가?

개발 의존성이 프로덕션에 포함되지 않았는가?

플랫폼별 의존성이 제대로 처리되는가?

취약점이 있는 패키지는 없는가?

불필요하게 큰 패키지는 없는가?


새로 배운 테스트 시나리오

Clean install 테스트: node_modules/venv 삭제 후 처음부터 설치

Cross-platform 테스트: Mac/Linux/Windows에서 각각 설치

버전 고정 테스트: 6개월 후에도 같은 버전이 설치되는지

의존성 취약점 스캔: CI/CD에 보안 검사 추가


조화가 아닌 균형

"yarn과 uv pip의 조화"라고 제목을 지었지만, 솔직히 조화보다는 끊임없는 균형 맞추기에 가깝습니다. 마치 서커스에서 여러 개의 접시를 동시에 돌리는 것처럼, 하나가 떨어지기 전에 다시 돌려줘야 하는 그런 느낌이죠.

하지만 이 경험은 저를 더 나은 QA 엔지니어로 만들어줬습니다. 이제 "로컬에서는 되는데 운영에서 안 돼요"라는 이슈를 받으면, 가장 먼저 이런 것들을 확인합니다.

Lock 파일이 커밋되어 있나?

환경별 의존성 버전이 일치하나?

플랫폼별 차이는 없나?

최근에 업데이트된 패키지는 없나?


무엇보다, 개발팀이 "의존성 때문에..."라고 할 때 진심으로 공감할 수 있게 됐다는 게 가장 큰 수확입니다.

다음 편에서는 이 모든 의존성 위에서 구축한 결제 시스템 이야기를 들려드리겠습니다. 토스페이먼츠와의 사투, 테스트 결제가 실제 결제가 될 뻔한 아찔한 순간들... 정말 스릴 넘치는 이야기가 기다리고 있습니다. 특히 "왜 환경 변수를 제대로 분리해야 하는지"를 뼈저리게 느낀 순간들을 공유하고 싶네요.

의존성 관리, 정말 만만치 않죠? 하지만 이것도 결국은 "체계적인 관리"의 문제더라고요. QA의 본질과 다르지 않았습니다.

keyword
이전 08화Vercel 배포와 CI/CD