7 पॉइंट द्वारा GN⁺ 2024-10-28 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • Microsoft का बड़ा JavaScript monorepo 1JS मासिक सक्रिय 1,000 उपयोगकर्ताओं, करीब 2,500 packages और लगभग 2 करोड़ lines of code तक बढ़ चुका था, और हाल में clone size 178GB तक पहुंच गया था, जिससे कुछ developers के लिए clone करना ही मुश्किल हो गया था
  • शुरुआती bottleneck Beachball change files की वह structure थी जिसमें एक folder में दसियों हजार files जमा हो रही थीं; files 40,000 तक बढ़ने पर हर नई file जोड़ने पर बड़ा tree object बन रहा था
  • बड़ा कारण यह था कि CHANGELOG.md और CHANGELOG.json जैसे लंबे filenames बार-बार update हो रहे थे, और Git का पुराना packing code delta target चुनते समय filename के आखिरी 16 अक्षर ही देख रहा था
  • git repack -adf --window=250 भी असरदार रहा, लेकिन Microsoft Git fork 2.47.0.vfs.0.2 में path-based packing git repack -adf --path-walk चलाने पर local repository 178GB से 5GB रह गई
  • बड़े monorepo में लंबे नाम वाली files अक्सर बदलती हैं, तो pack.usePathWalk true और नया git survey command जांचना चाहिए; संबंधित बदलाव Git upstream में भी शामिल किए जाने की योजना है

1JS monorepo का पैमाना और bottleneck

  • Microsoft के अंदर का बड़ा JavaScript monorepo 1JS सिर्फ repository size में ही नहीं, code और contributors के पैमाने में भी बड़ा है
    • 1,000 से ज्यादा monthly active users
    • करीब 2,500 packages
    • लगभग 2 करोड़ lines of code
  • हाल में clone की गई repository का size 178GB था, और कुछ European developers size की वजह से repository clone नहीं कर पा रहे थे
  • पहली बार clone करते समय यह 1–2GB के आसपास था, लेकिन कुछ महीनों बाद करीब 4GB तक बढ़ गया और उसके बाद भी लगातार बढ़ता रहा

Lesson #1: एक folder में हजारों files जमा न करें

  • git-sizer ने बड़े blobs और Beachball change files की समस्या उजागर की
  • बड़े blobs आम तौर पर binaries को गलती से commit करने पर बनते हैं, और Azure DevOps की check-in size limit जैसी सुविधाओं से इन्हें रोका जा सकता है
    • जो files पहले ही history में जा चुकी हों, उन्हें बाद में हटाना आसान नहीं होता
  • 1JS ने packages की semver range increments को automate करने के लिए Beachball change files का उपयोग किया
    • इसका उद्देश्य Changesets जैसा है
    • लक्ष्य semantic-release की तरह package version increments को automate करना है
  • change files delete नहीं की गईं, जिससे एक folder में 40,000 तक files जमा हो गईं, और ऐसी स्थिति में नई file जोड़ने पर बड़ा tree object बनता है
  • इसके लिए दो उपाय किए गए
    • Beachball PR के जरिए package-wise एक file के बजाय कई changes को एक single change file में रखने के लिए संशोधन किया गया
    • change files folder को समय-समय पर अपने आप साफ करने वाली pipeline लिखी गई

Lesson #2: versioned branch और changelog packing की समस्या

  • 1JS का version management flow versioned branch बनाए रखता है, जो main की mirror है
    • यह actual package versions को store करता है ताकि main में Git conflicts से बचा जा सके
    • इससे NPM package के रूप में release हुए semver versions और Git commits के बीच का संबंध ठीक-ठीक देखा जा सकता है
  • versioned branch में मुख्य रूप से CHANGELOG.md और CHANGELOG.json files में content जोड़ने वाले commits ही थे, लेकिन यह इतना बढ़ गया कि clone करना धीरे-धीरे मुश्किल होने लगा
  • 2021 के बाद repository में हर साल लाखों lines of code और सैकड़ों developers जुड़ते गए, इसलिए उसका बढ़ना स्वाभाविक था; लेकिन जब growth rate Microsoft के Office monorepo से भी ज्यादा हो गया, तो अलग कारण पर शक हुआ
  • Git के shallow checkout, sparse index जैसे बड़े monorepo से जुड़े features बनाने वाले developer ने जांच की, तो पता चला कि versioned branch fetch करते समय 125GB अतिरिक्त Git data fetch हो रहा था

Git packing code गलत delta कैसे बना रहा था

  • कारण यह था कि पुराना Git packing code files को compress करने से पहले compare करते समय filename के आखिरी 16 अक्षर ही check करता था
  • आम तौर पर Git बदली हुई file का सिर्फ diff push करता है, लेकिन इस packing problem की वजह से अलग-अलग packages की CHANGELOG.md files आपस में compare हो रही थीं
  • उदाहरण के लिए, अगर repo/packages/foo/CHANGELOG.json बदला जाए, तो Git push की तैयारी के दौरान repo/packages/bar/CHANGELOG.json के साथ diff बना सकता था
  • इसकी वजह से कई मामलों में पूरी file बार-बार फिर से push हो रही थी, और कुछ files दर्जनों MB आकार की हो सकती थीं
  • समस्या की details Stolee के Git mailing list post में संक्षेप में दी गई हैं

repack और path-walk के परिणाम

  • पहले git repack -adf --window=250 से ज्यादा बड़ा window इस्तेमाल करके pack files की compression बेहतर की गई, और repository size काफी घट गया
  • इसके बाद git-for-windows PR #5171 ने commits walk करने के default तरीके के बजाय Git paths walk करने वाला packing method जोड़ा
  • Microsoft के Git fork Microsoft git git version 2.47.0.vfs.0.2 में नए तरीके से clone करने के बाद repack चलाया गया
    • चलाया गया command: git repack -adf --path-walk
    • परिणाम: repository size 178GB से 5GB हो गया
  • नया configuration option push के समय भी सही deltas generate करवाता है
    • git config --global pack.usePathWalk true
  • git version 2.47.0.vfs.0.2 इस्तेमाल करने वाले developers local clone के बाद repack चला सकते हैं, और नए path-walk push algorithm से growth rate कम कर सकते हैं

GitHub, Azure DevOps और upstream Git पर असर

  • GitHub समय-समय पर repack और Git garbage collection करता है, लेकिन GitHub का मौजूदा packing method CHANGELOG.md, CHANGELOG.json जैसी files के deltas सही तरह calculate नहीं कर पाता
  • जिन लंबे filenames के आखिरी 16 से ज्यादा अक्षर समान हों और जो समय के साथ बार-बार बदलते हों, वे भी प्रभावित हो सकते हैं
    • उदाहरण के तौर पर i18n के लिए बड़ी string files का जिक्र किया गया
  • 1JS जिस Azure DevOps का इस्तेमाल करता है, वह अभी इस तरह का repack नहीं करता, और server-side repository size घटाने के लिए संबंधित काम जारी है
  • संबंधित बदलाव Git upstream में भी शामिल किए जाने की योजना है

बड़े monorepo में क्या जांचें

  • बड़े monorepo में अगर CHANGELOG.md या अपेक्षाकृत लंबे नाम वाली files बार-बार update होती हैं, तो path-walk से जुड़े बदलावों पर नजर रखना जरूरी है
  • नया git survey command repository की स्थिति समझने के लिए heuristics देता है
    • Top Files By Disk Size
    • Top Directories By Inflated Size
    • Top Files By Inflated Size
  • ये heuristics यह अंदाजा लगाने में मदद करते हैं कि path-walk का काम उस repository size पर भी असर डालेगा या नहीं

1 टिप्पणियां

 
GN⁺ 2024-10-28
Hacker News की राय
  • नया git-survey command कहाँ है, तो यह अभी git.git में शामिल नहीं हुआ है
    लेखक Microsoft के Git fork का इस्तेमाल कर रहे हैं, और यह command इस गर्मी में जोड़ा गया था: https://github.com/microsoft/git/pull/667

    • full-name-hash और path-walk भी सिर्फ fork में या Git HEAD में ही दिखते हैं
      manual page या 2.47 changelog में नहीं मिलते
  • यह जानने की जिज्ञासा है कि “यूरोप में लोग repository size की वजह से clone भी नहीं कर पा रहे” से क्या मतलब है
    यह अजीब लगता है कि कोई बड़ा repository यूरोप में fetch नहीं किया जा सकता लेकिन कहीं और किया जा सकता है, और server-side बदलाव लागू होने तक तो ऐसा लगता है कि वे फिर भी clone नहीं कर पाएंगे
    “पूरी file को बार-बार दोबारा push किया” वाला वाक्य बीच में कटा हुआ लगता है, और article पढ़ते समय GIF बहुत distract कर रहा था और reading mode में भी वैसा ही दिख रहा था

    • मैंने उस वाक्य को एक anecdote की तरह पढ़ा
      अगर उसे ज्यादा पूरा लिखें तो शायद मतलब यह होगा: “कोई व्यक्ति यूरोप में travel करते समय इस्तेमाल के लिए laptop पर पूरा repository clone करना चाहता था, लेकिन उस समय disk full होने की वजह से नहीं कर पाया। बाद में disk साफ करके clone कर सका”
      मुझे नहीं लगता कि इसका मतलब यह है कि यूरोप 180GB file संभाल नहीं सकता
    • यहाँ “यूरोप” शायद high latency, low bandwidth का proxy expression है
      खासकर अगर संबंधित व्यक्ति VPN, और उसमें भी भयानक “SSL VPN” workaround इस्तेमाल कर रहा हो; और खराब latency handling वाला software या टूटी हुई window scaling वाले server आज भी हैरानीजनक रूप से आम हैं
      अगर ज्यादातर developer server के पास हों और अच्छी bandwidth वाली connection इस्तेमाल कर रहे हों, तो ऐसी समस्या आसानी से सामने नहीं आती
    • मैं Australia से Microsoft के अंदर एक दूसरी team के लिए काम करता हूँ और Azure DevOps का एक बड़ा monorepo इस्तेमाल करता हूँ, और full clone करना लगभग संभव नहीं है
      Azure DevOps clone wired gigabit connection को बिल्कुल saturate नहीं करता, और इसमें इतना समय लग जाता है कि आखिरकार मेरी तरफ या Azure DevOps की तरफ से रुक जाता है और हार माननी पड़ती है
      अच्छी बात है कि लगभग सारा काम Codespaces के अंदर shallow clone से हो जाता है, इसलिए यह बड़ा issue नहीं है; और अगर हमारे repository में भी इस article के 1JS repository जैसी size explosion है, तो उम्मीद है वह fix हो जाएगी
    • शायद repository अमेरिका के west coast पर है, इसलिए यूरोप से clone करते समय हर बार Atlantic Ocean पार करना पड़ता होगा
    • हो सकता है वे कम विकसित internet infrastructure वाले देश, जैसे Germany, में हों
  • अपडेट: यह एक बेवकूफी भरी गलती थी, और फ़ाइल नाम में पूरा path शामिल नहीं होता
    लगता है explanation GIFs के बीच दब गया था, लेकिन यहाँ आख़िरी 16 अक्षर अलग-अलग हैं: उदाहरण यह है कि repo/packages/foo/CHANGELOG.md बदला गया, लेकिन Git ने push की तैयारी के दौरान repo/packages/bar/CHANGELOG.md से compare करके diff बनाया

    • Derrick ने इस cover letter में इसे बेहतर समझाया है: https://lore.kernel.org/git/pull.1785.git.1725890210.gitgitg...
      path-walk API cover letter भी देखने लायक है: https://lore.kernel.org/all/pull.1786.git.1725935335.gitgitg...
      ब्लॉग का उदाहरण बहुत साफ़ नहीं है, लेकिन Git असल में repository के अंदर सभी files के सभी versions लाकर path के आख़िरी 16 bytes को hash table में डाल रहा था, और जिन चीज़ों को वही file के अलग-अलग versions समझने की उम्मीद थी उन्हें delta compression के लिए group कर रहा था
      लेख में foo/CHANGELOG.md और bar/CHANGELOG.md केवल 13 अक्षर हैं, इसलिए example ज्यों का त्यों सही नहीं बैठता; किसी ऐसे path की कल्पना करनी होगी जिसमें लंबा common suffix हो
      यह हिस्सा --full-name-hash option से ठीक किया गया है, ताकि अब 16 bytes नहीं बल्कि पूरा path compare हो
      इसके बाद जिस window size बढ़ाने की बात की गई है, वह खराब file grouping को bypass करने वाला hack जैसा है, असली fix नहीं
      compressor को अब भी बेहद खराब input देकर और बहुत ज़्यादा memory इस्तेमाल करके bypass करने जैसा है, इसलिए इसे solution की तरह पेश करना confusing था
      सच में दिलचस्प हिस्से path-walk API और --full-name-hash हैं
    • लेख की “रंगीन” narrative के बजाय यह बताया जाता कि ठीक-ठीक क्या हो रहा था, तो अच्छा होता
      यहाँ file name से मतलब path है या Git एक ही नाम वाली कोई भी file चुनकर diff बना रहा है, यह जानने की जिज्ञासा है
      यह भी जानना चाहूँगा कि दूसरी file चुनने के तरीके में कोई pattern है या नहीं
    • पूरा path consider किया जाता है, यह सही है
      --full-name-hash PR का पहला commit message देख सकते हैं: https://github.com/git-for-windows/git/pull/5157/commits/d5c...
      अंश के तौर पर, "/CHANGELOG.json" 15 अक्षर का है और beachball tool इसे बनाता है; parent directory का केवल आख़िरी अक्षर ही इस file के अलग-अलग versions को distinguish कर सकता है, और उसमें भी सिर्फ़ top 2 bits ही reflect होते हैं
      अगर वह अक्षर letter है तो हमेशा collision होगा, और "/CHANGELOG.md" path में भी मिलती-जुलती समस्या है, हालांकि parent directory से फर्क पड़ने की थोड़ी ज़्यादा गुंजाइश है
      grouping algorithm नाम के दाईं तरफ़ से दूर जाते-जाते हर character को कम weight देता है: hash = (hash >> 2) + (c << 24)
      hash 32-bit है, और पूरे path के हर 8-bit character को क्रम से hash के सबसे ऊपरी 8 bits में जोड़ता है; लेकिन existing hash bits को दाईं ओर 2 bits shift करने के कारण final hash पर केवल आख़िरी 16 characters का असर पड़ता है
      असल में क्या होता है, यहाँ देखा जा सकता है: https://go.dev/play/p/JQpdUGXdQs7
      Go में port करके "aaa/CHANGELOG.md" और "zzz/CHANGELOG.md" की final values compare की हैं, इसलिए aaa और zzz में अलग-अलग values डालकर देखेंगे तो दिखेगा कि final value पर उनका कितना कम असर होता है
    • file name में पूरा path होना ज़रूरी नहीं है
      CHANGELOG.md के आख़िरी 16 characters ही पूरा file name हैं, और इस तरह समझें तो filepathwalk solution समस्या को क्यों हल करता है, यह भी explain होता है
      लेकिन अगर सच में आधार केवल file name के आख़िरी 16 characters हैं और पूरा path नहीं, तो खासकर monorepo में यह समस्या कहीं ज़्यादा common होनी चाहिए थी
    • मुझे भी वही हिस्सा खटका
      शायद foo और bar जैसे meta variables की वजह से confusion हुआ, और असली package names में firstPkg, secondPkg जैसा common suffix रहा होगा
      सिर्फ़ "/CHANGELOG.md" ही 13 characters है, इसलिए common suffix के 3 characters भी हों तो 16-character collision हो जाता है
  • मैंने इसे तुरंत nixpkgs पर test किया; GitHub से ज्यों का त्यों clone करने पर यह लगभग 5GB है
    लेख में बताए गए पहले option --window 250 ने size को घटाकर 1.7GB कर दिया, और Microsoft Git fork का नया --path-walk option कम effective रहा, जिससे total 1.9GB हुआ
    दोनों ही initial size के आधे से कम हैं, इसलिए अच्छा होगा अगर GitHub के पास ऐसे काम run करने का कोई तरीका हो; और उससे भी बेहतर होगा अगर लोग ऐसी चीज़ों को सीधे control कर सकने वाले तरीके से hosting शुरू करें

  • लेख में Derrick Stolee का ज़िक्र है, जिन्होंने ज़रूरी बदलावों को dig into करके release किया
    अगर Git internals, local और CI में Git clone size घटाने जैसी चीज़ों में रुचि है, तो Derrick ने GitHub blog पर कई बेहतरीन posts लिखी हैं: https://github.blog/author/dstolee/
    उनकी personal site भी देखने लायक है: https://stolee.dev/

  • अगर गलती से कोई बड़ा binary commit हो गया हो, तो सुधार का तरीका यह माना जा सकता है कि filter-branch चलाकर उस problematic binary को हटाया जाए, सभी को binary के लिए git-lfs इस्तेमाल करना सिखाया और configure किया जाए, फिर force push करके सबके working environment को सामान्य स्थिति में वापस लाया जाए
    यह ideal नहीं है, लेकिन ऐसे बड़े file को Git में बनाए रखने से बेहतर है जिसका इस्तेमाल भी नहीं हो रहा

    • जिन्हें filter-branch डरावना लगता है, उनके लिए BFG भी है: https://rtyley.github.io/bfg-repo-cleaner/
      जैसा किसी और ने बताया, यह समस्या छोटे लेकिन अक्सर बदलने वाले files की है, इसलिए space बचाने के लिए history से पुराने versions हटाकर आगे से LFS इस्तेमाल करना चाहिए
    • असल समस्या ऐसा binary file नहीं है जो एक बार आकर फिर बदलता नहीं, बल्कि अक्सर बदलने वाला छोटा binary file है
    • आजकल recommend किया जाने वाला तरीका filter-repo है: https://github.com/newren/git-filter-repo
    • Linus पर दोष डालना आसान है
  • Git को hack करना मजेदार हो सकता है, लेकिन सोचता हूं कि क्या शुरुआत में ही monorepo में 2,500 packages न डालने का कोई तरीका नहीं था

    • code lines आम तौर पर exponential तरीके से बढ़ने की प्रवृत्ति रखती हैं
      codebase जितना बड़ा होता है, उतना ही यह उम्मीद करना मुश्किल हो जाता है कि लोग पुराना wheel फिर से invent नहीं करेंगे, क्योंकि या तो उन्हें उस code के मौजूद होने का पता नहीं होता, या वे अपने use case को handle करने के लिए उसे बदलते हुए मौजूदा usages तोड़ देने से डरते हैं
      अनुभव से लगता है कि 50 modules से 100 modules तक जाने की तुलना में 100 से 200 तक जाने में कम समय लगता है
    • तो फिर संबंधित सारा overhead उठाते हुए 2,500 Git repositories अलग-अलग रख सकते हैं
    • मेरा भी पहला ख्याल बिल्कुल यही था
      यह पूरी तरह खुद पैदा की गई समस्या लगती है
  • हाल में GitButler द्वारा पोस्ट किए गए GitMerge presentations देखकर monorepo और scalability से जुड़े developments follow कर रहा हूं, और Microsoft, GitHub, GitLab से कई अच्छी चीजें आ रही हैं
    मैं और समझना चाहता हूं कि आखिरी 16 characters check और full path check की समस्याएं delta compression, pack index और multi-pack index के साथ कैसे जुड़ती हैं

    • अगर यह open source के लिए इतना अच्छा काम है, तो जिज्ञासा है कि क्या वे custom Git commands को official Git में वापस शामिल कराने के लिए merge request खोलने वाले हैं
  • यह देखकर अच्छा लगा कि Microsoft खुद Azure DevOps इस्तेमाल कर रहा है
    लगता था कि ज्यादा से ज्यादा Azure services सिर्फ GitHub के लिए native connector दे रही हैं, इसलिए Azure DevOps को शायद छोड़ दिया जा रहा है

  • Git के internals को इतनी अच्छी तरह जानने वाला कोई व्यक्ति हाथ की पहुंच में हो और मदद कर सके, इस scale की company में ऐसा project करते समय यह वाकई बड़ा perk होगा

    • GitHub जैसी organization से करीबी संबंध होना निश्चित रूप से मदद करता है, लेकिन जिस भी organization में इतने सारे developers हों, वह ऐसी काफी niche समस्या हल करने के लिए highly specialized consultant लाने की लागत justify कर सकती है