Bagaimana Kami Mengurangi Ukuran Git Monorepo JavaScript sebesar 94%
(jonathancreamer.com)- Monorepo JavaScript besar milik Microsoft, 1JS, telah berkembang menjadi sekitar 1.000 pengguna aktif bulanan, sekitar 2.500 paket, dan sekitar 20 juta baris kode; baru-baru ini ukuran clone-nya mencapai 178GB, sehingga sebagian developer kesulitan bahkan untuk melakukan clone
- Bottleneck awal yang terlihat adalah struktur file perubahan Beachball yang menumpuk puluhan ribu file dalam satu folder; ketika jumlah file mencapai 40.000, setiap penambahan file baru membuat tree object besar
- Penyebab yang lebih besar adalah, dalam situasi nama file panjang seperti
CHANGELOG.mddanCHANGELOG.jsondiperbarui berulang kali, kode packing Git yang lama memilih target delta hanya dengan melihat 16 karakter terakhir nama file git repack -adf --window=250juga efektif, tetapi ketika menjalankan packing berbasis path,git repack -adf --path-walk, pada fork Microsoft Git2.47.0.vfs.0.2, repositori lokal menyusut dari 178GB menjadi 5GB- Jika dalam monorepo besar ada file bernama panjang yang sering berubah, perlu memeriksa
pack.usePathWalk truedan perintah barugit survey; perubahan terkait juga akan masuk ke upstream Git
Skala dan bottleneck monorepo 1JS
- Monorepo JavaScript besar internal Microsoft, 1JS, besar bukan hanya dari sisi kapasitas repositori, tetapi juga dari skala kode dan kontribusinya
- Lebih dari 1.000 pengguna aktif bulanan
- Sekitar 2.500 paket
- Sekitar 20 juta baris kode
- Ukuran repositori yang baru di-clone adalah 178GB, dan sebagian developer di Eropa tidak dapat melakukan clone karena ukurannya
- Saat pertama kali di-clone ukurannya berada di kisaran 1–2GB, tetapi beberapa bulan kemudian membesar hingga sekitar 4GB, lalu terus meningkat setelahnya
Pelajaran #1: Jangan menumpuk ribuan file dalam satu folder
git-sizermengungkap masalah blob besar dan file perubahan Beachball- Blob besar muncul saat binary tidak sengaja di-commit, dan dapat dicegah dengan fitur seperti batas ukuran check-in di Azure DevOps
- File yang sudah masuk ke history tidak mudah dihapus setelahnya
- 1JS menggunakan Beachball change files untuk mengotomatiskan kenaikan rentang semver paket
- Tujuannya mirip dengan Changesets
- Sasarannya adalah mengotomatiskan kenaikan versi paket seperti semantic-release
- Karena file perubahan tidak dihapus, jumlahnya menumpuk hingga 40.000 dalam satu folder, dan dalam kondisi ini penambahan file baru menghasilkan tree object besar
- Ada dua langkah penanganan
- Melalui Beachball PR, diubah agar beberapa perubahan dimasukkan ke satu file perubahan, bukan satu file per paket
- Membuat pipeline yang secara berkala membersihkan folder file perubahan secara otomatis
Pelajaran #2: Masalah branch versioned dan packing changelog
- Alur manajemen versi 1JS mempertahankan branch
versionedyang merupakan mirror darimain- Menyimpan versi paket yang sebenarnya untuk menghindari konflik Git di
main - Memungkinkan pemetaan yang akurat antara versi semver yang dirilis sebagai paket NPM dan commit Git
- Menyimpan versi paket yang sebenarnya untuk menghindari konflik Git di
- Branch
versionedterutama hanya berisi commit yang menambahkan konten ke fileCHANGELOG.mddanCHANGELOG.json, tetapi ukurannya membesar hingga clone makin sulit dilakukan - Repositori memang tumbuh secara alami sejak 2021 dengan penambahan ratusan ribu baris kode dan ratusan developer setiap tahun, tetapi ketika laju pertumbuhannya menjadi lebih besar daripada monorepo Office milik Microsoft, muncul kecurigaan ada penyebab tersendiri
- Setelah diperiksa oleh developer yang membuat fitur Git untuk monorepo besar seperti
shallow checkoutdansparse index, diketahui bahwa saat mengambil branchversioned, ada 125GB data Git tambahan yang di-fetch
Cara kode packing Git membuat delta yang salah
- Penyebabnya adalah perilaku kode packing Git lama yang, saat membandingkan file sebelum kompresi, hanya memeriksa 16 karakter terakhir nama file
- Biasanya Git hanya melakukan push diff dari file yang berubah, tetapi karena masalah packing ini, file
CHANGELOG.mddari paket yang berbeda saling dibandingkan - Misalnya, ketika
repo/packages/foo/CHANGELOG.jsondiubah, Git dapat membuat diff denganrepo/packages/bar/CHANGELOG.jsondalam proses persiapan push - Akibatnya, dalam banyak kasus seluruh file berulang kali di-push ulang, dan sebagian file bisa berukuran puluhan MB
- Detail masalah ini dirangkum dalam tulisan Stolee di mailing list Git
Hasil repack dan path-walk
- Pertama,
git repack -adf --window=250digunakan untuk memperbaiki kompresi file pack dengan window yang lebih besar, dan ukuran repositori turun signifikan - Setelah itu, git-for-windows PR #5171 menambahkan metode packing yang menelusuri path Git, menggantikan cara default yang menelusuri commit
- Pada Microsoft git, fork Git milik Microsoft,
git version 2.47.0.vfs.0.2, clone dilakukan lalu repack dijalankan dengan metode baru- Perintah yang dijalankan:
git repack -adf --path-walk - Hasil: ukuran repositori turun dari 178GB menjadi 5GB
- Perintah yang dijalankan:
- Opsi konfigurasi baru juga memastikan delta yang benar dibuat saat push
git config --global pack.usePathWalk true
- Developer yang menggunakan
git version 2.47.0.vfs.0.2dapat menjalankan repack setelah melakukan clone lokal, lalu menekan laju pertumbuhan dengan algoritme push path-walk baru
Dampak pada GitHub, Azure DevOps, dan upstream Git
- GitHub menjalankan repack dan Git garbage collection secara berkala, tetapi metode packing GitHub saat ini belum dapat menghitung delta dengan benar untuk file seperti
CHANGELOG.mddanCHANGELOG.json - Kasus nama file panjang yang memiliki lebih dari 16 karakter terakhir yang sama dan sering berubah seiring waktu juga dapat terdampak
- File string besar untuk i18n diberikan sebagai contoh
- Azure DevOps yang digunakan 1JS belum menjalankan repack semacam ini, dan pekerjaan terkait sedang dilakukan untuk mengurangi ukuran repositori di sisi server juga
- Perubahan terkait juga akan masuk ke upstream Git
Hal yang perlu diperiksa di monorepo besar
- Jika dalam monorepo berskala besar
CHANGELOG.mdatau file dengan nama relatif panjang diperbarui berulang kali, perubahan terkait path-walk perlu dipantau - Perintah baru
git surveymenyediakan heuristik untuk menilai kondisi repositori- Top Files By Disk Size
- Top Directories By Inflated Size
- Top Files By Inflated Size
- Heuristik ini membantu memperkirakan apakah pekerjaan path-walk juga akan berdampak pada ukuran repositori tersebut
1 komentar
Komentar Hacker News
Soal di mana perintah baru
git-surveyberada: perintah itu belum masuk ke git.gitPenulis memakai fork Git milik Microsoft, dan perintah ini ditambahkan musim panas ini: https://github.com/microsoft/git/pull/667
full-name-hashdanpath-walkjuga tampaknya hanya ada di fork tersebut atau hanya di Git HEADTidak bisa ditemukan di halaman manual maupun catatan perubahan 2.47
Saya penasaran apa maksudnya “orang-orang di Eropa bahkan tidak bisa mengkloning karena ukuran repositori”
Aneh kalau repositori besar tidak bisa diunduh di Eropa tetapi bisa di tempat lain, dan terdengar seolah mereka tetap tidak akan bisa mengkloning sampai perubahan di sisi server diterapkan
Kalimat “terus-menerus mendorong ulang seluruh file” sepertinya terpotong di tengah, dan saat membaca artikel itu, GIF-nya terlalu mengganggu serta tetap terlihat bahkan dalam mode baca
Kalau ditulis lebih lengkap, kira-kira seperti “Ada seseorang yang mencoba mengkloning seluruh repositori ke laptop untuk dipakai saat bepergian di Eropa, tetapi saat itu disknya penuh sehingga tidak bisa. Setelah itu ia membersihkan disk dan berhasil mengkloningnya”
Saya rasa ini bukan berarti ada masalah besar bahwa Eropa tidak sanggup menangani file 180GB
Apalagi jika orang tersebut memakai VPN, terlebih lagi trik “SSL VPN” yang buruk; perangkat lunak dengan penanganan latensi yang buruk atau server dengan window scaling yang rusak sampai sekarang pun masih mengejutkan sering ditemukan
Jika sebagian besar developer memakai koneksi dengan bandwidth bagus dan dekat dengan server, masalah seperti ini tidak begitu terlihat
Clone Azure DevOps sama sekali tidak mampu memenuhi koneksi gigabit kabel, dan karena memakan waktu terlalu lama, pada akhirnya prosesnya berhenti entah di sisi saya atau di sisi Azure DevOps sehingga saya menyerah
Untungnya hampir semua pekerjaan saya dilakukan dengan shallow clone di dalam Codespaces, jadi ini bukan masalah besar; kalau repositori kami juga mengalami ledakan ukuran yang mirip dengan masalah repositori 1JS di artikel ini, saya berharap itu diperbaiki
Pembaruan: ini kesalahan konyol, dan nama file tidak menyertakan path lengkap
Penjelasannya sepertinya tertimbun di antara GIF, tetapi 16 karakter terakhir di sini berbeda satu sama lain: contohnya adalah mengubah
repo/packages/foo/CHANGELOG.md, tetapi saat Git menyiapkan push, Git membandingkannya denganrepo/packages/bar/CHANGELOG.mdlalu membuat diffCover letter API
path-walkjuga layak dibaca: https://lore.kernel.org/all/pull.1786.git.1725935335.gitgitg...Contoh di blog memang tidak terlalu jelas, tetapi Git pada dasarnya mengambil semua versi dari semua file di dalam repository, memasukkan 16 byte terakhir dari path ke hash table, lalu mengelompokkan hal-hal yang diharapkan sebagai versi berbeda dari file yang sama untuk delta compression
foo/CHANGELOG.mddanbar/CHANGELOG.mddalam tulisan itu hanya 13 karakter, jadi contoh tersebut tidak tepat apa adanya; kita harus membayangkan path dengan sufiks bersama yang lebih panjangBagian ini diperbaiki dengan opsi
--full-name-hash, sehingga sekarang yang dibandingkan adalah path lengkap, bukan 16 bytePeningkatan ukuran window yang dibahas setelahnya lebih mirip hack untuk mengakali pengelompokan file yang buruk, bukan perbaikan sebenarnya
Karena masih memberi input yang buruk sekali ke compressor dan mengakalinya dengan memakai memori sangat besar, penyajiannya sebagai solusi terasa membingungkan
Bagian yang benar-benar menarik adalah path-walk API dan
--full-name-hashSaya penasaran apakah nama file di sini berarti path, atau Git memilih sembarang file dengan nama yang sama lalu membuat diff
Saya juga ingin tahu apakah ada pola tertentu dalam cara file lain dipilih
Lihat pesan commit pertama dari PR
--full-name-hash: https://github.com/git-for-windows/git/pull/5157/commits/d5c...Mengutipnya,
"/CHANGELOG.json"berjumlah 15 karakter dan dibuat oleh tool beachball; hanya huruf terakhir direktori induk yang bisa membedakan versi-versi berbeda dari file ini, dan itupun hanya 2 bit teratas yang tercerminJika huruf itu berupa karakter alfabet, tabrakan selalu terjadi, dan path
"/CHANGELOG.md"juga memiliki masalah serupa, meski ada sedikit lebih banyak ruang untuk perbedaan pada direktori indukAlgoritma pengelompokan memberi bobot lebih rendah untuk tiap karakter semakin jauh posisinya dari sisi kanan nama:
hash = (hash >> 2) + (c << 24)Hash-nya 32-bit, dan tiap karakter 8-bit dari path lengkap ditambahkan berurutan ke 8 bit teratas hash, tetapi karena bit hash yang sudah ada digeser 2 bit ke kanan, pada akhirnya hanya 16 karakter terakhir yang memengaruhi hash akhir
Apa yang sebenarnya terjadi bisa dilihat di sini: https://go.dev/play/p/JQpdUGXdQs7
Karena sudah dipindahkan ke Go dan membandingkan nilai akhir
"aaa/CHANGELOG.md"dan"zzz/CHANGELOG.md", coba masukkan berbagai nilai padaaaadanzzzuntuk melihat betapa kecil pengaruhnya terhadap nilai akhir16 karakter terakhir dari
CHANGELOG.mdadalah seluruh nama file, dan jika ditafsirkan begitu, itu juga menjelaskan mengapa solusifilepathwalkmenyelesaikan masalahTetapi jika benar-benar hanya berdasarkan 16 karakter terakhir nama file dan bukan path lengkap, rasanya masalah ini seharusnya jauh lebih sering terjadi, terutama di monorepo
Mungkin ini membingungkan karena metavariabel
foodanbar; kemungkinan nama paket aslinya memiliki sufiks bersama sepertifirstPkg,secondPkg"/CHANGELOG.md"saja sudah 13 karakter, jadi sufiks bersama 3 karakter saja sudah menimbulkan tabrakan 16 karakterSaya langsung mencobanya pada
nixpkgs; jika dikloning apa adanya dari GitHub, ukurannya sekitar 5GBOpsi pertama yang disebut di tulisan,
--window 250, menurunkan ukurannya menjadi 1,7GB, sedangkan opsi baru--path-walkdari fork Microsoft Git kurang efektif dan menghasilkan total 1,9GBKeduanya kurang dari separuh ukuran awal, jadi saya berharap GitHub punya cara untuk menjalankan pekerjaan seperti ini, dan akan lebih baik lagi jika orang mulai menghosting dengan cara yang memungkinkan kontrol langsung atas hal semacam ini
Tulisan itu menyebut Derrick Stolee, yang menggali perubahan yang diperlukan dan merilisnya
Jika tertarik pada struktur internal Git, pengurangan ukuran clone Git di lokal dan CI, Derrick telah menulis banyak artikel bagus di blog GitHub: https://github.blog/author/dstolee/
Situs pribadinya juga layak dilihat: https://stolee.dev/
Jika tanpa sengaja meng-commit biner besar, cara memperbaikinya bisa dianggap dengan menjalankan
filter-branchuntuk menghapus biner bermasalah, mengajari dan mengonfigurasi semua orang agar memakai git-lfs untuk biner, lalu melakukan force push dan mengembalikan lingkungan kerja masing-masing ke kondisi normalMemang tidak ideal, tetapi lebih baik daripada terus menyimpan file besar yang bahkan tidak dipakai di Git
filter-branch, ada juga BFG: https://rtyley.github.io/bfg-repo-cleaner/Seperti yang ditunjukkan orang lain, masalah ini berkaitan dengan file kecil yang sering berubah, jadi untuk menghemat ruang, hapus versi-versi lama dari histori dan gunakan LFS mulai sekarang
Mengutak-atik Git memang mungkin menyenangkan, tapi saya bertanya-tanya apakah sejak awal tidak ada cara untuk tidak memasukkan 2.500 paket ke dalam monorepo
Semakin besar codebase, semakin sulit berharap orang tidak menciptakan ulang roda yang sudah ada, karena mereka tidak tahu kode itu ada, atau takut saat mengubahnya agar menangani use case mereka malah merusak penggunaan yang sudah ada
Secara intuitif, rasanya waktu yang dibutuhkan untuk naik dari 100 modul ke 200 modul lebih sedikit daripada dari 50 modul ke 100 modul
Ini terlihat seperti masalah yang sepenuhnya mereka ciptakan sendiri
Setelah melihat presentasi GitMerge terbaru yang diunggah GitButler, saya mengikuti perkembangan terkait monorepo dan skalabilitas, dan ada banyak hal bagus yang muncul dari Microsoft, GitHub, dan GitLab
Saya ingin lebih memahami bagaimana masalah pemeriksaan 16 karakter terakhir dan pemeriksaan seluruh path berinteraksi dengan kompresi delta, pack index, dan multi-pack index
Senang melihat Microsoft memakai Azure DevOps sendiri
Sepertinya makin banyak layanan Azure hanya menyediakan konektor native ke GitHub, jadi saya kira Azure DevOps sedang menuju arah ditelantarkan
Bisa punya orang yang memahami struktur internal Git sedalam itu dalam jangkauan untuk membantu pasti merupakan keuntungan besar saat mengerjakan proyek seperti ini di perusahaan sebesar itu