Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[PyCon KR] Mono-repo migration using Pantsbuild

[PyCon KR] Mono-repo migration using Pantsbuild

Joongi Kim

October 03, 2022
Tweet

More Decks by Joongi Kim

Other Decks in Programming

Transcript

  1. 발표자 소개 • 일하는 분야 : 백엔드, 분산처리, 가속 컴퓨팅

    • 요즘 관심사 : 일관성, 리팩토링, 3년 후의 나도 알아볼 수 있게 • 요즘 상태 🤯 😵💫‍‍ • (어쩌다보니) 7년 연속 PyCon KR 발표 중... 주로 asyncio 관련 삽질 내용
  2. 목차 • Mono-repo vs. Multi-repo: 그것이 문제로다 • 기존 개발

    프로세스의 문제점 • Pantsbuild에 대한 짧은 소개 • Pantsbuild를 활용한 이전 과정 • 우리 실정에 맞게 커스터마이징한 부분들 • 이전 과정을 마친 후의 경험 • 정리
  3. 현대적 빌드 시스템의 필요성 • Software Engineering at Google ("구글

    엔지니어는 이렇게 일한다") Backend.AI 프로젝트가 정확히 이 지점을 지나고 있는 상황 (7년차) ref) https://abseil.io/resources/swe-book/html/ch01.html#time_and_change
  4. 현대적 빌드 시스템? • 의존성 관리 자동화 내부 의존성 :

    우리 조직 안에서 작성한 컴포넌트·서비스끼리의 의존 관계 외부 의존성 : 우리 조직 바깥에서 작성한 컴포넌트·서비스로의 의존 관계 • 선언적 의존성 관리 사람의 실수나 사람에 의한 차이를 가능한 한 줄이려면 어떻게 해야 하는가? • 재현 가능한 빌드 configuration이 같다면 누구의 컴퓨터에서 실행해도 같은 결과가 나와야 함 • 자동화 및 캐싱 Linting, Test, Build & Deploy와 같은 CI/CD 전 과정 자동화를 지원해야 함 매 commit마다 변경된 범위를 자동 추출하고 기존 결과 재활용하여 필요한 작업만 수행
  5. 의존성 관리 자동화 • 패키지 관리자 yarn, npm, cargo, poetry,

    pipenv, go get, ... • 핵심 기능 의존성 해결 ✓ 예) A → C, B → C 의존 관계가 있을 때 A와 B 모두와 호환되는 C 버전이 존재하는가? ✓ 1차 의존성의 n차 의존성을 몽땅 개별 샌드박스 vs. 호환 버전 모두 맞추기 (NP-complete[1]) 의존성 잠그기 : 공급망 공격 및 예측되지 않은 버전 업데이트로 인한 오류 예방 자동화 ✓ 의존성의 개수와 상호 의존 관계의 숫자가 조금만 늘어도 사람이 관리하는 것이 불가능함 ✓ 의존성의 버전 업데이트 자동 탐지 및 패키지 metadata에 자동 적용 등 [1] https://research.swtch.com/version-sat
  6. Mono-repo와 빌드 시스템 • "One version rule" A → C,

    B → C 의존 관계가 있을 때 C는 단 하나의 버전만 존재해야 하며, A와 B의 모든 코드베이스도 그 단 하나의 C 버전에 맞도록 항상 유지보수되어야 함 • Mono-repo를 도입하면 좋은 점? 전체 코드베이스에서 중복을 쉽게 발견할 수 있음 C의 API가 바뀌었을 때 전체 코드베이스에서 한번에 수정(리팩토링)할 수 있음 내부 의존성을 모두 선언적 빌드 설정으로 표현 가능 ✓ 적절한 도구가 있다면 의존 관계 추론 자체를 자동화해버릴 수도 있음
  7. 장단점 비교 ✗ 연관 코드를 모두 고려해 작업하므로 개발속도 느려질

    수 있음 ✗ 코드 동기화에 많은 시간이 걸림 ✗ 실제로 변경사항이 없는 컴포넌트도 릴리즈 시 새 버전이 찍힘 ✗ 개별 컴포넌트에 대한 fork를 따로 만드는 것이 어려움 ✓ 신입 개발자들의 진입장벽을 낮춤 ✓ 중앙집중화된 코드 관리 ✓ 전체 시스템에 대한 고통 없는 리팩토링 가능 ✓ 관련 있는 코드를 깨먹기 어려움 ✓ 각 팀이 같은 개발 프로세스와 문화를 공유하게 됨 ✓ 독립적인 컴포넌트 버전 번호 부여 ✓ 독립적인 서비스 릴리즈 일정 ✓ 저장소 단위의 접근 제어 ✓ 각 저장소 담당 팀들이 독립적으로 일 진행 ✗ 공통 코드·라이브러리의 반복적인 동기화 필요 ✗ 저장소 단위로 팀들이 파편화됨 ✗ 전체 시스템에 대한 view를 가진 사람이 적어짐 ✗ 신입 개발자들이 여러 저장소를 둘러보기 어려움 Multi-Repo Mono-Repo ref) https://kinsta.com/blog/monorepo-vs-multi-repo/
  8. 기존 프로세스 요약 • GitHub 저장소 설정 기준 릴리즈 패키지

    1개당 저장소 1개 패키지별 CI/CD 워크플로우 작성 용이 & 패키지별 개별 릴리즈 가능 • Backend.AI 개발환경 설정 방법 서버측 개발 환경 설치를 위한 최소 컴포넌트 집합 (6개) ✓ manager, agent, common, client-py, webserver, storage-proxy ✓ 프론트엔드는 또 다른 이야기... 개발환경 자동설정 스크립트 (install-dev.sh) ✓ docker-compose 기반 데이터베이스 컨테이너 설치 ("halfstack") ✓ 컴포넌트별 저장소 클론, venv 생성, editable install 수행
  9. 기존 프로세스 문제점 (1/2) • 여러 저장소 사이의 브랜치 동기화

    개발하다가 git switch를 n번 해야 하는데 중간에 하나 빼먹으면... • 하나의 이슈에 여러 개의 PR을 작성·리뷰해야 함 대표적 실수 사례 : manager에 API를 추가했는데 client에 대응 명령어 추가하는 걸 까먹는 경우 ✓ 리뷰어도 종종 까먹음... GitHub Issue & PR 기능과 어울리지 않음 ✓ 하나의 이슈에 여러 개의 PR이 연결되면 하나의 PR만 병합해도 이슈 완료로 처리됨 (OR 조건) GitHub Project v2 나왔지만 (아직) 여러 저장소에 걸쳐 label, milestone을 통합 관리 불가 PR 작성자도 헷갈리고 PR 리뷰어도 헷갈리고... 🤯 • 신입 개발자들의 어려움 여러 저장소에 걸친 CI 동작을 위해 PR branch 이름을 맞춰야 하는 암묵적 규칙 존재
  10. 기존 프로세스 문제점 (2/2) • 리팩토링의 어려움 manager, agent에서 모두

    참조하는 common 패키지의 무언가를 바꾸면 manager, agent 저장소에도 PR을 생성해야 함 common 패키지를 다른 컴포넌트에서 재활용하려 해도 점점 더 귀찮아질 것이 걱정되어 소극적이게 됨 새로운 패키지를 추가하는 것이 관리 포인트를 늘리게 되므로 공통으로 사용할 코드를 목적이나 의도와 상관 없이 common 패키지 안에 전부 다 때려박게 됨 • 릴리즈 과정의 어려움 common 버전을 먼저 릴리즈하고 PyPI 업로드 확인 후 manager, agent 버전을 릴리즈해야 CI가 깨지지 않으므로 사람이 기다리는 시간이 발생 6개의 패키지를 똑같은 명령어를 반복해서 릴리즈하다보면 현타가 옴... (가끔 실수도 발생) ✓ changelog로부터 release note 추출하는 부분이 자동화되어 있지 않았음 ✓ 자동화하려고 해도 6개 저장소에 똑같은 설정과 커밋을 반복해야 하니 안 하게 됨
  11. 해결책 • 문제점을 요약하면: 같은 일을 여러 저장소에서 반복함에 대한

    귀차니즘으로 리팩토링을 게을리하게 됨 저장소 경계를 넘나들 때 코드 작성자와 리뷰어 모두 context switching 오버헤드가 너무 커짐 • (Semi-)mono-repo로 갑시다! 개발자가 수백, 수천 명이고 코드가 몇백만줄 이렇다면 scalability 문제가 있겠지만 우리는 아님 회사 전체 mono-repo가 아니라, 동일 릴리즈 주기를 갖고 상호 내부 의존성이 존재하는 Backend.AI 서버측 오픈소스 컴포넌트들만을 대상으로 함 • 고민 내부 의존성 관리를 어떻게 자동화할까? (setup.cfg 파싱 또는 생성?) CI에서 변경된 부분만 테스트 돌리고 싶은데 어떻게 구현하지? (git sparse checkout?) Mono-repo를 지원하는 현대적 빌드 시스템이 필요하다!
  12. What is Pants? • 주요 기능 정적 분석에 의한 의존성

    자동 추론 Python 생태계에 대한 first-class support 작업 그래프를 강력한 캐싱과 함께 비동기 병렬 실행 플러그인 API를 제공하여 기능 확장이 가능함 • 톺아보기 https://www.pantsbuild.org/docs/how-does-pants-work https://blog.pantsbuild.org/pycon-us-2022-talk/ Pants 2 is a fast, scalable, user-friendly build system for codebases of all sizes. It's currently focused on Python, Go, Java, Scala, Shell, and Docker, with support for other languages and frameworks coming soon. ref) https://www.pantsbuild.org/
  13. Pants Architecture Rust 기반 DAG 스케줄러 및 비동기 병렬 실행

    엔진 (monadic, pure, cancellable/interruptible, concurrent, cached) Python 기반 빌드 규칙 정의 엔진 & 내장된 언어별 backend 플러그인 파일시스템과 운영체제 Git 저장소에 정의된 BUILD 정의 파일 및 pants.toml 사용자 설정 (빌드 타겟 환경으로는 임의 Python 버전 사용 가능) pantsbuild-pants라는 이름의 PyPI 패키지 안에 포함 사전 설치된 Python 런타임 (3.7, 3.8, 3.9 중 하나) PEX 가상환경 생성기 및 패키지 의존성 resolver
  14. Pants 기본 사용법 • Pants를 사용하도록 세팅된 Git 저장소에 필요한

    요소들: ./pants 스크립트 (다운로드 받아서 복사해둠) pants.toml 및 pants.ci.toml **/BUILD 파일들 ✓ 빌드 대상(소스 파일, 리소스 파일 등)과 의존 관계 선언 • ./pants [global-options] {goal} [goal-options] [targets] Pants 본체를 ~/.cache/pants/ 및 ./.pants.d 안에 자동 설치 BUILD 파일로부터 타겟과 의존 관계를 DAG 형태로 생성 병렬 실행이 가능한 경우 병렬화하면서 DAG 작업 그래프 실행 ref) https://docs.backend.ai/en/latest/dev/daily-workflows.html
  15. GitHub 저장소 재구성 backend.ai-manager backend.ai-agent backend.ai-common backend.ai-webserver backend.ai-client-py backend.ai-storage-proxy backend.ai-webui

    backend.ai-client-js Backend.AI Core https://github.com/lablup/... Backend.AI Fronted https://github.com/lablup/backend.ai 대통합!
  16. Mono-repo 저장소 구조 Unified package version Pants build config for

    each directory (like Bazel) "local-config" templates for Backend.AI Core Backend.AI developer documentation Backend.AI plugin development workspace Utility shell scripts for developers Backend.AI Core source codes Backend.AI Core test codes Pants main config Toolchain configs (flake8, mypy, pytest) Unified requirements for all components Unified requirements dependency lock Toolchain requirements dependency lock Our Pants plugin for custom setup.py generation Main entry scripts for daily use venvs & build artifacts generated by Pants VERSION **/BUILD configs/{manager,agent,common,...} docs/ plugins/ scripts/ src/ai/backend/{manager,agent,common,...} tests/{manager,agent,common,...} pants.toml, pants.ci.toml pyproject.toml, .flake8 requirements.txt python.lock tools/*.lock tools/pants-plugin/setupgen/ ./pants, ./py, ./backend.ai dist/
  17. 기존 multi-repo 이전 방법 setup.cfg, requirements/*.txt README.md changes/, CHANGELOG.md configs/

    scripts/ src/ai/backend/{component}/ tests/ src/ai/backend/{component}/BUILD, VERSION, pants.toml, BUILD, requirements.txt src/ai/backend/{component}/README.md changes/, CHANGELOG.md configs/{component}/ scripts/{component}/ src/ai/backend/{component}/ tests/{component}/ (unified) (unified) __version__ = '22.03.1' from pathlib import Path __version__ = ( Path(__file__).parent / 'VERSION' ).read_text().strip() $ cd src/ai/backend/{component} $ ln -s ../../../../VERSION src/ai/backend/{component}/__init__.py: (moved) (moved) (moved) (moved) (moved)
  18. 전체 이전 과정 • https://github.com/lablup/backend.ai/pull/417 4월 27일에 시작하여 5월 31일에

    병합 (총 168개 커밋) / 이후에도 다수의 follow-up PR 있음 ✓ 원래 계획은 2주였지만 역시나... 😅 Pantsbuild 커뮤니티 슬랙에서 60여회 이상의 기술 질의응답 진행 Pants 버그 신고 5회 (모두 수정됨), 기능 제안 2회, 문서 수정 2회 Pants가 의존하는 Pex의 버그 신고 및 릴리즈 트리거 3회 ✓ 한국 시간으로 오후에 버그 신고, 밤에 개발자들과 대화, 새벽에 개발자들이 고치고 릴리즈해줌, 다음 날 아침에 적용하고 오후에 피드백 반복... • Mono-repo 합친 결과물 Backend.AI Core 본체 코드 : 7.4만줄 모든 외부 의존성까지 포함하였을 때 코드 : 150만줄 이상
  19. setup.py Generator • Pants 플러그인 : tools/pants-plugins/setupgen • 추가한 기능

    저장소 루트 디렉토리의 VERSION 파일을 버전 번호 소스로 사용 README.md, README.rst 파일의 존재 여부에 따라 long_description_type 다르게 설정 버전 번호에 alpha, beta, rc 여부에 따라 그에 맞는 trove classifier 적용 라이센스 유형 옵션 인자를 추가하여 빌드될 패키지마다 다른 라이센스를 지정할 수 있게 함
  20. Platfrom-specific Deps • Pants 플러그인 : tools/pants-plugins/platform_resources • 추가한 기능

    Backend.AI Agent의 의존성으로 추가되는 pre-built executable을 명시적으로 지정한 타겟 플랫폼 옵션에 따라 서로 다른 리소스 파일을 사용하도록 함 다른 플러그인과 달리 Pants 내부의 의존성 관리 API가 버전에 따라 자주 바뀌어서 매번 호환성 업데이트가 필요함 (향후 안정화될 수 있음)
  21. towncrier Tool • Pants 플러그인 : tools/pants-plugins/towncrier • 추가한 기능

    black, isort, flake8 등과 같이 Python 소스파일 타겟을 위한 추가 도구로 towncrier를 정의함 다른 도구처럼 towncrier도 별도의 lock 파일과 전용 venv 환경 하에서 실행될 수 있도록 함
  22. Backend.AI 동적 모듈 로딩 • 직접 BUILD 파일 검색 및

    parsing하는 자체 모듈 로더 개발 https://github.com/lablup/backend.ai/blob/main/src/ai/backend/plugin/entrypoint.py • ./pants run ... 대신 ./pants export :: 실행한 후 ./py wrapper 스크립트 활용 pex가 생성한 별도의 실행환경에는 package metadata도 없고 BUILD 파일도 없음 ㅠㅠ
  23. 좋았던 점들 • 새로운 릴리즈를 PyPI에 배포하는 시간이 10분 이하로

    감소 의존성이 있는 컴포넌트들을 순차적으로 릴리즈하고 CI 작업을 돌릴 필요가 없어짐 • 각종 릴리즈 관련 작업들의 자동화 다수 저장소에 중복으로 해야 했던 일을 한번만 하면 됨 예) CHANGELOG.md에서 마지막 버전의 변경 내용만 추출하여 GitHub Release 설명 등록 • 하나의 이슈는 하나의 PR로 완결 여러 컴포넌트에 걸친 리팩토링(특히 컴포넌트 사이의 의존성이 있을 때)을 하나의 PR로 완결 개발 관련 문서 업데이트까지 하나의 PR 안에서 완결 위와 같은 변화로 인해 GitHub Projects v2 대시보드를 더욱 유용하게 활용할 수 있게 됨 • 변경 범위만 테스트 (모노리포의 코드 양이 많아도 CI 소요 시간 영향 최소화) ./pants test --changed-since=main
  24. 적응이 필요했던 점들 (1/3) • Pants 자체 Python 요구 버전과

    Backend.AI의 Python 요구 버전 불일치 → 다중 Python 설치 Pants는 Apple Silicon Mac에서 Python 3.9 환경을 요구함 Backend.AI는 Python 3.10 환경을 요구함 macOS Monterey는 Python 3.8을 기본 제공 Homebrew, pyenv 등을 언제 어떻게 설치했느냐에 따라 Python 3.9가 없다면... ✓ 가장 빠른 해결 방법 : brew install [email protected] 또는 pyenv local 3.9.13 pyenv나 venv에 대한 경험이 적은 기여자들의 경우 여기서 많은 삽질을 하게 됨 (예 : 오픈소스 컨트리뷰션 아카데미 ... 🙇)
  25. 적응이 필요했던 점들 (2/3) • PYTHONPATH 환경변수를 하위 프로세스에 전달하는

    것을 종종 까먹어서 겪는 import error Pants 환경에서는 pants.toml에 설정된 source_roots를 하위 프로세스에서도 인식해야 함 • Pants가 인식 못하는 내부 의존 관계 때문에 패키지 배포 후 발생하는 "module not found" 오류들 importlib.import_module() 함수를 이용한다거나 SQLAlchemy가 database URL로부터 엔진을 결정하여 import할 모듈을 선택한다거나 아직까지 이 부분을 자동으로 의존성 추론하게 하는 방법은 찾지 못함
  26. 적응이 필요했던 점들 (3/3) • 기존 테스트 코드들이 병렬로 잘

    실행되도록 하기까지 많은 노력 필요 특히 database 컨테이너를 고정 포트 번호로 띄우는 경우 충돌 발생 ✓ pants.toml 설정 : [pytest].execution_slot_var = "BACKEND_TEST_EXEC_SLOT" ✓ 컨테이너 생성 fixture에서 이 환경변수 값을 포트 번호에 더하여 중복 사용 방지 • Ubuntu 22.04 + Snap + Docker + /tmp Docker가 Snap을 통해 설치된 경우 host /tmp 대신 가상 private /tmp를 사용하도록 강제됨 각종 테스트 코드에서 /tmp 디렉토리에 임시 생성한 파일을 컨테이너가 못 읽어서 한참 동안 삽질 사실 이건 Pants 자체의 문제는 아니지만, 컨테이너에 의존하는 테스트 코드 병렬 실행 가능하도록 변환하는 과정에서 fixture들이 각 테스트 모듈마다 서로 다른 임시디렉토리를 사용하게 하다보면 거의 필연적으로 겪는 문제 ✓ /tmp 대신 ./.tmp를 사용하는 방식으로 우회
  27. 총평 • 언제나 그렇듯 생각보다 오래 걸림 (원래 계획은 2주였으나

    꼬박 한달 넘게...) • 하지만 이후의 개발 프로세스 경험을 고려하면 충분히 가치있는 일이었음. 강력한 캐싱 기능과 의존성 기반 변경사항 탐지 기능으로 모노리포로 합친 후에도 기본적인 작업 프로세스가 크게 느려지지 않음 ✓ black + isort + git hook 도입, release note 생성 자동화, 변경 영향 범위만 테스트 실행 테스트 병렬화로 향후 GitHub Action Runner 사양을 높였을 때 큰 이득을 볼 수 있는 길이 열림 • 이전 작업 과정에서 Pantsbuild 커뮤니티 슬랙에서 매우 많은 도움을 받음 친절한 코어 커뮤니티의 중요성 체감 문서 수정, 버그 보고, PyCon 발표 등으로 최대한 되돌려주기 위해 노력 • Python 기반 mono-repo를 고민한다면 Pants 고려해봅시다!