7 poin oleh GN⁺ 2024-10-28 | 1 komentar | Bagikan ke WhatsApp
  • 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.md dan CHANGELOG.json diperbarui berulang kali, kode packing Git yang lama memilih target delta hanya dengan melihat 16 karakter terakhir nama file
  • git repack -adf --window=250 juga efektif, tetapi ketika menjalankan packing berbasis path, git repack -adf --path-walk, pada fork Microsoft Git 2.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 true dan perintah baru git 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-sizer mengungkap 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
  • 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 versioned yang merupakan mirror dari main
    • 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
  • Branch versioned terutama hanya berisi commit yang menambahkan konten ke file CHANGELOG.md dan CHANGELOG.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 checkout dan sparse index, diketahui bahwa saat mengambil branch versioned, 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.md dari paket yang berbeda saling dibandingkan
  • Misalnya, ketika repo/packages/foo/CHANGELOG.json diubah, Git dapat membuat diff dengan repo/packages/bar/CHANGELOG.json dalam 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=250 digunakan 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
  • 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.2 dapat 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.md dan CHANGELOG.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.md atau file dengan nama relatif panjang diperbarui berulang kali, perubahan terkait path-walk perlu dipantau
  • Perintah baru git survey menyediakan 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

 
GN⁺ 2024-10-28
Komentar Hacker News
  • Soal di mana perintah baru git-survey berada: perintah itu belum masuk ke git.git
    Penulis memakai fork Git milik Microsoft, dan perintah ini ditambahkan musim panas ini: https://github.com/microsoft/git/pull/667

    • full-name-hash dan path-walk juga tampaknya hanya ada di fork tersebut atau hanya di Git HEAD
      Tidak 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

    • Saya membaca kalimat itu sebagai sebuah anekdot
      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
    • Di sini “Eropa” kemungkinan besar adalah ungkapan pengganti untuk latensi tinggi, bandwidth rendah
      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
    • Saya bekerja di Australia untuk tim lain di internal Microsoft dan menggunakan monorepo besar di Azure DevOps, dan hampir tidak bisa melakukan clone penuh
      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
    • Mungkin repositorinya berada di pantai barat AS, sehingga setiap kali mengkloning dari Eropa harus menyeberangi Atlantik
    • Bisa juga berada di negara dengan infrastruktur internet yang kurang berkembang, misalnya Jerman
  • 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 dengan repo/packages/bar/CHANGELOG.md lalu membuat diff

    • Derrick menjelaskannya dengan lebih baik di cover letter ini: https://lore.kernel.org/git/pull.1785.git.1725890210.gitgitg...
      Cover letter API path-walk juga 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.md dan bar/CHANGELOG.md dalam tulisan itu hanya 13 karakter, jadi contoh tersebut tidak tepat apa adanya; kita harus membayangkan path dengan sufiks bersama yang lebih panjang
      Bagian ini diperbaiki dengan opsi --full-name-hash, sehingga sekarang yang dibandingkan adalah path lengkap, bukan 16 byte
      Peningkatan 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-hash
    • Saya berharap tulisan itu menjelaskan apa yang sebenarnya terjadi, alih-alih narasi yang “berwarna”
      Saya 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
    • Benar, path lengkap memang diperhitungkan
      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 tercermin
      Jika 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 induk
      Algoritma 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 pada aaa dan zzz untuk melihat betapa kecil pengaruhnya terhadap nilai akhir
    • Nama file tidak selalu mencakup path lengkap
      16 karakter terakhir dari CHANGELOG.md adalah seluruh nama file, dan jika ditafsirkan begitu, itu juga menjelaskan mengapa solusi filepathwalk menyelesaikan masalah
      Tetapi 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
    • Saya juga terganggu oleh bagian itu
      Mungkin ini membingungkan karena metavariabel foo dan bar; kemungkinan nama paket aslinya memiliki sufiks bersama seperti firstPkg, secondPkg
      "/CHANGELOG.md" saja sudah 13 karakter, jadi sufiks bersama 3 karakter saja sudah menimbulkan tabrakan 16 karakter
  • Saya langsung mencobanya pada nixpkgs; jika dikloning apa adanya dari GitHub, ukurannya sekitar 5GB
    Opsi pertama yang disebut di tulisan, --window 250, menurunkan ukurannya menjadi 1,7GB, sedangkan opsi baru --path-walk dari fork Microsoft Git kurang efektif dan menghasilkan total 1,9GB
    Keduanya 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-branch untuk 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 normal
    Memang tidak ideal, tetapi lebih baik daripada terus menyimpan file besar yang bahkan tidak dipakai di Git

    • Bagi yang takut dengan 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
    • Masalah intinya bukan file biner yang masuk sekali lalu tidak berubah, melainkan file biner kecil yang sering berubah
    • Cara yang direkomendasikan belakangan ini adalah filter-repo: https://github.com/newren/git-filter-repo
    • Lebih mudah menyalahkan Linus
  • 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

    • Jumlah baris kode pada umumnya cenderung bertambah secara eksponensial
      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
    • Kalau begitu, tinggal punya 2.500 repositori Git terpisah dengan semua overhead terkaitnya
    • Pikiran pertama saya juga persis itu
      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

    • Kalau memang itu hal yang baik untuk open source, saya penasaran apakah mereka berencana membuka merge request untuk memasukkan kembali perintah Git kustom itu ke Git resmi
  • 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

    • Punya hubungan dekat dengan organisasi seperti GitHub jelas membantu, tetapi organisasi mana pun dengan jumlah developer sebanyak itu bisa membenarkan biaya mendatangkan konsultan yang sangat terspesialisasi untuk memecahkan masalah yang hampir bersifat niche seperti ini