چرا گوگل از ریپوزیتوری مشترکی با حجم ۸۶ ترابایت استفاده می‌کند


ورژن کنترل چیست؟

ابزاری است برای مدیریت تغییرات مجموعۀ اطلاعاتی مثل سورس کد، نوشتار و... . این ابزار در جایی که چندین نفر بخواهند روی منبعی مشترک کار کنند، اهمیت پیدا می‌کند.

انواع ورژن کنترل

  • Centralized Version Control System (CVCS)

ارتباط به‌صورت کلاینت/سرور است. به این صورت که ریپوزیتوری در یک یا چندین سرور مستقر شده است و کلاینت‌ها به آن متصل می‌شوند و تغییرات خود را اعمال می‌کنند. SVN و CVS مثال‌های محبوبی از CVCS هستند.

  • Distributed Version Control System (DCVS)

علاوه بر سرور مرکزی هر کلاینت یک نسخه از ریپوزیتوری را به‌صورت لوکال دارد. تغییرات در ریپوی لوکال انجام می‌شود و در نهایت با ریپوی مرکزی همگام‌سازی می‌شود. Git و Mercurial مثال‌هایی از DCVS هستند.

گوگل CVCS مختص خود را توسعه داده که Piper نام دارد.


اطلاعاتی دربارۀ مقیاس ریپوی گوگل

  • مجموع تعداد فایل‌ها:‌ یک میلیارد؛
  • تعداد فایل‌های سورس کد: نُه میلیون؛
  • تعداد خطوط کد: دو میلیارد؛
  • تعداد کامیت: سی‌وپنج میلیون؛
  • حجم محتوا : هشتادوشش ترابایت؛
  • تعداد کامیت در روز : ‌چهل هزار.

ده‌ها هزار توسعه‌دهنده روی این ریپوی مشترک کدنویسی می‌کنند.


چرا گوگل از گیت استفاده نمی‌کند؟

اولین قدم برای توسعۀ پروژه‌ای که با گیت نگهداری می‌شود، clone یا به‌عبارتی دریافت یک کپی از آن پروژه در workspace محلی است. آیا منطقی است ۸۶ ترابایت کد را clone کنیم؟

در پروژه های گیت توصیه می شود که از ریپوهای کوچک استفاده کنید. بنابراین اگر گوگل می‌خواست ‌از گیت استفاده کند، مجبور بود ریپوی بزرگ خود را به هزاران ریپوی کوچک‌تر تبدیل کند که در ادامه دلایل انجام‌ندادن چنین کاری توضیح داده شده است. البته در حال حاضر، گوگل برای کدبیس اندروید از هشتصد ریپوی مستقل گیت استفاده می‌کند.

علاوه بر این، گوگل تلاش‌هایی برای مقیاس‌پذیرترکردن برخی distributed version controlهای اوپن‌سورس مثل Mercurial کرده است. این همکاری با کمک جامعۀ اوپن‌سورس Mercurial و برخی شرکت‌هایی انجام شده است که به استفاده از مونو ریپو تمایل داشتند.


گوگل چگونه چالش بزرگ ریپو را حل کرده است؟

به‌جای اینکه کل ریپو کپی شود، فقط فایل‌هایی که به تغییر نیاز دارند، در workspace کپی می‌شوند. توسعه‌دهندگان قبل از تغییر ابتدا یک کپی از فایل مدنظر در workspace خود ایجاد می‌کنند. workspaceها برای کد ریویو با دیگر توسعه‌دهنده‌ها به‌اشتراک گذاشته می‌شوند و بعد از تأیید در ریپوی مرکزی کامیت می‌شوند. فرایند Piper در شکل نشان داده شده است.

Piper Workflow
Piper Workflow


مدل branching

در گوگل کدنویسی روی branchها کاری غیرمعمول است و خیلی به‌ندرت چنین اتفاقی می‌افتد. تنها branch مجاز release است. حتی باگ فیکس هم در برنچ اصلی انجام می‌گیرد و روی برنچ ریلیز cherry pick می‌شود.

مدل Branching
مدل Branching


آیا دسترسی به همۀ کدها وجود دارد؟

ورژن کنترل Piper امکان تعریف دسترسی در سطح فایل را دارد. بیش از ۹۹درصد فایل‌هایی را که در Piper ذخیره شده است، تمامی توسعه‌دهنده‌های تمام‌وقت مشاهده می‌کنند. بعضی کانفیگ‌های حیاتی و همچنین برخی الگوریتم‌های خاص دسترسی محدود دارند. همچنین خواندن و نوشتن در فایل‌ها لاگ می‌شود. اگر سهواً اطلاعات حساسی تغییر کند، امکان رول‌بک وجود دارد.


فلوی گوگل

روزانه هزاران توسعه‌دهنده روی این ریپو هزاران کامیت انجام می‌دهند. برای جلوگیری از بروز مشکل، گوگل ابزاری برای آزمایش خودکار پیاده‌سازی کرده است که وابستگی‌های هر تغییر را تشخیص می‌دهد و آن‌ها را rebuild می‌کند و اگر خطایی رخ دهد، تغییرات به‌صورت خودکار رول‌بک می‌شوند. ابزارهایی هم برای ارزیابی کیفیت کد پیاده‌سازی شده است. همچنین مالک‌های پروژه باید تمامی کدها را قبل از کامیت، ریویو کنند.


مزایا و معایب

هر تصمیم، نوعی trade-off است و نمی‌توان یک نسخه برای همۀ شرکت‌ها و فرهنگ‌ها ارائه کرد. در ادامه از نگاه گوگل مزیت‌ها و معایب monorepo ارائه می‌شود. در برخی موارد تجربه مختصری از چالش هایی که در «بله» با آن مواجه شده‌ایم نیز بیان شده است.


مزیت‌ها

  • Unified versioning, one source of truth

نیازی به آرتیفکت‌کردن و ورژن‌زدن کتابخانه‌ها نیست و مستقیماً در کد به آن‌ها دسترسی داریم.

  • Extensive code sharing and reuse

از تکرار و کپی کدهای مشترک جلوگیری می‌شود؛ برای مثال در «بله» برای فرار از پیچیدگی کتابخانه‌کردن کدهای مشترک برخی از توسعه‌دهندگان کدهای مشترک مورد نیاز را در پروژه‌ها کپی می‌کردند. در مدل مونو ریپو به‌علت سادگی استفاده از کدهای مشترک، احتمال بروز چنین مشکلاتی کمتر است. علاوه بر این به‌علت یکجابودن کدها، توسعۀ ابزاری برای جلوگیری از کپی‌کردن کدهای مشترک ساده‌تر خواهد بود.

  • Simplified dependency management

مسئله diamond dependency در شرایطی پیش می‌آید که کتابخانۀ A به B و C وابستگی دارد و B و C هم به D وابستگی دارند؛ اما B به نسخۀ یک از D، و C هم به نسخۀ دو از D نیاز دارد. در این شرایط معمولاً در بیلدکردن A به‌مشکل می‌خوریم؛ زیرا هم‌زمان به دو نسخۀ متفاوت از این کتابخانه نیاز است و ممکن است یکی از نسخه‌ها دیگری را نقض کند. در این شرایط ممکن است بهترین اتفاق کامپایل‌ارور باشد و بدترین اتفاق دان‌شدن هنگام عملیاتی‌کردن نسخۀ جدید. :) در مونو ریپو مسئلۀ diamond dependency کمتر اتفاق می‌افتد.

همچنین وجود کل سورس کد در یک ریپو این مزیت را دارد که توسعه‌دهندگان core libraryها خیلی راحت‌تر می‌توانند تست و بنچ‌مارک بنویسند‌. زیرا با تغییر هر کتابخانه، تمامی تست‌های کدهایی که به آن کتابخانه وابسته هستند، اجرا می‌شوند و مشکل در لحظه مشخص می‌شود؛ اما در حالتی که ریپوها جداست، باید نسخه داده شود و تمامی پروژه‌هایی که به این کتابخانه وابسته هستند، به‌روزرسانی شوند و تست‌های خودشان را ران کنند تا نقص و اشکالات آن مشخص شود که کاری زمان‌بر و پرهزینه است.

Diamond Dependency
Diamond Dependency


  • Atomic changes

چندین تغییر مرتبط باهم در یک کامیت یا یک پول‌ریکوئست انجام می‌شود؛ برای مثال، در «بله» برای تعریف api جدید، مجبور به تغییر سه پروژه مختلف بودیم و هم‌زمان سه تا پول‌ریکوئست باید می‌دادیم و همه هم باید باهم مرج می‌شد تا ناسازگاری پیش نیاید. در مدل monorepo همۀ این‌ها خیلی ساده‌تر و با یک پول‌ریکوئست به‌انجام می‌رسد؛ چون کنار هم و در یک ریپوی هستند.

  • Large-scale refactoring

از آن‌جا که وابستگی‌ها در یک پروژه هستند، عملیات ریفکتور خیلی راحت‌تر می‌شود. در ریپوهای مستقل هنگام ریفکتور پروژه‌هایی که وابستگی‌های زیادی به آن وجود دارد، نیاز هست که پروژه‌های وابسته شناسایی شده و آن ها نیز به طور مستقل تغییر داده شوند. اما در ریپوی مشترک همه تغییرات به صورت یکجا و با ریسک کمتری انجام می‌شود.

  • Collaboration across teams

دسترسی همۀ تیم‌ها به کدهای یکدیگر، منجر به فرهنگی می‌شود که به‌جای مطالعۀ مستندات، کدهای یکدیگر را مطالعه کنند و این اتفاق همکاری و تعامل بیشتر بین تیم‌ها را به‌همراه دارد.

  • Code visibility and clear tree structure

فهم ساختار کلی پروژه و ارتباط بین آن‌ها ساده‌تر است؛ زیرا مرزی بین repository وابستگی‌ها وجود ندارد.


هزینه‌ها

  • Tooling investments for both development and execution

باتوجه‌به بزرگ‌بودن مقیاس repository، ابزارهای متداول و اوپن‌سورس توانایی مدیریت و نگهداری این حجم از کد را ندارند و باید ابزارهای مختصِ چنین ابعادی را توسعه داد.

  • Codebase complexity

ازآنجاکه ایجاد وابستگی در monorepo خیلی ساده است، اتفاق متداولی است که تیم‌ها دربارۀ گراف وابستگی فکر نکنند و ناخودآگاه وابستگی‌های غیرضروری ایجاد می‌شود.

  • Effort invested in code health

ابزارهایی مختص بررسی سلامت کد باید ایجاد شود. برای مثال گوگل ابزارهای بخصوصی برای تشخیص خودکار کدهای تکراری و کدهای بلااستفاده توسعه داده است.


برگرفته از مقالۀ Why Google Stores Billions of Lines of Code in a Single Repository