pip ResolutionImpossible / 의존성 충돌 에러, 5분 진단부터 완벽 해결까지
어제까지 잘 되던 pip install이 왜 빨간 벽이 됐을까
새 프로젝트를 셋업하거나 동료 코드를 클론해 pip install -r requirements.txt를 돌렸는데, 터미널이 한참 멈칫하더니 빨간 글씨로 ResolutionImpossible을 토해낸 적 있으신가요? 분명 어제까지 잘 되던 명령어인데 갑자기 벽에 막히면 당황스럽습니다.
결론부터 말씀드리면, 이건 버그가 아니라 pip가 "이 조합은 애초에 설치하면 안 된다"고 막아주는 정상 동작입니다. 이 글을 끝까지 읽으면 빨간 로그에서 무엇이 무엇과 충돌하는지 5분 안에 짚어내고, 임시 우회부터 근본 해결까지 상황에 맞게 골라 쓸 수 있게 됩니다. 복붙해서 바로 돌릴 수 있는 명령어 위주로 가겠습니다.
에러 메시지 해독하기: 빨간 로그를 한 줄씩 읽는 법
대표적인 에러 로그는 이렇게 생겼습니다.
ERROR: Cannot install -r requirements.txt (line 2) and urllib3==2.0.0
because these package versions have conflicting dependencies.
The conflict is caused by:
The user requested urllib3==2.0.0
botocore 1.29.76 depends on urllib3<1.27 and >=1.25.4
To fix this you could try to:
1. loosen the range of package versions you've specified
2. remove package versions to allow pip to attempt to solve the dependency conflict
ERROR: ResolutionImpossible핵심은 "The conflict is caused by:" 블록입니다. 위 예시를 풀어 읽으면 이렇습니다.
- 내가(
The user requested)urllib3==2.0.0을 직접 요구했다. - 그런데
botocore(boto3가 끌어오는 전이 의존성)는urllib3<1.27을 요구한다. - 2.0.0과 1.27 미만은 동시에 만족할 수 없다 → 그래서 불가능.
여기서 전이(transitive) 의존성이 핵심 개념입니다. 내가 직접 적지 않았지만 boto3 → botocore → urllib3처럼 줄줄이 딸려오는 패키지들이 서로 다른 버전을 요구하면 충돌이 납니다. 누가 어떤 버전을 요구하는지는 pipdeptree로 추적합니다.
pip install pipdeptree
pipdeptree -p urllib3 --reverse # urllib3를 누가 요구하는지 역추적설치 가능한 버전 후보를 확인하려면 이런 트릭이 유용합니다.
pip index versions urllib3 # 배포된 모든 버전 나열
pip install "urllib3==" # == 뒤를 비우면 가능 버전 목록을 에러로 출력왜 이제야 터지나: pip 새 resolver(20.3+)의 정체
"예전엔 됐는데 왜 지금 안 되지?"의 답이 여기 있습니다. pip 20.3부터 의존성 해결 엔진이 완전히 교체됐습니다.
| 구분 | 예전 (legacy) | 현재 (backtracking) |
|---|---|---|
| 충돌 검사 | 느슨함, 먼저 만나는 버전 설치 | 모든 제약을 동시 만족하는지 검사 |
| 충돌 시 | 조용히 깨진 채 설치 | ResolutionImpossible로 중단 |
| 위험 | 런타임에 ImportError 폭발 | 설치 단계에서 미리 차단 |
과거 resolver는 urllib3 같은 패키지를 그냥 아무 버전이나 깔아버렸고, 그 결과 import 시점이나 런타임에 정체불명의 에러가 터졌습니다. 지금의 backtracking resolver는 가능한 조합을 되짚어가며(backtrack) 탐색하다가 답이 없으면 멈춥니다. 빨간 에러는 사실 잠재 버그를 미리 막아준 셈이죠.
상황별 해결 전략 4가지
(a) 버전 범위 완화 — 가장 흔하고 깔끔한 해법
충돌의 원인이 너무 빡빡한 핀(고정)일 때가 많습니다. before/after를 비교해 보겠습니다.
# requirements.txt — before (충돌)
boto3==1.26.76
urllib3==2.0.0 # botocore가 <1.27을 요구 → 충돌# requirements.txt — after (해결)
boto3==1.26.76
urllib3>=1.26,<3 # botocore와 공존 가능한 범위로 완화버전을 못 박는 대신 범위로 풀어주면 resolver가 양쪽을 만족하는 지점을 찾아냅니다.
(b) --use-deprecated=legacy-resolver — 급할 때만, 위험 인지하고
pip install -r requirements.txt --use-deprecated=legacy-resolver설치는 됩니다. 하지만 충돌을 무시하고 깐 것이라 런타임에 깨질 수 있습니다. 이건 빌드 파이프라인을 당장 통과시켜야 하는 비상용일 뿐, 근본 해결이 아닙니다. legacy resolver는 향후 제거 예정이니 의존하지 마세요.
(c) pip-tools로 결정론적 lock 생성 — 권장 워크플로
직접 의존성만 requirements.in에 적고, 나머지는 도구가 잠가줍니다.
# requirements.in — 내가 진짜 쓰는 것만
boto3
requests
pandaspip install pip-tools
pip-compile requirements.in # 전이 의존성까지 해결해 requirements.txt 생성
pip-compile --generate-hashes requirements.in # 해시 포함(보안·재현성)
pip-sync requirements.txt # 환경을 lock과 정확히 일치시킴생성된 requirements.txt에는 urllib3==1.26.18 같은 정확한 버전과 해시가 박혀, 팀 어디서 깔아도 동일한 환경이 보장됩니다.
(d) constraints.txt로 전이 의존성만 고정
requirements는 그대로 두고, 골치 아픈 전이 의존성 버전만 따로 묶을 수 있습니다.
# constraints.txt
urllib3>=1.26,<2python -m venv .venv && source .venv/bin/activate # 가상환경 분리는 필수
pip install -r requirements.txt -c constraints.txtconstraints는 "설치하라"가 아니라 "설치된다면 이 범위로"라는 제약만 겁니다. 프로젝트별 가상환경 분리와 함께 쓰면 글로벌 환경 오염도 막을 수 있습니다.
실무 한마디: 핀을 거는 자리를 옮기세요
의존성 충돌로 새벽에 호출당하는 팀의 공통점은 모든 버전을 손으로 고정(pin)했다는 것입니다. requirements.txt에 100줄 넘게 ==를 박아두고 패키지 하나 올릴 때마다 충돌 퍼즐을 푸는 식이죠. 핀을 거는 책임을 사람에서 pip-compile로 옮기는 순간, 충돌 디버깅 시간이 체감상 절반 이하로 줄었습니다. 사람은 requirements.in에 "무엇이 필요한가"만 선언하고, "어떤 버전 조합이 양립 가능한가"는 도구에게 맡기세요.
요즘은 Astral의 **uv**가 Rust 기반 resolver로 pip보다 수십 배 빠른 속도와 더 친절한 충돌 메시지를 보여줘 빠르게 채택되고 있습니다(uv pip compile로 pip-tools를 대체 가능). Poetry·PDM은 자체 lock 파일과 SAT 기반 해결을 제공하고, PEP 665 등 lock 파일 표준화 논의도 진행 중입니다. 다만 도구가 무엇이든 위에서 설명한 "에러 로그 읽는 법"은 그대로 통합니다.
재발 방지 베스트 프랙티스 체크리스트
- ✅ 직접 의존성만
requirements.in에 적는다 (전이 의존성은 손대지 않는다) - ✅ 직접 의존성 버전은 가능하면 **범위(
>=,<)**로 적어 resolver에게 여유를 준다 - ✅
pip-compile결과인 lock 파일을 Git에 커밋한다 - ✅ 정기적으로
pip-compile --upgrade를 돌려 보안 패치를 흡수한다 - ✅ 프로젝트마다 가상환경을 분리한다 (
venv/uv/conda)
참고: 공식 문서
이 글에서 다루는 동작·설정·에러의 1차 출처는 다음 공식 문서입니다. 버전별 옵션과 정확한 동작은 여기서 확인하세요.
자주 묻는 질문 (FAQ)
Q. --use-deprecated=legacy-resolver로 설치는 됐는데 그냥 써도 되나요?
A. 권하지 않습니다. 충돌을 무시하고 설치한 것이라 import나 런타임에 AttributeError/ImportError로 터질 수 있습니다. 급한 배포만 막고, 곧바로 버전 범위 완화나 pip-tools로 근본 해결하세요. legacy resolver는 향후 제거됩니다.
Q. "The conflict is caused by:" 블록이 너무 길어서 못 읽겠어요.
A. 첫 줄(The user requested)과 충돌하는 패키지가 요구하는 범위만 보면 됩니다. 그래도 복잡하면 pipdeptree -p <패키지> --reverse로 그 패키지를 누가 끌어오는지 역추적하면 범인이 한눈에 보입니다.
Q. pip 대신 uv를 바로 써도 되나요?
A. 가능합니다. uv pip install, uv pip compile이 기존 명령어와 거의 호환되며 훨씬 빠르고 충돌 메시지도 친절합니다. 다만 팀 전체 합의 후 lock 파일 관리 방식을 통일하는 게 좋습니다.
이 글은 AI 에이전트가 자료 조사와 1차 초안 작성을 담당하고, 사람 편집자가 사실관계·출처·톤과 맥락을 검토한 뒤 발행했습니다. 환경(OS·버전)에 따라 결과가 다를 수 있으니 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.
댓글
첫 번째 댓글을 남겨보세요.