좌충우돌 pipenv 도입기

Share on facebook
Share on linkedin
Share on twitter
Share on email
파이썬 프로젝트의 의존성 관리를 대신해 주는 pipenv를 사용하고 느낀 장단점을 정리해보았다. pipenv는 의존성 관리에 강력한 편의성을 제공해주는 대신 설치 속도를 희생한다.

목차

포스트 공유하기

Share on facebook
Share on linkedin
Share on twitter
Share on email

의존성 관리의 필요성

어느 정도 규모 있는 파이썬 프로젝트를 개발한다면, 개발환경을 맞춰줘야 할 일이 필연적으로 생긴다. 내 컴퓨터에서 잘 돌아가는 코드가 다른 개발자의 컴퓨터나 실제 서버에서도 잘 돌아가려면, 최소한 각각의 개발 환경은 일관성이 있어야 하기 때문이다.

개발 환경이라 함은 많은 의미를 포괄하지만 이 포스트에서는 의존성 관리, 즉 패키지 설치 과정에 집중해보고자 한다. A라는 파이썬 프로젝트에서 B 패키지의 모듈을 사용했다면, B가 설치되지 않고서는 A가 정상 동작할 수 없는데, 이러한 상황을 “A가 패키지 B에 대한 의존 관계(Dependency)를 갖는다”고 말한다. 여기서 패키지 B 또한 제3의 패키지 C에 대한 의존성을 가질 수 있다. 이렇게 꼬리에 꼬리를 무는 과정이 반복되면 패키지 간 의존 관계가 사람이 다루지 못할 정도로 한없이 복잡해질 수 있다. 의존성 관리 도구가 필요한 순간이다.

파이썬 프로젝트에서 의존성을 관리하는 가장 공식적인 방법은 Requirements File을 다루는 것이다. 필요한 패키지와 적절한 버전을 나열한 텍스트 파일을 작성하는 방식이다. 다만 패키지 설치와는 별개로 requirements.txt를 따로 관리해야 하다보니 번거롭게 느껴질 수 있다.

# numpy 라이브러리를 설치하고 싶을 때
pip install numpy
pip freeze > requirements.txt

# 다른 개발환경에서 변경된 패키지 의존성을 반영할 때
pip install -r requirements.txt

실제로, 기존의 전통적인 의존성 관리 방식에 불편하다는 의견이 많아 pipenvpoetry같이 보다 간편히 의존성을 관리할 수 있는 도구들이 개발되었다.

pipenv를 실제로 사용해보다

SkileBot은 2018년에 시작된 디스코드 챗봇 프로젝트인데, 파이썬 기반이고 discord.py를 비롯한 많은 외부 패키지를 사용한다. 당연히 의존성 관리 문제로부터 자유롭지 못했고 편의성만 보고 pipenv를 덜컥 도입해버렸다. “덜컥” 도입해버렸다 함은 pipenv를 Node.js의 npm 같은 것이겠거니 생각하고 내부 동작에 대한 이해 없이 도입했다는 것이다. 결과적으로 많은 편의성을 주었지만 동시에 많은 삽질의 원흉이 되었다….

장점

가상환경 관리를 알아서 한다

앞에서 굳이 짚지 않고 넘어갔지만, 보통은 의존성 관리를 할 때 파이썬 가상환경 관리 도구 virtualenv도 같이 따라붙는다. 같은 이름의 패키지더라도 프로젝트마다 요구하는 버전이 달라질 수 있기 때문이다. 프로젝트 A에서는 P==0.1 (P 패키지의 0.1 버전)이, 프로젝트 B에서는 P==1.0 (P 패키지의 1.0 버전)이 필요하다면 하나의 파이썬 환경에서 두개의 프로젝트를 동시에 돌리지 못하고 충돌이 발생하게 된다. 이를 해결하는 것이 virtualenv다.

한편 pipenv의 경우 이름에서부터 추론할 수 있듯 pipvirtualenv를 함께 관리해준다. 따라서 virtualenv 명령어를 사용해본 경험이 전혀 없더라도 쉽게 프로젝트별로 가상환경을 구축할 수 있다.

개인적으로는 pipenv와는 별개로 파이썬 가상환경 관리에 pyenv를 사용하고 있다. pyenv는 특히 파이썬 버전이 다른 환경도 잘 관리할 수 있다는 장점이 있다. pipenv와도 잘 호환된다. 만약 pyenv가 인식된 경우 pipenv에서 자체적으로 가상환경 생성을 하지 않으며 설정에서 잘 조절할 수 있다.

Lock 파일을 별도로 관리해준다

pipenv로 의존성을 관리하면 프로젝트 폴더에 PipfilePipfile.lock 두 개의 파일이 생성되는 것을 볼 수 있다. 각각은 서로 다른 역할을 맡는다.

  • Pipfile에는 해당 프로젝트에서 일차적으로 필요한 패키지(Top-level dependencies)들만 포함한다. 개발자가 새로운 패키지를 추가 또는 삭제할 때는 프로젝트에 직접적으로 연관된 Pipfile만 편집하면 된다.
  • Pipfile.lock에는 각각의 하위 패키지가 요구하는 패키지까지 전부 포함한다. 프로그램의 정상 동작을 보장할 수 있을 만큼의 상세한 패키지 정보는 Pipfile.lock에서 알아서 관리한다.

더불어 Pipfile.lock에는 패키지의 해쉬 정보도 포함된다. 패키지 해쉬는 서로 다른 개발 환경에서도 정확히 똑같은 파일들이 다운로드되어 설치될 것이라는 사실을 보장한다. 패키지와 버전명이 일치하더라도 파일이 변조되어 해쉬값이 다른 패키지가 설치될 가능성을 막아주니 보안성에도 향상된다.

개발 모드와 운영 모드를 하나의 파일에서 관리한다

일부 파이썬 프로젝트를 보면 requirements.txt와는 별개로 requirements-dev.txt 같은 변종 파일이 있는 것을 볼 수 있다. 이는 개발 환경에 따라 필요한 패키지들이 달라질 수 있기 때문이다. 일례로 black이라는 패키지는 파이썬 코드의 들여쓰기 형식 등을 검사해주는 코드 포매터(code formatter)다. 코드가 시시각각 변하는 “개발 모드”에서는 자주 쓰여도, 이미 리뷰가 끝나 실제 서비스에 사용되는 “운영 모드”에서는 전혀 필요 없는 패키지가 될 수 있다. requirements-dev.txt는 같은 프로젝트 내에서도 서로 다른 맥락의 개발 환경이 존재할 수 있고, 이를 관리하기 위한 독립적인 파일이 또 필요함을 의미한다.

이와는 다르게 Pipfile은 파일 하나에서 “운영 모드” 패키지와 “개발 모드” 패키지(dev-packages)를 동시에 관리할 수 있다. 실제로 명령어를 호출할 때도 --dev 옵션을 넣거나 빼는 것으로 간단히 환경을 구분할 수 있다.

단점

pipenv는 느립니다.

pipenv는 프로젝트 의존성 관리에 필요한 세세한 설정을 개발자가 일일이 신경쓸 필요가 없다는 것이 큰 장점이다. 그러나 다르게 말하면 개발자가 어쩌면 굳이 신경쓰고 싶지 않았던 부분까지 비용을 들여 관리해준다고 볼 수도 있다. 프로젝트에서 pipenv를 도입한 다음 가장 크게 느낀 불편함이 바로 속도 문제였다. 그냥 pip install 명령어를 사용하는 것에 비해 pipenv는 Locking 파일을 생성하는 데 너무 많은 시간을 소모한다.

SkileBot에서 직접적으로 필요로하는 패키지는 13개 남짓 된다. 그런데도 사소한 의존성 업데이트 한 번에 30분이 넘는 시간이 걸리고, 심지어는 타임아웃 에러가 떠서 의존성 업데이트에 실패하는 경험을 여러 번 하다 보니 심적 부담이 생겨 패키지 업데이트를 쉽게 못 하게 되었다. 그럼에도 가끔은 기존 라이브러리에 버그가 발생해 강제로 버전을 업데이트해야 하는 상황도 생기는데, 시급히 해결이 필요한 이슈라서 결국에는 야심차게 도입한 pipenv를 버려두고 주먹구구식으로 pip install 명령어를 실행하게 되는 단계에까지 이르렀다.

PyPI 미러 사용하기

여러 삽질 끝에 근본적인 원인이 사실 pipenv보다는 파이썬 패키지 저장소 PyPI의 느린 반응에 있었음을 알아냈다. Pipfile에는 어떤 저장소에서 패키지를 가져올 건지 설정하는 [[source]] 란이 있는데, 여기에 다음과 같이 카카오의 PyPI 미러 서버를 등록해두면 최소한 국내 환경에서는 속도 문제를 훨씬 개선할 수 있었다.

[[source]]
url = "http://mirror.kakao.com/pypi/simple/"
verify_ssl = false
name = "kakao"

[[source]]
url = "https://pypi.org/simple/"
verify_ssl = true
name = "pypi"

[packages]
...

첨언하자면 카카오 미러 PyPI와는 별개로 원본 PyPI 주소도 남겨놨는데 이는 미러링되지 않은 파이썬 패키지에 대비하기 위한 것이다. 일례로 앞서 언급했던 black==20.8b1 패키지가 카카오 미러를 통해서는 제대로 설치되지 않아 어려움을 겪기도 했다.

pipenv vs poetry 속도 비교

pipenv 속도 문제로 한참을 고통받던 중에 poetry로 의존성 관리 도구를 바꿔볼까도 조사해봤었다. 실험 결과 poetrypipenv 보다는 약간의 성능 향상이 있으나 어쨌든 Locking 로직이 있는 이상 전통적인 requirements.txt 관리보다 오랜 시간이 걸리는 것은 어쩔 수 없는 듯 보였다. 의존성 관리 도구를 통째로 갈아엎는 비용보다는 현재 쓰던 pipenv의 PyPI 설정만 바꿔서 사용하는 편이 간편하다고 생각하여 SkileBot은 pipenv를 계속 쓰기로 했다.

의존성 관리 상황pipenv (v2020.6.2)poetry
패키지 전체 설치 최초 시도timeout (16:00.72)
PyPI에 카카오 미러 설정 후 재시도10:17.905:59.24
Lock 파일 지우고 재시도 (pip 캐시 사용)1:18.331:46.98
Lock 파일 없이 requirements.txt로 설치40.1937.40
패키지 하나의 버전만 업그레이드59.1713.63

의사결정을 위해 pipenvpoetry의 설치 속도를 비교하는 실험을 진행했다. 하지만 많은 변인이 통제되지 않아 신뢰성이 높지는 않고, 단지 의존성 관리 도구를 사용하여 열댓 개 내외의 패키지를 설치할 때 10분 내외의 시간이 걸릴 수 있다는 정도가 참고할 만하다. 두 도구 사이의 성능을 정량적으로 비교·분석한 블로그 포스트를 보고 싶으면 아래를 같이 참고하길 바란다.

결론

pipenv는 느리다

pipenv를 도입할 당시에는 기존 pip 패키지 관리 방식의 불만과 pipenv의 장점과 사용법을 다룬 글을 많이 보았다. 그러나 정작 실제로 도입하고 나서 가장 고통받던 속도 이슈를 깊이 짚고 넘어간 포스트를 찾기 어려웠고, 고생을 많이 했다.

물론 가장 치명적인 원인은 pipenv보다는 PyPI의 느린 반응속도였고 이는 미러 사이트 설정으로 손볼 수 있었다. 지금도 여전히 패키지 업데이트에 5분 내외의 시간이 걸리지만 편의성을 고려하면 감수할 만한 수준이다. SkileBot은 pipenv와 계속 함께하기로 결정했지만, 그 이전에 좀 더 의존성 관리 도구의 장단점을 깊이 조사해볼걸 하는 후회는 남는다.

최근에는 2020년에 버전 1.0.1을 출시한 신생 의존성 관리 도구 poetry에 대한 많은 얘기가 나온다. 하지만 여전히 기존의 전통적 패키지 관리 방식에 비하면 속도 이슈를 무시할 수는 없는 상황이다. 각 방식의 장단을 이해하여 프로젝트에 어울리는 최적의 도구를 도입하기를 바란다.

목차

Skile (스카일)

Skile (스카일)

자연어처리에 관심이 많고 챗봇을 좋아하는 개발자 스카일입니다. 디스코드 봇 SkileBot을 만들었고, 팀 크레센도의 대표로서 효과적인 팀 운영에도 많이 신경쓰고 있습니다.

더 둘러보기