معماری جدید ذخیره سازی Git GitHub
آدرس مقاله: https://github.blog/engineering/architecture-optimization/inside-githubs-new-git-storage-architecture/ آدرس نظرات: https://news.ycombinator.com/item?id=50397099 امتیاز: 212 # نظر: 59
هفته گذشته در GitHub Universe، معماری جدید ذخیرهسازی Git خود را معرفی کردیم که میتواند هزاران نوشتن در ثانیه را با خواندن آن مقیاس در صورت تقاضا انجام دهد. از طریق آزمایش داخلی، ما توانایی پشتیبانی از 5000 عامل همزمان را که روی یک مخزن کار میکنند، ارسال 7200 فشار در ثانیه به مخزن نشان دادهایم، همه اینها در حالی که ترافیک خواندنی را با تأخیر مداوم پایین ارائه میکند. این معماری جدید پایهای برای GitHub برای پشتیبانی از توسعه عاملی برای صدها میلیون توسعهدهنده است، و ما آن را در زیر GitHub ایجاد کردهایم که همچنان در حال اجراست.
در طول سال گذشته، عوامل ظاهر یک مخزن شلوغ در GitHub را تغییر دادند. در اولین پست خود در این مجموعه، ما دو اصل را در ساختار نسل بعدی ذخیره سازی Git بیان کردیم: به حداقل رساندن هماهنگی، و جدا کردن فضای ذخیره سازی از محاسبات. در آخرین پست خود، این پل را پوشش دادیم: انتقال 99.99999٪ از مخازن به Azure، محدود کردن حد نصاب نوشتن به منابع فردی، و ایستادن کپی های مجازی با پشتیبانی Azure Blob Storage.
ما همچنین با یک مشاهده کار را ترک کردیم: مقدار کمی از نوشتن باید سفارش داده شود. اشیاء Git تغییرناپذیر هستند و با محتوای آنها نامگذاری می شوند، به این معنی که دو نویسنده می توانند آنها را همزمان بدون توافق بر سر چیزی ذخیره کنند. بخشی از یک فشار که نیاز به توافق دارد، به روز رسانی مرجع است، و یک به روز رسانی مرجع در مقایسه بسیار کوچک است. کپی های مجازی اولین گام به سوی ساختن پیرامون آن و ابزاری بود که به وسیله آن مخازن را به معماری جدید وارد کردیم.
بقیه معماری بر اساس آن مشاهدات است. فقط بهروزرسانیهای مرجع نیاز به توافق دارند. این اجازه می دهد تا بخش هایی از زیرساخت Git که قبلا به طور محکم جفت شده بودند (ذخیره داده ها، ارائه درخواست ها و نگهداری مخازن) به طور مستقل مقیاس شوند. برای توسعهدهندگان، Git به Git میماند: آنها فشار میدهند، واکشی میکنند، و شاخهبندی میکنند به همان شیوه، با همان حفاظتها. در زیر، نیازمندیهای طراحی را بررسی میکنیم، یک درخواست را از طریق سیستم دنبال میکنیم و به عمق دو جزء آن میپردازیم.
ما در مورد مقیاس پلت فرم GitHub در دو محور فکر می کنیم:
امروزه، شلوغ ترین مخازن در GitHub.com حداکثر ده ها نوشته را در یک ثانیه دریافت می کنند، معمولا در فواصل زمانی کوتاه. از آنجایی که بیشتر این کار به سمت عواملی که به صورت موازی و شبانه روزی کار می کنند تغییر می کند، انتظار داریم که این انفجارها در سال آینده به یک نرخ پایدار تبدیل شوند. هر فشار منجر به خواندن بیشتر می شود: CI اجرا می شود، سیستم های استقرار commit جدید را می کشند، و سایر عوامل آن را برای زمینه واکشی می کنند. این fan-out خواندن منجر به یک اثر تقویتی بزرگ در ترافیک کلی می شود که با نوشتن مقیاس می شود.
ما در حال حاضر شاهد اجرای این روند در مخازن در بالای منحنی هستیم. نمودار زیر درخواستهای روزانه به یک مخزن در GitHub را از مارس تا مه نشان میدهد، زمانی که ترافیک آن حدود 4 برابر شد. رشد آن از آن زمان به بعد در یک مسیر تصاعدی ادامه یافته است.
ما باید برای جهانی برنامه ریزی کنیم که در آن توسعه عامل محور با این سرعت شتاب می گیرد. به جای برنامه ریزی برای افزایش بعدی، هدفی را حداقل دو مرتبه از مقیاس بزرگتر تعیین می کنیم. برای یک مخزن واحد، این بدان معناست که معماری جدید باید:
علاوه بر این، سیستم باید قابلیت اطمینان و مقیاس کلی را در سطح جهانی بهبود بخشد.
در ماه مه سال 2026، در آستانه مهاجرت اولیه Azure، کار بر روی طرحی را آغاز کردیم که بتواند این خواسته ها را برآورده کند. ما چندین روش را امتحان کردیم. در همه آنها، داده های بسته در Azure Blob Storage زندگی می کردند، و در برخی از آنها ما با حفظ حالت مرجع در آنجا نیز آزمایش کردیم. ما برخی از معاوضههایی را که در زیر یافتیم پوشش میدهیم، اما روی طرحی استوار شدیم که با حفظ صحت، دوام، در دسترس بودن و تأخیر، ضمانت پلتفرمی مانند GitHub را به حداکثر میرسانیم.
ما آن را «روبیکون» نامیدیم، اشارهای به نقطهای بیبازده که صنعت با توسعهی عاملی عبور کرد.
بیایید در روبیکون از بالا به پایین قدم بزنیم. بسیاری از چیزها ثابت میمانند: درخواستهای مشتریان Git، API، و رابط کاربری وب به Spokes میرسند، که همچنان مسیریابی و تعادل بار را مدیریت میکند و API داخلی را که بقیه GitHub برای خواندن و نوشتن دادههای مخزن استفاده میکنند، ارائه میکند. Spokes همچنین نقش خود را به عنوان یک سرویس قرار دادن و متعادل کننده، حفظ پارتیشن بندی، جداسازی و انصاف مستاجر مورد نیاز برای مقیاس افقی حفظ می کند. در مقابل، صفحه داده زیر Spokes جدید است.
Spokes هر درخواست را به یکی از گروه کوچکی از کپی های مجازی اختصاص داده شده به آن مخزن هدایت می کند. نسخههای مجازی مجموعهای از محاسبات یکبار مصرف هستند که خواندن، نوشتن یا هر دو را ارائه میکنند. دیسک محلی کپی مجازی یک حافظه پنهان است که می تواند در هر زمان بازسازی شود.
Spokes در صورت امکان، ترافیک مخزن را به همان کپی های مجازی هدایت می کند، که حافظه پنهان آنها را گرم و تأخیر پایین نگه می دارد، اما هیچ چیز در مورد صحت بستگی به درخواستی ندارد که روی هر ماکت مجازی خاصی فرود آید. هنگامی که یک نسخه مجازی از بین می رود، دیگر کپی های مجازی مخزن به خدمت خود ادامه می دهند و با رسیدن ترافیک، حافظه پنهان آن جایگزین می شود.
هنگامی که یک مخزن اشغال می شود، ما نسخه های مجازی بیشتری اضافه می کنیم. به طور جداگانه، ناوگانی از گرههای فشردهسازی در پسزمینه، مستقیما در برابر Blob Storage و Ref-CAS کار میکنند تا بستههای هر مخزن را ادغام کرده و اشیاء غیرقابل دسترس را حذف کنند. این کار هرگز بر روی یک نسخه مجازی اجرا نمی شود، به این معنی که CPU و حافظه آنها می تواند تقریبا به طور کامل برای ارائه درخواست ها اختصاص داده شود.
بیایید معماری را از منظر نوشتن و خواندن دنبال کنیم:
رویکردهای متمایزی که در معماری جدید اتخاذ کردهایم وجود دارد، از پشته نرمافزار روی نسخههای مجازی گرفته تا نحوه تنظیم مسیریابی ترافیک و زمانبندی حجم کاری برای موارد مختلف (مانند بسیاری از مخازن کوچک در مقابل یک مخزن بزرگ). بیایید عمیقا در دو مؤلفه معماری غوطه ور شویم و داستان های آنها را با محوریت دو اصل طراحی بازگو کنیم.
سرویسهای ذخیرهسازی اشیا مانند Azure Blob Storage در کاری که انجام میدهند فوقالعاده خوب هستند و برای بسیاری از برنامههای کاربردی سنگین مانند ذخیرهسازی کد منبع مناسب هستند. آنها بادوام و تکراری هستند، تا جایی که ما نیاز داریم نگه می دارند، هزینه نسبتا پایینی برای هر بایت دارند و تا زمانی که ترافیک در بسیاری از کلیدها پخش می شود، توان عملیاتی فوق العاده بالایی را ارائه می دهند. در نتیجه، تقریبا هر چیزی که در معماری جدید ذخیرهسازی Git بادوام است در Azure Blob Storage زندگی میکند، از جمله:
بیشتر این دادهها یک بار روی یک کلید مشخص نوشته میشوند و هرگز بعد از آن تغییر نمیکنند، که همین موضوع آن را برای Blob Storage بسیار مناسب میکند.
مراجع برعکس است. ارجاعات یک مخزن شلوغ، بخش نسبتا کوچکی از حالت است که بسیاری از آنها همزمان برای بهروزرسانی آن تلاش میکنند. ارائه این الگوی دسترسی از Blob Storage سخت تر است و اولین نمونه های اولیه ما دلیل آن را به ما نشان دادند. ذخیره سازی اشیاء برای این نوع حجم کاری یک انتخاب بصری است، و انتخابی است که تا سطح معینی از مقیاس به خوبی کار می کند: نوشتن شرطی یک مقایسه و تعویض رایگان را ارائه می دهد، و داده ها از قبل بادوام و تکراری هستند. با این حال، با مجموعهای از انتخابهای دشوار مواجه شدیم که در زیر با دو رویکردی که امتحان کردیم نشان داده شده است.
اولین رویکرد ما یک گزارش پیشنویس برای همه مراجع مخزن بود که توسط یک حباب فهرستبندی میشد. هر به روز رسانی یک نوشتن مشروط در برابر آن لکه است. درست است، و خواندن ارزان است: یک ماکت مجازی می پرسد که آیا لکه تغییر کرده است یا نه، و اگر تغییر نکرده است، تمام شده است. نوشتن جایی است که کوتاه می آید. فقط یک نوشتن مشروط به یک حباب میتواند در یک زمان در حال پرواز باشد، بنابراین حتی زمانی که هر فشاری را که در حین نوشتن وارد میشود به مورد بعدی اضافه میکنیم، سقف نوشتن مخزن یک نوشتن در هر سفر رفت و برگشت به یک حباب است.
در عمل، این کار باعث شد تا ما توان نوشتن را در اواسط صدها در ثانیه انجام دهیم.
رویکرد دوم ما مراجع را در چندین بخش تقسیم میکند و هر مرجع به یکی از آنها هش میشود. به مراجع در بخشهای مختلف مینویسد دیگر منتظر یکدیگر نیستند، که سقف را بالا میبرد. اما خواندن اکنون باید هر بخش را بررسی کند تا بداند آیا چیزی تغییر کرده است یا خیر، و سرعت آن فقط به سرعت کندترین بخش است. ما تأخیر نوشتن را با تأخیر خواندن مبادله میکردیم، و تعداد خواندنها بر اساس سفارشهای بزرگی از تعداد نوشتهها بیشتر بود. ما میتوانیم این موضوع را با نمایش نوشتههای متعدد و خواندن در کنار هم تجسم کنیم:
هیچکدام از گزینهها همه نیازها را برآورده نکردند، بنابراین چیز دیگری ساختیم: یک سرویس گزارش تکراری اختصاص داده شده به مراجع یا سرویس Ref-CAS. این گروه اجماع (کوچکی) است که در پایان پست گذشته گفتیم هنوز جایی در پشته است. در اینجا نحوه عملکرد آن آمده است.
هر به روز رسانی مرجع در Ref-CAS یک مقایسه و تعویض است که توسط یک حد نصاب انجام می شود. یک تراکنش فقط مراجعی را که لمس میکند قفل میکند، بررسی میکند که آیا هر کدام همچنان ارزش مورد انتظار نویسنده را دارند و زمانی که اکثر کپیهای گزارش آن را ثبت کردند، قابل مشاهده میشود. Agent ها عمدتا به شعبه های خود فشار می آورند، بنابراین اکثر به روز رسانی ها با یکدیگر مخالف نیستند و آنها به صورت موازی تکرار می شوند. هنگامی که دو فشار یک مرجع را به روز می کنند، تنها یکی به گزارش اضافه می شود.
هر commit به Ref-CAS یک شماره توالی log (LSN) دریافت می کند و نسخه مخزن LSN آخرین commit آن است. خواندن با بررسی نسخه در برابر آن نسخه شروع می شود. یک کپی مجازی نسخه ای از حالت مرجعی را که قبلا در اختیار دارد ارسال می کند. اگر آن نسخه هنوز جاری باشد، پاسخ هیچ مرجعی ندارد و یک مخزن بدون تغییر هزینه یک مقایسه دارد. اگر نسخه مجازی پشت سر باشد، منابع و نسخه فعلی را به همراه نشانگرهایی برای بستهبندی دادهها و هر چیز دیگری که نیاز به دانستن دارد، برمیگرداند، مانند زمانی که آخرین چرخه جمعآوری زباله رخ داده است.
برخلاف گروه اجماع در معماری قدیمی ما، Ref-CAS هرگز داده های شی را نگه نمی دارد یا اعتبار سنجی نمی کند. فقط اشاره گرها را به آن ذخیره می کند. یک کپی مجازی قبل از اینکه تراکنش مرجع را انجام دهد، بسته فشار را در Blob Storage آپلود می کند. یک مرجع فقط به اشیایی اشاره می کند که از قبل بادوام هستند.
این رویکردها به ما این امکان را میدهند که وضعیت مرجع را به تنهایی در دهها هزار عملیات در ثانیه انجام دهیم و پایهای برای حاشیهنویسیهای اضافی در گزارش تکرار ارائه کنیم که به خواندن کمک میکند تا تصمیم بگیرد چه چیزی برای یک درخواست معین دانلود شود.
اما پشتیبانی از هزاران نوشتن در ثانیه در یک مخزن یک مشکل درجه دوم ایجاد می کند: حفظ عملکرد خواندن و نوشتن. برای آن، ما به یک رویکرد متفاوت برای تراکم نیاز داشتیم.
هر فشار یک بسته به Blob Storage اضافه می کند. Git میتواند یک مخزن از بستههای زیادی ارائه کند، اما هر بسته اضافی جستجوها را کمی گرانتر میکند، بنابراین تعداد بستهها نمیتواند بدون محدودیت افزایش یابد. در معماری قدیمی ما، فشردهسازی روی همان کپیهایی اجرا میشود که به ترافیک سرویس میدهند، و باید روی هر ماکتی اجرا شود. در معماری جدید، به طور کامل در پسزمینه روی گرههای جداگانه اجرا میشود، مستقیما در برابر ذخیرهسازی بادوام، با استفاده از سرویس Ref-CAS برای اطلاع دادن به نسخههای مجازی از وضعیت در حال تکامل.
گرههای فشردهسازی هر کدام ادعای مالکیت بر زیرمجموعهای از دادههای مخزن را از طریق اجاره میدهند، که به ما امکان میدهد فشردهسازی را به صورت افقی مقیاس کنیم. در اینجا نحوه عملکرد یک گره فشرده سازی از طریق یک تکه از بسته های مخزن آمده است:
گره ابتدا در Blob Storage یک قطعه را اجاره می کند، بنابراین تنها یک گره در یک زمان یک خرده را فشرده می کند. در همین حال، کپیهای مجازی به آپلود بستهها و فهرست کردن آنها در مانیفست بسته ادامه میدهند. گره مانیفست را برای یافتن بستههای جدید میخواند و آنها را در یک بخش باز جمعآوری میکند، که در واقع محل نگهداری دادههایی است که قرار است فشرده شوند. هنگامی که بخش به یک حد تعریف شده بر اساس تعداد بسته، اندازه یا سن می رسد، بخش را مهر و موم می کند تا بسته های جدیدی را قبول نکند، سپس بسته های خود را به بسته های کمتر و بزرگتر ترکیب می کند.
در حالی که فشرده سازی اتفاق می افتد، بخش بعدی برای قطعه می تواند شروع به گرفتن بسته های ورودی آن کند. قبل از اینکه هر چیزی منتشر شود، گره بررسی می کند که بسته های جدید دقیقا دارای همان اشیاء ورودی هستند. سپس بسته های جدید را آپلود کرده و به مانیفست اضافه می کند. تراکنش دوم ورودی ها را از مانیفست حذف می کند و پاکسازی بعدا آنها را حذف می کند.
برای کاهش مقیاس، این فرآیند به طور مستقل برای بسیاری از خرده ها به طور همزمان اجرا می شود. هش هر بسته به یک قطعه نگاشت می شود، بنابراین افزودن خرده ها و گره ها توان تراکم را اضافه می کند. بسته فشردهای که هنوز کمتر از حد مجاز اندازه است، با هش خودش به یک قطعه برمیگردد و دوباره ادغام میشود، بنابراین بستهها در طبقات رشد میکنند تا زمانی که به حد مجاز برسند. نتیجه خالص، تعداد بستههای بهطور مؤثری صاف در حین صعود فشار است:
فشرده سازی بسته ها را با هم ادغام می کند، اما هر شی را نگه می دارد. جمعآوری زباله مرحلهای است که دیگر اشیاء را حذف میکند، مانند مواردی که توسط شاخههای حذف شده و فشارهای زور باقی ماندهاند. در معماری جدید ما، جمعآوری زباله با استفاده از کارگران اختصاصی برای رسیدگی به بخش سنگین محاسباتی، بسیار شبیه به فشردهسازی عمل میکند. همچنین چند رفتار خاص برای حذف ایمن داده ها دارد که شایسته بحث خودشان است.
با استفاده از این رویکردها، ما توانستهایم به طور مداوم یک منحنی تأخیر مسطح را در حین بالا رفتن نوشتار نشان دهیم، و بهینهسازیهای برنامهریزیشده دیگری برای عملکرد بیشتر فرآیندهای فشردهسازی و جمعآوری زباله داریم.
دو اصل که ما با آن شروع کردیم اجازه میدهد هر بخش از این معماری به تنهایی مقیاس شود. ما خواندن و نوشتن را با افزودن کپی های مجازی مقیاس می کنیم. Ref-CAS به جای کل مخزن، هر مرجع را به طور مستقل سفارش می دهد. ما تعمیر و نگهداری را با افزودن گره های تراکم مقیاس می کنیم. هدف ما این است که هر مخزن، از پروژه جانبی یک توسعهدهنده تا شلوغترین مونورپو، بر روی همان معماری اجرا شود، بدون اینکه مدیریت خاصی برای افراد پرمشغله وجود داشته باشد. مخازن کوچک نمی مانند. یک پروژه جانبی میتواند ناوگانی از نمایندگان را یک شبه انتخاب کند، و برای ادامه دادن به آن نیازی به مهاجرت یا رسیدگی خاصی از سوی ما ندارد.
یک معماری همچنین به این معنی است که هر بهبودی که انجام می دهیم به یکباره به هر مخزن می رسد.
این پایه به ما امکان می دهد کارهای بیشتری انجام دهیم. ما بر روی سه حوزه بعدی تمرکز کردهایم: مقیاسبندی معماری فراتر از آن چیزی که میتوانیم پیشبینی کنیم امروز به آن نیاز داریم، کارآمدتر کردن عاملها در کاری که در GitHub انجام میدهند، و تکامل خود Git برای توسعه عامل.
ما برنامه هایی برای پیشبرد این معماری بیشتر داریم. هر یک از این پیشرفتها به یک مخزن اجازه میدهد تا فعالیتهای بیشتری را انجام دهد و در عین حال کار پشت هر درخواست را کاهش دهد:
مقیاس پذیری زیرساخت یک طرف عوامل پشتیبانی است. طرف دیگر هر عاملی را ارزانتر میکند: بایتهای کمتر برای واکشی، رفت و برگشت کمتر و انتظار کمتر برای تغییرات. هر یک از این موارد دنبال سریع بر اساس کار ذخیره سازی در این پست است:
ما در خود گیت هم کار داریم. GitHub یک مشارکت طولانی مدت در پروژه Git بوده است و ما اکنون متوقف نمیشویم. چند نمونه از کاری که ما مشتاقیم ادامه دهیم:
عوامل نحوه ساخت نرم افزار را تغییر داده اند. GitHub و Git با آن در حال تغییر هستند. ما هیجان زده هستیم که این معماری جدید را در خط شروع قرار دهیم. اکنون در یک پیشنمایش خصوصی محدود، با عرضه گستردهتر در اوایل سال آینده، در دسترس است. ما نمیتوانیم منتظر بمانیم تا ببینیم تیمها چه چیزی میسازند، وقتی تعداد نمایندگانی که بر روی یک مخزن کار میکنند توسط ایدههایشان محدود شود. ما به توسعه Git و GitHub در کنار مشتریان خود و جامعه Git ادامه خواهیم داد.
برایان یک مهندس نرمافزار اصلی است که روی ذخیرهسازی و سرویسهای اصلی کار میکند که تمام تعاملات مخزن در GitHub را تامین میکند.
ما یک کپی از 99.99999٪ از مخازن GitHub را به Azure منتقل کردیم. در اینجا آمده است که چگونه حد نصاب های هر مرجع بقیه را به ارمغان می آورد، و چگونه ذخیره سازی اشیاء اجازه می دهد تا مقیاس خواندن به طور مستقل انجام شود.
ویژگی های بومی GitHub را برای بررسی دسترسی همکاران خارجی و بایگانی مخازن قدیمی فعال کنید.
API تاریخچه ستاره مخزن جدید ما اکنون روندها را بدون بازگرداندن هویت فردی یا نیاز به کار پایگاه داده نامحدود ارائه می دهد. اینها معاوضه های مهندسی است که ما انجام دادیم.
متن اصلی (انگلیسی)
GitHub's new Git storage architecture
Article URL: https://github.blog/engineering/architecture-optimization/inside-githubs-new-git-storage-architecture/ Comments URL: https://news.ycombinator.com/item?id=50397099 Points: 212 # Comments: 59