متى ينهار Git؟ دروس من المستودعات الأحادية عند 30 ألف دمج يوميًا
كيف يفشل توسّع Git مع المستودعات الأحادية بطرق متوقعة عند معدلات دمج هائلة. دروس من العملاء المتصلين دائمًا والـcommits من جهة الخادم.

إذا كنت تدير مستودع git أحادي، فهناك رقم ما في مستقبلك ستتطاير عنده الأمور. بالنسبة لـ Anthropic كان هذا الرقم نحو 30 ألف دمج يوميًا، وتاريخ فرع رئيسي بحجم 100 جيجابايت، وما يقارب 25 مليون عملية checkout في CI يوميًا، وهو ما يعني، إذا جُلبت بطريقة ساذجة، أكثر من 600 بيتابايت من حركة git. كل يوم. وكان ردهم أن كتبوا نظام التحكم بالإصدارات الخاص بهم خلال خمسة أشهر. وجوابك يجب ألا يكون هذا. لكن الأخطاء التي وقعوا فيها هي نفسها التي أراها لدى فرق عند عُشر هذا الحجم، لذا دعنا نستعرضها.
الخطأ الأول: التعامل مع سير عمل مركزي كأنه موزَّع
الرهان الأساسي في تصميم Git هو اللامركزية. كل نسخة محلية (clone) مستودع كامل: التاريخ كاملًا، ومخزن الكائنات كاملًا، وتعمل بالكامل دون اتصال. وهذا الرهان هو ما منح git انتشاره العالمي، وما زال صحيحًا لمعظم المستودعات. لكن الحقيقة التي لا يعترف بها أحد تقريبًا: قلّما يستخدم أحد git بهذه الطريقة فعلًا. مؤسستك لديها remote واحد موثوق على GitHub أو GitLab. مطوروك يدفعون ويسحبون باستمرار. ومشغلات CI متصلة بالإنترنت بحكم طبيعتها. أنت تشغّل نظامًا مركزيًا يرتدي زي النظام الموزَّع، وتدفع تكاليف الموزَّع: كل مهمة CI تسحب تاريخًا لن تقرأه أبدًا، وكل sandbox لوكيل يبني packfiles لا يحتاجها.
هذا أول درس حقيقي من إعادة تصميم Anthropic. حين تراجعوا، لاحظوا أن عملاءهم هم «وكلاء ومهام متصلة بالإنترنت بحكم التعريف»، فصمّموا حول خادم يمكن الوصول إليه دائمًا. أصبح العميل بسيطًا عمدًا: ذاكرة تخزين مؤقت محلية محدودة، ونسخة عمل على القرص، ومراقب للملفات. يتولى الخادم عمليات blame وحساب الفروقات والدمج. وهذه هي النتيجة نفسها التي توصلت إليها Google مع Piper وMeta مع Sapling، وليست موقفًا ضد git، بل إدراك بأن البنية يجب أن تطابق عبء العمل الفعلي، لا العمل الذي صُمم له الأداة قبل عشرين عامًا. رأيت هذا النمط في أدوات الأمان أيضًا: تنشر الفرق بنية وكيل تعمل دون اتصال، ثم تشغّلها في بيئة لا يحدث فيها انقطاع أبدًا، ثم يتساءلون لماذا أصبحت طبقة المزامنة كابوسًا.
الخلاصة لفريقك ليست «ابنِ نظام تحكم بالإصدارات». بل: راجع افتراضاتك. اكتب الخصائص التي تعتمد عليها فعلًا في git. هل تعمل دون اتصال؟ هل تحتاج forks حقيقية؟ هل تحتاج توقيعًا تشفيريًا للـcommits؟ إذا كانت الإجابة الصادقة هي «remote واحد، متصل دائمًا، وCI يتولى كل شيء»، فتوقف عن التحسين لسير عمل موزَّع لا تملكه، وابدأ بتحسين تكاليف الـcheckout والاستعلامات بدلًا من ذلك.
الخطأ الثاني: إخفاء توسّع المستودع بالمرايا والبذور
تمر كل فرق المستودعات الكبيرة بالمراحل المؤلمة نفسها. أولًا الـshallow clones، ثم sparse checkouts وpromisor remotes، ثم مرايا git داخلية، ثم «بذور» لقطات في تخزين الكائنات كي تتمكن مهام CI من البدء من نسخة قريبة من رأس الفرع الرئيسي. استخدمت Anthropic كل هذه الحلول، وبلغت ذروتها عدة مئات من المرايا الداخلية. ونمط الفشل واحد دائمًا: الحلول البديلة لا تفشل بصوت عالٍ، بل تتعفن ببطء. تبتعد البذور أكثر فأكثر عن الرأس بين عمليات التحديث. وتؤدي النسخ الجزئية إلى كسر git blame وgit log --follow بطرق مربكة. ويعود bisect أحدهم بنتائج خاطئة بصمت لأن التاريخ مقتطع. ولا يلاحظ أحد ذلك حتى مراجعة الحادث.
هذا الجزء الأخير هو ما يؤرقني. أخطاء صحة التاريخ الجزئي هي بالضبط نوع الخطأ الخفي الذي يعيش في الفجوة بين ما تظن أن النظام يضمنه وما يفعله فعلًا. الـshallow clone لا يعطيك خطأ حين تطلب blame أبعد من عمقه، بل يعطيك إجابة جزئية فحسب. وإذا كان خط أنابيب الاسترجاع الآلي يثق بتلك الإجابة، فقد بنيت آلة تعيد عكس التغيير الخطأ بثقة تامة.
- الـshallow clones تُعطل الأدوات المعتمدة على التاريخ (blame وbisect وmerge-base) بطرق تفشل بصمت لا بصوت عالٍ.
- بذور اللقطات تبتعد عن رأس الفرع الرئيسي؛ كلما ارتفع معدل الدمج زادت سرعة الابتعاد، فيتدهور الحل البديل تمامًا عند ازدياد الحمل.
- أساطيل المرايا تضاعف سطح التشغيل: مئات النسخ ذات الحالة قد تتأخر أو تتلف أو تنفد مساحتها بشكل مستقل.
- كل حل بديل يمس دلالات التاريخ يحتاج اختبار انحدار، وإلا سيعضك أثناء الحادث.
الحل عند حجم Anthropic كان هيكليًا: لا توجد نسخة كاملة من المستودع في أي مكان، حتى على الخادم، وكل شيء مُعنون بالمحتوى وقابل للتخزين المؤقت أفقيًا. أما عند حجمك فالحل أكثر تواضعًا: قِس عدد الأجهزة التي تحتفظ بنسخ كاملة أو شبه كاملة من مستودعك، وكن صادقًا بشأن الانحراف ونمط الفشل في كل واحدة منها.
الخطأ الثالث: جعل كل مهمة CI تحتفظ بنسختها الخاصة من المستودع
سؤال يستحق أن تطرحه على أسطول CI لديك: كم عدد هذه المهام التي تحتاج نسخة عمل أصلًا؟ كانت إجابة Anthropic بعد بناء نظامها: «أقل بكثير مما افترضنا». المهمة التي تحتاج الفرق بين commit وقاعدة دمجه، أو blame لملف، أو قائمة الـcommits التي تمس مسارًا، يمكنها أن تسأل الخادم مباشرة عبر API. بلا clone، ولا fetch، ولا قرص. وأنظمة bisect والاسترجاع الآلي لديهم «لن تكون عملية على نطاق واسع» بدون هذا الوصول البرمجي الرخيص، وأصدق ذلك، لأنني رأيت فرقًا تحاول تشغيل الأنظمة نفسها فوق نسخ محلية فتغرق في وقت الجلب.
هذا المبدأ ينطبق بعيدًا عن التحكم بالمصدر. اعتبر الـcheckout استثناءً مكلفًا لا الخيار الافتراضي. وعندما تحتاج المهمة ملفات فعلًا، تخفض نسخ العمل المتفرقة والافتراضية، التي تُنشئ فقط المجلدات المطلوبة أو حتى الملفات المقروءة فعليًا، التكلفة بمراتب من الحجم. وقد أسقط بروتوكول الجلب لدى Anthropic تفاوض git تمامًا: يطلب العميل أشجارًا وكائنات blob محددة وغير قابلة للتغيير، ويستطيع أي cache الرد، ويعمل النظام كشبكة CDN متدرجة بدلًا من خادم ذكي يحسب packfiles لكل عميل. وإذا سبق أن قست أداء خادم git وهو ينهار تحت حمل CI، فستدرك لماذا يهم ذلك: توليد packfiles مكثف للمعالج تحديدًا لأن البروتوكول يطلب من الخادم حساب الفرق بين ما يملكه كل عميل مسبقًا.
إذا كان عملاؤك متصلين دائمًا وهناك نسخة واحدة موثوقة، فكل نسخة محلية تحتفظ بها هي ذاكرة تخزين مؤقت تدفع ثمنها كاملًا، وتحصل مع ذلك على دلالات ذاكرة تخزين مؤقت على أي حال.
الخطأ الرابع: السماح لتكلفة الـcommit بالتصاعد مع حجم المستودع
أكثر قرار هندسي مثير للاهتمام في تصميمهم هو الـchangeset. الـcommit في git يشير إلى شجرة تمثل حالة المستودع كاملة، أما الـchangeset لديهم فيسجل فقط قائمة الملفات التي تغيّرت بالنسبة إلى أول أب، مع مؤشرات مُعنونة بالمحتوى إلى تخزين الـblobs، ويُلحق بسجل write-ahead. لا packfiles تُستقبل، ولا أقفال، ولا تنسيق بين الخوادم. أما الـrefs (الفروع والوسوم) فقد قُلّل من شأنها عمدًا، لأن «الحالة العامة التي يجب أن تتفق عليها كل الخوادم مشكلة توسّع قادمة». وتحريك ref هو عملية compare-and-swap على صف واحد في Spanner، منفصلة عن إنشاء الـcommit.

الفكرة المرافقة هي «البيانات المشتقة»: فهارس blame، وتاريخ المسارات، وقوائم تخطي الرسم البياني للـcommits، كلها تُحسب بشكل متكرر من أب كل changeset، وكلها عبر آلية عامة واحدة. احسبها مسبقًا أو عند الطلب لكل نوع. أضف أنواع فهارس جديدة دون لمس العميل. وهذا نمط جيد فعلًا لأي نظام قائم على الأحداث، فقد أوصيت بالشكل نفسه لخطوط أنابيب سجلات التدقيق، حيث يكون السجل الإلحاقي هو الحقيقة، وكل بنية استعلام إسقاطًا قابلًا لإعادة البناء. ولحظة أن يصون أحدهم إسقاطًا يدويًا خارج تلك الآلية، ستحصل على انحراف.
تحذير من منظور الأمان: الذين أبدوا قلقهم من المركزية محقون ويستحقون الأخذ بجدية. نظام خادم واحد تعيش فيه الـcommits والدمج واستعلامات التاريخ كلها هدف عالي القيمة، ووكلاء الذكاء الاصطناعي الذين لديهم صلاحية كتابة على «المستودع الواحد الذي يحكم الكل» يركزون المخاطر بشكل سيئ. بنت Anthropic إلى جانب ذلك «نظام IAM للتحكم بالمصدر» غني، والتفاصيل هنا أهم من بنى البيانات. إذا كنت ستركزّ النظام، فسيتغير حساب نطاق الضرر لديك: خطط للعزل، وبيانات اعتماد لكل وكيل، والتدقيق من اليوم الأول، لا في الربع الثالث. والفرق التي تشغّل وكلاء ذوي حالة على بنية تحتية مشتركة تعرف هذا الألم سلفًا.
الخطأ الخامس: افتراض أن 30 ألف دمج يوميًا تعني 30 ألف قرار سليم يوميًا
الهندسة مبهرة. السؤال المفتوح هو ما الذي يمر عبرها. قال أحد الممارسين بصراحة: كلما توقف عن مراجعة الكود المولّد بالذكاء الاصطناعي، «أجد لاحقًا كودًا رديئًا ومتعثرًا. ليس كل مراجعة تكشف كودًا رديئًا، لكن بما يكفي لأن أتوقف عن مراجعة كل شيء». اضرب معدل الإصابة هذا في 30 ألف دمج يوميًا، وستفهم لماذا اضطرت Anthropic أيضًا إلى بناء «طرق مبتكرة لإدارة واعتماد كميات كبيرة من الكود المُراجَع»، أي سطح المراجعة، وطابور الاعتماد، وخطوط الاسترجاع الآلي. لم يكن نظام التحكم بالإصدارات هو المنتج، بل كان خط الاعتماد. وأصبح المصدر عنق الزجاجة فقط بعد أن أصبحت سعة المراجعة عنق الزجاجة قبله.
هناك أيضًا تفصيل صريح مدفون في قصصهم: وصف المهندسون في المشروع الكثير من دورهم بأنه قول «لا» لوكيل البرمجة. كان خط استيعاب النموذج الأولي الذي كتبوه يدويًا جيدًا، إلى أن أعاد نموذج أحدث كتابته بذاكرة أخف بعشرين ضعفًا وأسرع بأربعة أضعاف، لكن الوصول إلى ذلك احتاج بشرًا يملكون الذوق ليقرروا ما يُقبل. وهذا يتطابق مع كل ما رأيته: الذكاء الاصطناعي ينقل عمل المهندس من الكتابة إلى المراجعة، وأصبحت سعة المراجعة هي المورد النادر. كتبنا سابقًا عن كيف تُبطئ كل طبقة مراجعة الفريق، والصعوبة هنا أن إزالة طبقات المراجعة مع الكود المولّد بالوكلاء ليست آمنة أيضًا. تحتاج أدوات مراجعة أفضل، لا مراجعة أقل.
ماذا ينبغي أن تفعل فعلًا
أنت لست Anthropic. ليست لديك ميزانية الرموز التي لديهم، كما يعترفون، ولا 25 مليون عملية checkout يوميًا. لكن أدوات البرمجة بالذكاء الاصطناعي تدفع معدلات الدمج لدى الجميع للأعلى، ونقاط الضغط تأتي بترتيب متوقع. إليك ترتيب الخطوات الذي أتبعه، وفق «قانون بريا» للندم الأقل:
- قِس أولًا. عدّ عمليات checkout اليومية، والبايتات المجلوبة لكل مهمة CI، وعدد الأجهزة التي تحتفظ بنسخ كاملة من المستودع. غالبًا ما تواجه مشكلة هدر قبل مشكلة توسّع.
- ألغِ عمليات checkout غير الضرورية. انقل أسئلة البيانات الوصفية (الفروقات وblame والنسب) إلى APIs، أي واجهات GitHub وGitLab أو خدمة داخلية خفيفة، قبل أن تشتري أي بنية تحتية.
- اجعل النسخ المتفرقة والجزئية آمنة. اختبر أدوات bisect وblame والاسترجاع بصراحة على الـshallow clones، أو امنع استخدامها في الأتمتة.
- اجعل CI يعتمد افتراضيًا على جلب كائنات غير قابلة للتغيير من الخادم حيثما أمكن، وتعامل مع أساطيل المرايا كدين تقني بفائدة مركبة.
- استثمر في خط الاعتماد، لا في صيغة المستودع. سعة المراجعة وطوابير الدمج والاسترجاع الآلي هي المواضع التي تنكسر فيها التطوير على نطاق الوكلاء أولًا.
- فكّر في بنى التحكم بالإصدارات البديلة فقط بعد ذلك. Sapling مفتوح المصدر ويمنحك معظم فوائد العميل والخادم دون أن تكتب نظامك الخاص.
الدرس الأعمق هنا ليس عن git أبدًا. كل أداة تُشفّر افتراضات عن عبء عملها، من بشر يعملون دون اتصال، إلى commit واحد في كل مرة، وكود يُكتب أبطأ مما يُراجَع. وهذه الافتراضات تُنتهك الآن من الوكلاء، في البنية التحتية، والمراجعة، وحدود الأمان. الفرق التي ستخرج منتصرة لن تكون تلك التي تعيد بناء كل شيء من الصفر. بل تلك التي لاحظت أي الافتراضات انكسر أولًا، وأصلحت تحديدًا تلك.


