Monorepo 的 Git 挑戰
repo size:Google 的 monorepo 有 80TB+,但大多數公司的 monorepo 在幾 GB 到幾十 GB。幾 GB 的 repo clone 一次要幾分鐘,每次 CI 都 full clone 就是浪費。
大檔案:設計素材、訓練資料、binary artifacts——如果這些放進 git,repo 會快速膨脹而且 git 的 diff/merge 對 binary 沒有意義。
只改一個服務卻要跑所有測試:monorepo 裡有 10 個服務,只改了 orders/ 卻要等所有服務的 CI 跑完,浪費 CI 資源。
Sparse Checkout:只 checkout 你需要的目錄
# 初始化 sparse checkout
git clone --filter=blob:none --sparse https://github.com/company/monorepo.git
cd monorepo
# 只 checkout orders 服務
git sparse-checkout init --cone
git sparse-checkout set services/orders shared/common
# 現在工作目錄只有這兩個目錄,其他服務的檔案不在本機--filter=blob:none(Partial Clone,見下節)配合使用,效果最好。
Partial Clone:只下載你需要的 object
傳統 git clone 會下載整個 repo 的所有 object(所有版本的所有檔案)。Partial Clone 讓 git 延遲下載——你實際 checkout 的檔案才下載。
# blobless clone:有所有的 commit 和 tree,但 blob(檔案內容)延遲下載
git clone --filter=blob:none https://github.com/company/monorepo.git
# treeless clone:更激進,tree object 也延遲下載(適合 CI 環境)
git clone --filter=tree:0 https://github.com/company/monorepo.gitCI 環境配合 --depth 1(shallow clone)可以把 clone 時間從幾分鐘降到幾十秒:
git clone --filter=blob:none --depth 1 --sparse https://github.com/company/monorepo.gitGit LFS:把大檔案存在別的地方
Git LFS(Large File Storage)讓大檔案存在外部 storage(GitHub LFS、GitLab LFS、S3 + git-lfs server),git repo 裡只存一個 pointer。
# 追蹤所有 png 和 psd 檔
git lfs track "*.png" "*.psd" "*.mp4"
# 這會在 .gitattributes 加上 filter 設定
# 之後 add / commit 就跟普通操作一樣
git add design-mockup.psd
git commit -m "add design mockup"LFS 的代價:clone 要有 LFS 支援、LFS storage 通常額外收費(GitHub 免費 1GB)。適合:設計素材、音影片、ML 訓練資料。不適合:頻繁變動的 binary 檔案(每次 push 都要上傳新版本)。
CI 的 Affected 策略
Monorepo 裡只改了一個服務,只需要跑那個服務的測試。工具:
Nx(JavaScript/TypeScript monorepo):nx affected --target=test 只跑受影響的 project。
Turborepo(JavaScript/TypeScript):類似 Nx,有 remote cache(跑過的 task 結果共用)。
Bazel(多語言,Google 開源):依賴圖的極致版,只 rebuild 和 retest 真正 affected 的部分。學習曲線陡。
自製 diff-based CI:
# 找出和 main 有差異的服務
CHANGED_SERVICES=$(git diff --name-only origin/main | grep -o 'services/[^/]*' | sort -u)
# 只跑有改變的服務的測試
for SERVICE in $CHANGED_SERVICES; do
cd $SERVICE && npm test && cd -
doneMonorepo 的 git 策略最終是在「開發體驗(不要下載整個 repo)」和「CI 效率(不要跑不相關的測試)」之間取得平衡。Sparse checkout + Partial clone 解前者,Affected task 解後者。