7 puan yazan GN⁺ 2024-10-28 | 1 yorum | WhatsApp'ta paylaş
  • Microsoft’un büyük JavaScript monorepo’su 1JS, aylık 1.000 aktif kullanıcı, yaklaşık 2.500 paket ve yaklaşık 20 milyon satır koda ulaştı; yakın zamanda klon boyutu 178 GB’a çıktığı için bazı geliştiriciler depoyu klonlamakta bile zorlandı
  • Başta ortaya çıkan darboğaz, Beachball değişiklik dosyalarının tek bir klasörde on binlerce dosya biriktiren yapısıydı; dosya sayısı 40.000’e ulaşınca her yeni dosya eklendiğinde büyük bir tree object oluşuyordu
  • Daha büyük neden ise CHANGELOG.md ve CHANGELOG.json gibi uzun dosya adlarının tekrar tekrar güncellendiği durumlarda, Git’in mevcut paketleme kodunun delta hedefini seçerken dosya adının yalnızca son 16 karakterine bakmasıydı
  • git repack -adf --window=250 de etkili oldu, ancak yol tabanlı paketleme olan git repack -adf --path-walk Microsoft Git fork’u 2.47.0.vfs.0.2 üzerinde çalıştırıldığında yerel depo 178 GB’tan 5 GB’a düştü
  • Büyük monorepo’larda uzun adlı dosyalar sık değişiyorsa pack.usePathWalk true ve yeni git survey komutuna bakmak gerekiyor; ilgili değişiklikler Git upstream’e de yansıtılacak

1JS monorepo’sunun ölçeği ve darboğazı

  • Microsoft içindeki büyük JavaScript monorepo’su 1JS, yalnızca depo boyutu açısından değil, kod ve katkı ölçeği açısından da büyük
    • Aylık 1.000’den fazla aktif kullanıcı
    • Yaklaşık 2.500 paket
    • Yaklaşık 20 milyon satır kod
  • Yakın zamanda klonlanan deponun boyutu 178 GB idi ve bazı Avrupalı geliştiriciler boyutu nedeniyle depoyu klonlayamıyordu
  • İlk klonlandığında 1–2 GB düzeyindeydi, ancak birkaç ay sonra yaklaşık 4 GB’a çıktı ve sonrasında da büyümeye devam etti

Ders #1: Tek bir klasöre binlerce dosya yığmamak

  • git-sizer, büyük blob’ları ve Beachball değişiklik dosyaları sorununu ortaya çıkardı
  • Büyük blob’lar genellikle binary dosyaların yanlışlıkla commit edilmesiyle oluşur; Azure DevOps’un check-in boyutu sınırı gibi özelliklerle engellenebilir
    • Geçmişe zaten girmiş dosyaları sonradan kaldırmak kolay değildir
  • 1JS, paketlerin semver aralığı artışını otomatikleştirmek için Beachball change files kullanıyordu
  • Değişiklik dosyaları silinmediği için tek bir klasörde 40.000’e kadar birikti; bu durumda yeni bir dosya eklendiğinde büyük bir tree object oluşturuluyordu
  • İki önlem alındı
    • Beachball PR ile paket başına bir dosya yerine birden çok değişikliğin tek bir değişiklik dosyasında tutulması sağlandı
    • Değişiklik dosyaları klasörünü periyodik olarak otomatik temizleyen bir pipeline yazıldı

Ders #2: versioned branch ve changelog paketleme sorunu

  • 1JS’in sürüm yönetimi akışı, main’in aynası olan versioned branch’ini korur
    • Gerçek paket sürümlerini saklayarak main üzerinde Git çakışmalarını önler
    • NPM paketi olarak yayınlanan semver sürümleri ile Git commit’leri arasındaki eşleşmeyi doğru biçimde görmeyi sağlar
  • versioned branch’i çoğunlukla CHANGELOG.md ve CHANGELOG.json dosyalarına içerik ekleyen commit’lerden oluşuyordu, ancak klonlamayı giderek zorlaştıracak kadar büyüdü
  • Depo, 2021’den bu yana her yıl yüz binlerce satır kod ve yüzlerce geliştirici eklenmesiyle doğal olarak büyüdü; ancak büyüme oranı Microsoft’un Office monorepo’sundan daha yüksek hale gelince ayrı bir nedenden şüphelenildi
  • Git’in shallow checkout, sparse index gibi büyük monorepo’lara yönelik özelliklerini geliştiren bir geliştirici incelediğinde, versioned branch’i alınırken 125 GB ek Git verisi fetch edildiğini gördü

Git paketleme kodu yanlış deltaları nasıl oluşturuyordu?

  • Neden, eski Git paketleme kodunun dosyaları sıkıştırmadan önce karşılaştırma yaparken dosya adının yalnızca son 16 karakterini kontrol etmesiydi
  • Normalde Git yalnızca değişen dosyanın diff’ini push eder; ancak bu paketleme sorunu nedeniyle farklı paketlere ait CHANGELOG.md dosyaları birbirleriyle karşılaştırılıyordu
  • Örneğin repo/packages/foo/CHANGELOG.json değiştirildiğinde, Git push hazırlığı sırasında repo/packages/bar/CHANGELOG.json ile bir diff oluşturabiliyordu
  • Bu yüzden birçok durumda tüm dosya tekrar tekrar push ediliyordu; bazı dosyaların boyutu onlarca MB olabiliyordu
  • Sorunun ayrıntıları Stolee’nin Git mailing list yazısında özetleniyor

repack ve path-walk sonuçları

  • Önce git repack -adf --window=250 ile daha büyük bir window kullanılarak pack dosyası sıkıştırması iyileştirildi ve depo boyutu belirgin biçimde küçüldü
  • Ardından git-for-windows PR #5171, commit’ler üzerinde yürüyen varsayılan yöntem yerine Git yolları üzerinde yürüyen bir paketleme yöntemi ekledi
  • Microsoft’un Git fork’u olan Microsoft git git version 2.47.0.vfs.0.2 ile yeni yöntem kullanılarak klonlandıktan sonra repack çalıştırıldı
    • Çalıştırılan komut: git repack -adf --path-walk
    • Sonuç: depo boyutu 178 GB’tan 5 GB’a düştü
  • Yeni yapılandırma seçeneği, push sırasında da doğru deltaların oluşturulmasını sağlar
    • git config --global pack.usePathWalk true
  • git version 2.47.0.vfs.0.2 kullanan geliştiriciler, yerel klondan sonra repack çalıştırıp yeni path-walk push algoritmasıyla büyüme oranını düşürebilir

GitHub, Azure DevOps ve upstream Git’e etkisi

  • GitHub periyodik olarak repack ve Git garbage collection gerçekleştirir, ancak GitHub’ın mevcut paketleme yöntemi CHANGELOG.md, CHANGELOG.json gibi dosyalar için deltaları doğru hesaplayamıyor
  • Zaman içinde sık değişen ve son 16 karakterden fazlası aynı olan uzun dosya adları da etkilenebilir
    • Örnek olarak i18n için kullanılan büyük string dosyaları gösteriliyor
  • 1JS’in kullandığı Azure DevOps henüz bu tür bir repack yapmıyor; sunucu tarafındaki depo boyutunu da küçültmek için ilgili çalışmalar yürütülüyor
  • İlgili değişiklikler Git upstream’e de yansıtılacak

Büyük monorepo’larda kontrol edilmesi gerekenler

  • Büyük ölçekli monorepo’larda CHANGELOG.md veya nispeten uzun adlı dosyalar tekrar tekrar güncelleniyorsa path-walk ile ilgili değişiklikleri takip etmek gerekiyor
  • Yeni git survey komutu, depo durumunu değerlendirebilecek heuristics sağlar
    • Top Files By Disk Size
    • Top Directories By Inflated Size
    • Top Files By Inflated Size
  • Bu heuristics, path-walk çalışmasının ilgili depo boyutu üzerinde de etkili olup olmayacağını tahmin etmeye yardımcı olur

1 yorum

 
GN⁺ 2024-10-28
Hacker News yorumları
  • Yeni git-survey komutu nerede diye sorarsanız, henüz git.git içine girmiş değil
    Yazar Microsoft'un Git fork'unu kullanıyor ve bu komut bu yaz eklenmiş: https://github.com/microsoft/git/pull/667

    • full-name-hash ve path-walk da yalnızca fork'ta ya da Git HEAD'de var gibi görünüyor
      Kılavuz sayfasında ya da 2.47 değişiklik günlüğünde bulunamıyor
  • “Avrupa'daki insanlar depo boyutu yüzünden clone bile edemiyor” ifadesinin ne anlama geldiğini merak ediyorum
    Büyük bir deponun Avrupa'da indirilemeyip başka yerlerde indirilebilmesi tuhaf; ayrıca sunucu tarafındaki değişiklik uygulanana kadar hâlâ clone edilemeyecekmiş gibi duyuluyor
    “Dosyanın tamamını sürekli yeniden push etti” cümlesi sanki ortasından kesilmiş gibi; yazıyı okurken GIF de çok dikkat dağıtıyor ve okuma modunda bile aynen görünüyor

    • Ben o cümleyi bir anekdot gibi okudum
      Daha eksiksiz yazılsa muhtemelen “Avrupa'da seyahat ederken kullanmak için tüm depoyu dizüstü bilgisayarına clone etmeye çalışan biri vardı; o sırada diski dolu olduğu için yapamadı. Sonra diski temizleyip clone edebildi” gibi olurdu
      Bunun Avrupa'nın 180 GB'lık dosyayı kaldıramadığına dair büyük bir sorun anlamına geldiğini sanmıyorum
    • Burada “Avrupa” muhtemelen yüksek gecikme, düşük bant genişliği için kullanılan bir temsil
      Özellikle kişi VPN, hele de berbat “SSL VPN” numaralarından birini kullanıyorsa daha da öyle; gecikmeyle kötü başa çıkan yazılımlar veya bozuk window scaling'e sahip sunucular hâlâ şaşırtıcı derecede yaygın
      Geliştiricilerin çoğu sunucuya yakınsa ve iyi bant genişliğine sahip bağlantılar kullanıyorsa bu tür sorunlar pek ortaya çıkmaz
    • Avustralya'dan Microsoft içindeki başka bir ekipte çalışıyorum ve Azure DevOps'ta büyük bir monorepo kullanıyoruz; tam clone'u neredeyse hiç yapamıyorum
      Azure DevOps clone işlemi kablolu gigabit bağlantıyı hiç doyuramıyor; o kadar uzun sürüyor ki sonunda ya benim tarafımda ya da Azure DevOps tarafında duruyor ve vazgeçmek zorunda kalıyorum
      Neyse ki işlerin neredeyse tamamını Codespaces içinde shallow clone ile yaptığım için büyük bir sorun değil; bu yazıdaki 1JS deposu sorununa benzer bir boyut patlaması bizim depoda da varsa düzeltilmesini umuyorum
    • Depo muhtemelen ABD'nin batı kıyısında olduğundan, Avrupa'dan clone ederken her seferinde Atlantik'i aşmak gerekiyor olabilir
    • İnternet altyapısı daha az gelişmiş bir ülkede, örneğin Almanya'da da olabilir
  • Güncelleme: Saçma bir hataydı; dosya adları tam yolu içermiyor
    Açıklama GIF’lerin arasında kaybolmuş gibi, ama burada son 16 karakter birbirinden farklı: repo/packages/foo/CHANGELOG.md değiştirilmişken Git’in push’a hazırlanırken repo/packages/bar/CHANGELOG.md ile karşılaştırıp fark oluşturduğu bir örnek bu

    • Derrick bunu şu kapak mektubunda daha iyi açıklıyor: https://lore.kernel.org/git/pull.1785.git.1725890210.gitgitg...
      path-walk API kapak mektubuna da bakmaya değer: https://lore.kernel.org/all/pull.1786.git.1725935335.gitgitg...
      Blogdaki örnek çok net değil, ama Git aslında depodaki tüm dosyaların tüm sürümlerini alıp yolun son 16 baytını bir hash tablosuna koyuyor ve aynı dosyanın farklı sürümleri olmasını beklediği şeyleri delta sıkıştırma için grupluyordu
      Yazıdaki foo/CHANGELOG.md ve bar/CHANGELOG.md yalnızca 13 karakter olduğundan örnek hâliyle doğru değil; daha uzun ortak soneki olan yollar hayal etmek gerekiyor
      Bu kısım --full-name-hash seçeneğiyle düzeltildi; artık 16 bayt yerine tüm yol karşılaştırılıyor
      Sonrasında bahsedilen pencere boyutunu artırmak, kötü dosya gruplamasını aşmaya yönelik bir hack’e daha yakın; gerçek bir düzeltme değil
      Sıkıştırıcıya hâlâ berbat bir girdi verip bunu muazzam bellek kullanarak aşıyorsunuz; bu yüzden bunun çözüm gibi sunulması kafa karıştırıcıydı
      Asıl ilginç kısım path-walk API ve --full-name-hash
    • Yazıdaki “renkli” anlatıdan ziyade tam olarak ne olduğunu açıklasaydı iyi olurdu
      Burada dosya adının yol anlamına mı geldiğini, yoksa Git’in aynı ada sahip herhangi bir dosyayı seçip fark mı oluşturduğunu merak ediyorum
      Başka dosyanın nasıl seçildiğinde bir örüntü olup olmadığını da bilmek isterdim
    • Tam yolun dikkate alındığı doğru
      --full-name-hash PR’ının ilk commit mesajına bakılabilir: https://github.com/git-for-windows/git/pull/5157/commits/d5c...
      Alıntılarsak, "/CHANGELOG.json" 15 karakter ve beachball aracı tarafından oluşturuluyor; bu dosyanın farklı sürümlerini ayırt edebilen tek şey üst dizinin son harfi, o da yalnızca üstteki 2 bit üzerinden yansıyor
      O harf bir karakterse her zaman çakışıyor; "/CHANGELOG.md" yolu da benzer bir sorun taşıyor ama üst dizinde farklılaşma payı biraz daha var
      Gruplama algoritması, adın sağ tarafından uzaklaştıkça her karaktere daha düşük ağırlık veriyor: hash = (hash >> 2) + (c << 24)
      Hash 32 bit; tüm yolun her 8 bitlik karakteri sırayla hash’in en yüksek 8 bitine ekleniyor, ancak mevcut hash bitleri 2 bit sağa kaydırıldığı için nihai hash’i yalnızca son 16 karakter etkiliyor
      Gerçekte ne olduğunu burada görebilirsiniz: https://go.dev/play/p/JQpdUGXdQs7
      Go’ya taşıyıp "aaa/CHANGELOG.md" ile "zzz/CHANGELOG.md" için nihai değerleri karşılaştırdım; aaa ve zzz yerine çeşitli değerler koyarsanız nihai değeri ne kadar az etkilediklerini görürsünüz
    • Dosya adı mutlaka tam yolu içermiyor
      CHANGELOG.md’nin son 16 karakteri dosya adının tamamı ve böyle yorumlandığında filepathwalk çözümünün sorunu neden çözdüğünü de açıklıyor
      Ama gerçekten dosya adının yalnızca son 16 karakterine dayanıyorsa ve tam yol değilse, özellikle monorepo’larda bu sorunun çok daha yaygın olması gerekirdi gibi geliyor
    • O kısım benim de gözüme takıldı
      Muhtemelen foo ve bar metad değişkenleri kafa karıştırmış; gerçek paket adlarının firstPkg, secondPkg gibi ortak bir soneki olmuş olabilir
      Yalnızca "/CHANGELOG.md" zaten 13 karakter, yani 3 karakterlik ortak bir sonek bile 16 karakter çakışması yaratıyor
  • Hemen nixpkgs üzerinde denedim; GitHub’dan olduğu gibi klonlayınca yaklaşık 5 GB
    Yazıda bahsedilen ilk seçenek olan --window 250 boyutu 1,7 GB’a düşürdü; Microsoft’un Git fork’undaki yeni --path-walk seçeneği ise daha az etkiliydi ve toplam 1,9 GB oldu
    İkisi de başlangıç boyutunun yarısından küçük; keşke GitHub’ın bu tür işlemleri çalıştırmasını sağlayan bir yol olsa, hatta insanlar böyle şeyleri doğrudan kontrol edebilecekleri şekilde barındırmaya başlasa daha da iyi olurdu

  • Yazıda gerekli değişikliği araştırıp yayımlayan Derrick Stolee geçiyor
    Git’in iç yapısı, yerelde ve CI’da Git klon boyutunu küçültme gibi konular ilginizi çekiyorsa Derrick’in GitHub blogunda çok sayıda harika yazısı var: https://github.blog/author/dstolee/
    Kişisel sitesi de bakmaya değer: https://stolee.dev/

  • Yanlışlıkla büyük bir ikili dosyayı commit ettiyseniz, düzeltme yolu filter-branch çalıştırıp sorunlu ikili dosyayı kaldırmak, herkese ikili dosyalar için git-lfs kullanmayı öğretip bunu yapılandırmak, ardından force push yapıp herkesin çalışma ortamını normal hâline döndürmek olarak görülebilir.
    İdeal değil, ama kullanılmayan büyük bir dosyayı Git’te tutmaya devam etmekten daha iyi.

    • filter-branch gözünü korkutanlar için BFG de var: https://rtyley.github.io/bfg-repo-cleaner/
      Başkasının da belirttiği gibi bu sorun küçük ama sık değişen dosyalarla ilgili olduğundan, yerden tasarruf etmek için geçmişteki eski sürümleri kaldırıp bundan sonra LFS kullanmak yeterli.
    • Temel sorun, bir kez eklenip değişmeyen ikili dosyalar değil, sık değişen küçük ikili dosyalar.
    • Bugünlerde önerilen yöntem filter-repo: https://github.com/newren/git-filter-repo
    • Suçu Linus’a atmak daha kolay.
  • Git’i hacklemek eğlenceli olabilir, ama en başta monorepo’ya 2.500 paket koymamanın bir yolu yok mu diye düşünüyorum.

    • Kod satırı sayısı genelde üstel olarak artma eğiliminde.
      Kod tabanı büyüdükçe mevcut tekerleği yeniden icat etmemeyi beklemek zorlaşıyor; çünkü ya kodun varlığından haberdar olunmuyor ya da kendi kullanım senaryosunu karşılayacak şekilde değiştirirken mevcut kullanımları bozma korkusu oluyor.
      Bana kalırsa 50 modülden 100 modüle çıkmak, 100 modülden 200 modüle çıkmaktan daha uzun sürüyor.
    • O zaman ilgili tüm ek yükü üstlenip 2.500 Git deposunu ayrı ayrı tutmak yeterli.
    • Benim de aklıma ilk gelen tam olarak buydu.
      Tamamen kendi kendilerine yarattıkları bir sorun gibi görünüyor.
  • Son dönemde GitButler’ın yayımladığı GitMerge sunumlarını izleyerek monorepo ve ölçeklenebilirlik alanındaki gelişmeleri takip ediyorum; Microsoft, GitHub ve GitLab’dan çok iyi şeyler geliyor.
    Son 16 karakter denetimi ve tam yol denetimi sorunlarının delta sıkıştırma, pack index ve multi-pack index ile nasıl etkileştiğini daha iyi anlamak isterim.

    • Açık kaynak için bu kadar iyi bir şeyse, özel Git komutlarını resmî Git’e geri almak için bir merge request açmayı planlayıp planlamadıklarını merak ediyorum.
  • Microsoft’un Azure DevOps’u bizzat kullandığını görmek güzel.
    Giderek daha fazla Azure hizmetinin yalnızca GitHub’a native connector sunduğunu gördüğüm için Azure DevOps’un ihmal edilmeye doğru gittiğini sanmıştım.

  • Git’in iç yapısını bu kadar iyi bilen birinin elinizin altında olup yardım edebilmesi, bu ölçekte bir şirkette böyle bir proje yaparken gerçekten güzel bir ayrıcalık olmalı.

    • GitHub gibi bir organizasyonla yakın ilişki içinde olmak kesinlikle yardımcı olur; ama bu kadar çok geliştiricisi olan herhangi bir organizasyon, böylesine niş sayılabilecek sorunları çözmek için son derece uzmanlaşmış danışmanlar getirme maliyetini gerekçelendirebilir.