توسيع نطاق Git في OpenAI
بناء نظام للتحكم في الشيفرة المصدرية يستوعب حجم عمل الوكلاء، مع الاستثمار في منظومة Git.
خلال العام الماضي، ازداد حجم مستودعنا الموحّد والمتطلبات المفروضة عليه بوتيرة مذهلة. كان من الواضح أننا بحاجة إلى تحديث جذري لنظام التحكم في الشيفرة المصدرية ليستوعب حجم العمل المتزامن الذي يتيحه Codex.
طوّرنا Pando، نظامًا خلفيًا جديدًا لمستودعات Git، دون تعطيل أي عمل هندسي أو تغيير طريقة استخدام فريقنا لـGit. يشغّل Pando الآن جميع أعمال التطوير في مستودع OpenAI الموحّد. صُمّم Pando لتحقيق هدفين طموحين: أولهما، إتاحة عمليات كتابة قابلة للتوسع أفقيًا ومُحسّنة للدفع المتزامن إلى فروع عديدة. وثانيهما، تعظيم إنتاجية القراءة، ليتمكن العملاء من تنزيل البيانات بسرعة تستفيد بالكامل من عرض النطاق المتاح لشبكاتهم.
حققنا هذين الهدفين مع الحفاظ على التوافق مع بروتوكول Git وعميله، مما أتاح لمهندسينا ووكلائنا اعتماد Pando بسلاسة ضمن سير عملهم الحالي. في بيئة الإنتاج، خفّض Pando وسيط زمن استجابة الدفع إلى نحو الخُمس. وهو يقدّم الآن ما يقارب بيتابايت من بيانات Git يوميًا.
سنستعرض هنا دوافعنا لبناء نظام جديد للتحكم في الشيفرة المصدرية، وأسباب اختيارنا الحفاظ على التوافق مع Git. سنقارن نظامنا بالنهج الشائعة لتوسيع نطاق Git، ونوضح ما يميّز Pando في تلبية احتياجات OpenAI للتوسع. وسنناقش أيضًا الخطوات المقبلة، وكيف سنواصل المساهمة في Git لدعم العمل بالحجم الذي يتيحه الوكلاء.
غيّر Codex حجم التطوير ووتيرته في OpenAI، إذ أتاح للمهندسين إنجاز أعمال أكثر بكثير بالتوازي. وهذا يعني مزيدًا من الفروع وعمليات الدفع والدمج، ومزيدًا من عمليات الجلب والاستنساخ من بيئات التطوير ومهام البحث والتكامل المستمر.
وتتراكم هذه المتطلبات لتضاعف العبء. بات لدى المهندس الواحد عدة تغييرات جاهزة للتكامل المستمر في الوقت نفسه، ويمكن لكل عملية دفع أن تطلق عدة مهام تجلب الإيداع نفسه.
منذ أواخر العام الماضي تقريبًا، بدأت الأنظمة الداعمة لمستودعنا الموحّد تجد صعوبة في مواكبة الطلب. واجهنا بطئًا في الجلب والدفع، إلى جانب فشل عمليات الدمج، وانخفضت الموثوقية أحيانًا إلى ما دون 90%. فهل بلغنا حدود Git نفسه، أم حدود الطريقة التي نتيح بها مستودعنا الموحّد؟ نشأ Git من مجتمع كان يتبادل رقع النواة عبر البريد الإلكتروني. لأكثر من عقدين، واصل المطوّرون تكييف أدواتهم حول Git مع تغيّر طرق بناء البرمجيات. وعندما بدأنا نواجه صعوبات في مستودعنا الموحّد، كان من المهم لنا أن نتمكن من مواصلة البناء على تلك الجهود.
رأينا سبيلًا للحفاظ على سير العمل المألوف، مع إعادة بناء النظام الخلفي لمعالجة عمليات الدفع إلى فروع مختلفة بالتوازي وتقديم السجل نفسه لعدد كبير من عملاء القراءة بالتزامن. اعتماد Git أساسًا لسير عمل التحكم في الشيفرة المصدرية يعني أن تحسينات العميل يمكن أن تفيد جهات خارج OpenAI أيضًا. أردنا المساهمة بهذه التحسينات من نسختنا من Git(يفتح في نافذة جديدة) في المشروع الأصلي، ومشاركتها مع المجتمع الأوسع.
لتوسيع نطاق نظام التحكم في الشيفرة المصدرية بُعدان أساسيان: عدد كبير من عملاء الكتابة يدفعون إلى فروع مختلفة، وعدد كبير من عملاء القراءة يجلبون السجل نفسه. يحسّن Pando الأداء في كليهما.
أولًا، ينسّق Pando تحديثات كل فرع بصورة مستقلة. يمكن أن تتقدم تحديثات الفروع غير المترابطة بالتوازي، ويحصل عميل القراءة الذي يجلب فرعًا على السجل اللازم لذلك الإصدار. في المقابل، قد يرى عميل القراءة الذي يطّلع على عدة فروع حالاتها في نقاط زمنية مختلفة.
ثانيًا، يعيد Pando استخدام ملفات الحزم (صيغة Git لضغط عدة كائنات معًا) عبر عمليات الجلب بدلًا من إنشاء ملف حزمة خاص لكل عميل. قد ينزّل العميل بعض الكائنات الموجودة لديه بالفعل، لكن الخادم يستطيع تقديم البايتات نفسها لعدد كبير من العملاء بقدر ضئيل من العمليات الحسابية.
تتيح لنا هذه الخيارات توسيع نطاق الكتابة عبر الفروع وتلبية طلبات القراءة من ذاكرات التخزين المؤقت. لفهم كيفية تكامل هذه الخيارات، لنبدأ بطريقة تخزين Git للسجل ونقله.
يجب أن يتمكن Git من الحفاظ على مزامنة جزأين من المستودع: الكائنات والمراجع.
يسجّل مستودع Git لقطات الملفات والسجل الذي يربط بينها على هيئة رسم بياني من الكائنات. يمكن أن يكون الكائن كتلة بيانات أو شجرة أو إيداعًا أو وسمًا. تحتفظ كتل البيانات بمحتوى كل ملف على حدة، وتربط الأشجار الأسماء بكتل بيانات أو بأشجار أخرى لتشكيل بنية المجلدات. تشير الإيداعات إلى شجرة جذرية وإلى صفر أو أكثر من الإيداعات الأب، لتشكّل سجل المستودع.
تُعرَّف الكائنات بقيمة تجزئة لنوعها ومحتواها. والكائنات غير قابلة للتغيير أيضًا: فتسجيل ملف تغيّر ينشئ كتلة بيانات جديدة، بينما يمكن للملفات والمجلدات التي لم تتغير إعادة استخدام الكائنات الموجودة.
الفروع والوسوم هي مراجع، تُعرف اختصارًا باسم «refs»، وتشير إلى إيداعات. يؤدي تنفيذ إيداع على فرع إلى إنشاء إيداع جديد وتحديث مرجع الفرع ليشير إليه.
تشير كائنات Git إلى بعضها باستخدام معرّفاتها؛ فالإيداعات تشير إلى الأشجار، والأشجار تشير إلى أشجار أخرى وكتل بيانات. يشير الفرع refs/heads/main إلى الإيداع C2، الذي يكون C1 إيداعه الأب. يشير C1 إلى الشجرة الجذرية T1، حيث يشير parser.py إلى كتلة البيانات B1. يشير C2 إلى الشجرة الجذرية T2، حيث يشير parser.py إلى كتلة البيانات B2. في كلتا الشجرتين الجذريتين، يشير docs/ إلى الشجرة نفسها، T3. يشير المدخل README.md فيها إلى كتلة البيانات B3، ويشير usage.md إلى كتلة البيانات B4.
يخزّن Git الكائنات إما «منفردة» أو «مجمّعة في حزم». تُخزَّن الكائنات المنفردة في ملفات مستقلة تُعرَّف بمعرّف الكائن. تُخزَّن الكائنات المجمّعة معًا في ملف حزمة، مما يتيح تخزين الكائنات المتشابهة كفروق بالنسبة إلى بعضها.
يمكن لـGit تخزين كتل البيانات في ملفات منفصلة أو تجميعها في حزم، وترميز إحدى الكتل بتسجيل الفروق بينها وبين كتلة أخرى.
في Git، يكون الكائن قابلًا للوصول من إيداع إذا استطاع Git العثور عليه بتتبّع الروابط من ذلك الإيداع عبر إيداعاته الأب وأشجاره وكتل بياناته. أثناء الجلب، يخبر العميل الخادم بالإيداعات التي يريدها وتلك الموجودة لديه بالفعل. يعثر الخادم على الكائنات التي يمكن الوصول إليها من الإيداعات المطلوبة، ويستبعد ما يعرف أنه موجود لدى العميل، ويرسل الباقي كملف حزمة بالصيغة نفسها التي يستخدمها Git على القرص. يبدأ الاستنساخ الكامل دون أي كائنات، لذا يحتاج إلى جميع الكائنات التي يمكن الوصول إليها من السجل الجاري استنساخه.
تنقل عملية الدفع الكائنات في الاتجاه المعاكس، بطلب تحديث مرجع واحد أو أكثر من الخادم. يستخدم العميل مجموعة المراجع التي يعلنها الخادم لتجنب إرسال كائنات موجودة لدى الخادم بالفعل. لذلك قد تعتمد الحزمة المُرسَلة على كائنات موجودة مسبقًا؛ فتلقّي الحزمة وحدها لا يكفي لإعادة بناء السجل الجديد.
يعني إنشاء نسخ متماثلة من مستودع Git إبقاء الكائنات والمراجع متاحة عبر عدة خوادم. قبل أن تتمكن نسخة متماثلة من تقديم القيمة الجديدة لمرجع، يجب أن تتمكن من توفير الكائنات التي يمكن الوصول إليها من تلك القيمة. تحتاج الخدمة أيضًا إلى تنسيق تحديثات المراجع المتنافسة، أي عمليات الدفع التي تحاول نقل الفرع نفسه من الرأس القديم نفسه إلى إيداعات جديدة مختلفة. قد تجعل الأنظمة التي تفرض ترتيبًا واحدًا على جميع عمليات الكتابة في المستودع عمليات الدفع إلى فروع غير مترابطة تنتظر بعضها.
أثناء تصميم Pando، قيّمنا نهجين تقليديين لتوسيع نطاق التحكم في الشيفرة المصدرية باستخدام Git: أحدهما ينسّق تحديثات المراجع بين نسخ المستودع المتماثلة باستخدام التثبيت ثلاثي المراحل (3PC، وهو بروتوكول لتنسيق التثبيت عبر عدة عُقد)، والآخر يرتّب التحديثات عبر سجل كتابة مسبقة مشترك (WAL، وهو سجل تُكتب فيه التغييرات قبل تطبيقها) في مخزن كتل البيانات.
في تصميم التثبيت ثلاثي المراحل الذي حاكيناه، تحتفظ خدمة Git بنسخة كاملة من المستودع في عدة نسخ متماثلة. عندما يتلقى أي خادم عملية دفع، يُنسخ ملف الحزمة الوارد ويُرسَل إلى جميع النسخ المتماثلة بالتوازي. لنشر عملية الدفع، يجب أن تقبل أغلبية النسخ المتماثلة تحديث المرجع. يُسمّى الخادم الذي يدير هذه العملية المنسّق، وتتطلب العملية عدة جولات اتصال بينه وبين كل نسخة متماثلة أخرى.
لقبول تحديث مرجع، تقفل كل نسخة متماثلة نسختها من المرجع وتتحقق من أنه لا يزال يشير إلى الإيداع الذي يتوقعه العميل. يرسل المنسّق بعد ذلك رسالة ما قبل التثبيت إلى النسخ المتماثلة التي وافقت. بمجرد أن تقرّ الأغلبية باستلام رسالة ما قبل التثبيت، يطلب منها المنسّق تنفيذ التحديث. يمكن لأي نسخة متماثلة محدّثة بعد ذلك الإعلان عن رأس الفرع الجديد من نسختها المحلية من المستودع.
يتيح الاحتفاظ بنسخ متماثلة كاملة تلبية طلبات القراءة محليًا، ويضمن حصول العملاء على رؤية متسقة لجميع المراجع أيًا كانت النسخة التي يختارونها. نتيجةً لذلك، تُحدَّث المراجع واحدًا تلو الآخر، وتتطلب كل عملية دفع عدة جولات اتصال لتنسيق تحديث المرجع بين النسخ المتماثلة.
يجعل انتظار النصاب أداء أقراص النسخ المتماثلة وزمن الاتصال عاملين مباشرين في زمن إنجاز الدفع. مع نمو حجم الكتابة، يجب أن تواكب النسخ المتماثلة الحزم الواردة إلى جانب حركة القراءة.
في التصميم القائم على WAL الذي حاكيناه، تستخدم خدمة Git ملف بيان مشتركًا في مخزن كتل البيانات لترتيب التحديثات على مستوى المستودع. عندما يتلقى خادم عملية دفع، يرفع ملف الحزمة الوارد إلى مخزن كتل البيانات ويجهّز تحديث المرجع في نسخته المحلية من المستودع. لنشر عملية الدفع، يضيف الخادم مدخلًا إلى ملف بيان مشترك محفوظ في مخزن كتل البيانات، يسجّل تحديث المرجع ويشير إلى ملف الحزمة. ثم يحدّث خادم Git ملف البيان باستخدام عملية مقارنة واستبدال. تستبدل هذه العملية ملف البيان بصورة ذرّية، أو ترفض التغيير إن تعذّر ذلك. إذا كان عميل كتابة آخر قد حدّثه في الأثناء، يقرأ الخادم المدخلات التي أُضيفت، ويتحقق من أن تحديث مرجعه ما زال صالحًا، ثم يعيد المحاولة باستخدام ملف الحزمة الذي رفعه بالفعل.
يمكن لعملاء الكتابة رفع ملفات حزمهم بالتوازي، لكن حتى عمليات الدفع إلى فروع غير مترابطة تتطلب تحديث ملف البيان نفسه. يحدّ زمن دورة الاتصال اللازمة لنجاح عملية المقارنة والاستبدال من وتيرة نشر المستودع لملف بيان جديد. يجعل مخزن كتل بيانات منخفض زمن الاستجابة هذه الخطوة سريعة. تتيح المعالجة على دفعات لملف بيان واحد تحديث عدة مراجع في الوقت نفسه، لكن مقابل وضع التحديثات في طابور وزيادة زمن الاستجابة.
يستطيع مخزن كتل البيانات تلقّي ملفات الحزم بالتوازي، لكن تحديثات ملف البيان المشترك تظل تتنافس فيما بينها. زيادة سعة مخزن كتل البيانات وحدها لا تحلّ هذا الاختناق. لا تزال كل نسخة متماثلة بحاجة إلى قراءة الحزم من مخزن كتل البيانات وفهرستها قبل أن تتمكن من تقديمها محليًا. إذا تجاوز معدل الكتابات الواردة سرعة إنجاز ذلك العمل، تتأخر النسخ المتماثلة ويضطر العملاء إلى الانتظار مدة أطول لجلب الإيداعات المدفوعة حديثًا.
وهذا يقودنا إلى Pando. يقدّم Pando بنية جديدة لخدمة مستودعات Git، بهدف التوسع لاستيعاب عدد كبير من العملاء الذين يدفعون بالتزامن. إليك كيفية عمله:
قبل تطوير Pando، كان لدينا حدس بأننا نستطيع تلبية متطلباتنا دون التخلي عن Git، لكن كان علينا التنازل عن بعض الأمور التي تفترض معظم أنظمة Git أنها متطلبات أساسية:
الاتساق على مستوى المستودع بأكمله: يتطلب توفير لقطة متسقة لجميع المراجع في نقطة زمنية واحدة تنسيقًا على مستوى المستودع. في نظام يرتّب جميع تحديثات المراجع معًا، تتنافس عمليات الدفع إلى فروع غير مترابطة على نقطة تنسيق واحدة.
ملفات حزم مخصصة لكل عميل: إذا جلب عميلان بسجلَّين محليَّين مختلفين الفرع نفسه، فقد يحتاج الخادم إلى إنشاء حزمة مختلفة لكل منهما لتجنب إرسال كائنات موجودة لديهما بالفعل. يستهلك إنشاء هذه الاستجابات موارد المعالج وعمليات الإدخال والإخراج على القرص، مما يصعّب التوسع لخدمة أعداد كبيرة من عملاء القراءة.
بدلًا من ذلك، يوفّر Pando نمط اتساق أقل صرامة (الاتساق السببي) لتوسيع نطاق الكتابة، ويعيد استخدام ملفات الحزم لتوسيع نطاق القراءة.
عندما يدفع عميل إلى فرع، يحفظ Pando ملف الحزمة الوارد ككتلة بيانات غير قابلة للتغيير في مخزن لكتل البيانات. كلما تغيّر الإيداع الذي يشير إليه الفرع، يسجّل Pando في مخزن البيانات الوصفية إصدارًا للمرجع يتضمن رأس الفرع الجديد، والحزمة التي وفّرته، وأي إصدارات سابقة للمراجع تعتمد عليها الحزمة. يمكن توسيع كل من مخزن كتل البيانات ومخزن البيانات الوصفية أفقيًا.
يتطلب جلب إيداع أو مرجع من Pando إعادة إرسال مجموعة من إصدارات المراجع (ملفات الحزم) إلى العميل، لإعادة بناء حالة العميل الذي أنشأ إصدار المرجع الجاري جلبه. يوفّر ذلك درجة أقل من الاتساق على مستوى المستودع بأكمله، مع ضمان قابلية التسلسل لتحديثات كل مرجع على حدة. لا تتعارض عمليات العملاء إلا عند الدفع إلى المرجع نفسه في الوقت ذاته. لا يمكن للعميل الذي يقرأ عدة فروع أن يفترض أن تلك القراءات تمثل نقطة زمنية واحدة على مستوى المستودع.
تضيف عملية الدفع إلى الفرع الرئيسي حزمة تزايدية تحتوي على إيداع واحد أو أكثر. تتضمن كل عملية دفع إلى فرع جانبي إيداعات ذلك الفرع السابقة مجددًا، حتى يتمكن العميل الذي لديه الإصدار المُعلَن من الفرع الرئيسي من جلب الفرع الجانبي في حزمة واحدة.
وهذا أحد أشكال الاتساق السببي. لجلب مرجع، يطّلع عميل القراءة على سجل عميل الكتابة الذي أنشأه. أثناء الدفع، يتحكم Pando في السجل المشترك الذي يتفق عليه العميل والخادم، لضمان أن يُنتج العملاء ملفات حزم يسهل تشاركها.
نتيجةً لذلك، لا يوفّر Pando حالة لجميع المراجع في نقطة زمنية واحدة أثناء عمليات Git، ولا ترتيبًا لتحديثات المراجع ضمن عملية استنساخ أو جلب. للتوضيح، لنفترض أن الفرعين foo وbar يُحدَّثان كلاهما أثناء التفاوض على استنساخ كامل. أثناء التفاوض، قد يرى أحد العملاء foo بالإصدار 1 وbar بالإصدار 2، بينما يرى عميل آخر foo بالإصدار 2 وbar بالإصدار 1. عمليًا، لا يعتمد المستخدمون والوكلاء على الترتيب العام لتحديثات المراجع، بل على ترتيب تحديثات كل مرجع على حدة.
في هذه المحاكاة لأنماط الاتساق الثلاثة، نضبط 1,000 عميل بحيث يختار كل منهم فرعًا مختلفًا ثم يدفع إيداعات فريدة بصورة متكررة، ونقيس إجمالي الوقت الفعلي المنقضي والزمن بين الدفع وإمكانية الجلب. المقياس الأخير مهم لأعباء العمل، إذ يقيس زمن دورة التفاعل اللازمة لتعاون المستخدمين والوكلاء أو لاستخدام أنظمة أخرى تعتمد على التحكم في الإصدارات. تمثّل المحاكاة مستويات تجهيز تتراوح من الأجهزة الافتراضية وأنظمة تخزين كتل البيانات القياسية إلى الفئات المتقدمة منها. في هذه المحاكاة، خفّفنا متطلبات الاتساق على مستوى المستودع في نظام التثبيت ثلاثي المراحل.
Simulating Git Server Implementations
Follow each writer through repeated pushes, and separately measure when an independent reader can check out each commit.
شغّل هذه المحاكاة لمقارنة Pando بنهجين آخرين. اختر البنية التحتية، من العتاد القياسي إلى المتقدم، وعدد العملاء، والمزيد من الإعدادات للاطّلاع على المقارنة بينها.
يُظهر نظام التثبيت ثلاثي المراحل في المحاكاة زمن استجابة أطول بكثير للكتابة تحت الحمل مع بلوغ الأقراص طاقتها القصوى؛ في المقابل، تُظهر أنظمة سجل الكتابة المسبقة زمن استجابة أطول بكثير للقراءة للسبب نفسه، إذ تجد الخوادم صعوبة في مواكبة القراءة من السجل. في الاختبارات الفعلية على مستودعنا الموحّد وفي هذه المحاكاة، يدعم Pando آلافًا عديدة من العملاء الذين يستطيعون الدفع بالتزامن دون التأثير في الأداء تحت الحمل.
عند معالجة عملية دفع، يتحقق Pando من صلاحية ملف الحزمة الوارد أثناء بثّه إلى مخزن كتل البيانات، دون فكّه في مستودع محلي. عندما يبدأ العميل عملية جلب، يختار Pando الحزم المخزّنة التي يحتاج إليها عميل القراءة، ثم ينزّلها العميل. لا يحتاج أي خادم إلى تطبيق ملفات الحزم على مستودع محلي أو تنفيذ عمليات فكّ وتجميع حزم مكلفة. نظرًا إلى أن هذه الخوادم تتولى أساسًا التفاوض واختيار ملفات الحزم، يمكن تشغيل خوادم Pando على عتاد قياسي.
يتيح ذلك لـPando التوسع حتى حدود قدرات العملاء، والشبكة بينهم وبين Pando، وخدمة تخزين كتل البيانات. مع توفير سعة كافية، يستطيع كل عميل يعمل على فرع مستقل نقل الحزم بأقصى سرعة يسمح بها اتصاله. لخدمة مزيد من العملاء بهذه السرعة، يمكننا إضافة خوادم Pando وتوسيع مخزن البيانات الوصفية ومخزن كتل البيانات بالتوازي معها.
Pushing and Fetching a Branch in Pando
Pando stores the writer’s packfile in blob storage and records the ref version in metadata service. On fetch, Pando selects the packs the reader needs, and Express serves them.
يُجري أحد الوكلاء تعديلات على الملفات وينشئ إيداعًا محليًا، ثم يشغّل git push، الذي ينشئ ملف حزمة بهذه التغييرات لتحديث فرع. يتلقى Pando الحزمة ويبثّها إلى مخزن كتل البيانات. بعد التحقق من صلاحية عملية الدفع الواردة بفك ترميزها في مرور واحد، يستخدم Pando معاملة متعددة المفاتيح لتسجيل إصدار مرجع في مخزن البيانات الوصفية وتحديث مؤشّر المرجع بعملية ذرّية. يتيح ذلك لـPando توفير اتساق «قراءة ما كتبته بنفسك». تتطلب عملية الكتابة بأكملها من عمليات قراءة البيانات الوصفية وكتابتها.
عندما يجلب عميل آخر الفرع، يختار Pando مجموعة من الحزم المخزّنة بالاستناد إلى السجل الموجود لدى العميل بالفعل. تتطلب هذه العملية من عمليات قراءة البيانات الوصفية، حيث تمثّل أقصر مسافة، مقاسةً بإصدارات المراجع، بين ما لدى العميل وما يريده. يعيد الخادم معرّفات URI لملفات الحزم، وينزّلها العميل عبر خدمة Express التي تخزّن ملفات الحزم في ذاكرات تخزين مؤقت إقليمية.
يعتمد هذا التصميم على الخدمات الثلاث الأساسية في بنية Pando: الدفع والجلب ودمج الحزم.
عندما يدفع عميل، يتصرف خادم Git التقليدي كلاعب بوكر يكشف جميع أوراقه، فيُظهر للعميل كل الفروع الموجودة لديه. يجمع العميل بعد ذلك الإيداعات والأشجار وكتل البيانات لإنشاء ملف حزمة يستبعد الكائنات التي يعرف أنها موجودة لدى الخادم بالفعل.
يمكن أن يؤدي الإعلان عن مزيد من المراجع إلى تقليل حجم عملية الدفع. لكنه قد يصعّب أيضًا إعادة استخدام الحزمة، لأن عميلًا آخر قد يفتقر إلى كائنات استبعدها عميل الكتابة.
يتبع Pando نهجًا مختلفًا. فبدلًا من كشف جميع أوراقه، لا يعلن للعميل إلا عن مجموعة مختارة من الفروع. ويفعل ذلك لسببين:
توفير رؤية متسقة لجميع المراجع مكلف. مع وجود ملايين الفروع، قد يتجاوز حجم إعلان المراجع وحده 100 MiB. ويتطلب توفير ذلك من كل خادم إما الاحتفاظ بخريطة ربط محدّثة، بما يستلزم عبئًا كبيرًا للتنسيق، أو تنفيذ استعلام في قاعدة البيانات تتزايد كلفته مع عدد المراجع.
عند تزويد العميل بجزء من المراجع فقط، سينشئ حزمة لا تعتمد إلا على ما يسمح له الخادم صراحةً باستبعاده. يتيح تتبّع الاعتماديات لـPando إعادة استخدام ملف الحزمة الذي ينشئه عميل في عملية دفع، وتقديمه استجابةً لعملية جلب لاحقة.
كلما أعلن خادم Git عن مزيد من المراجع، توفرت للعملاء الذين يدفعون مصادر أكثر لاستبعاد الكائنات منها، مما يتيح لهم إنتاج حزم أصغر. لكن إعادة استخدام ملفات الحزم هذه تتطلب أن يتمكن الخادم من التحقق من أن العملاء الذين يجلبونها لديهم كل الاعتماديات. وإلا فقد يخلو ملف الحزمة من كائنات يحتاج إليها العميل.
من ناحية أخرى، إذا لم يعلن خادم Git عن أي مراجع، فسينشئ العميل ملف حزمة يحتوي على سجل المستودع كاملًا. يمكن لأي عميل آخر استخدام ذلك الملف، لكن بكلفة كبيرة تجعل جلب أي فرع بمثابة استنساخ كامل عمليًا.
يؤدي الإعلان عن مزيد من المراجع إلى ملفات حزم أصغر. ويمكن تحقيق معظم الانخفاض في الحجم بالإعلان عن مرجع واحد فقط.
افتراضيًا، عندما يدفع عميل فرع ميزة جديدًا، لا يعلن Pando إلا عن فرع رئيسي محدّد ليكون أساسًا. يُبقي ذلك حجم الإعلان ثابتًا مع ازدياد فروع المستودع، لكنه ينطوي على مفاضلات. عندما يدفع مهندس إلى فرع واحد مرات عديدة، أو يبني فرع ميزة فوق آخر، يتعين على العميل إعادة إرسال كل ما هو خارج الفرع الرئيسي.
يجعل ذلك عمليات الدفع المتتالية إلى الفرع أكبر حجمًا، وقد تتضمن كائنات مكررة، ويستهلك عرض نطاق إضافيًا لدى العميل. في مستودعنا الموحّد، كان وسيط حجم ملفات الحزم المُنتجة بهذه الطريقة أقل من 20 KiB، وكانت قيمة المئين 99 أقل من 3 MiB. كانت هذه مفاضلة منطقية لنا، لأن عميل القراءة الذي لديه بالفعل سجل الفرع الرئيسي المُعلَن يستطيع جلب الفرع باستخدام ملف الحزمة الواحد هذا.
بعد اختيار مجموعة المراجع التي سيعلنها للعميل، يقبل Pando عملية الدفع ويحدّث مخزن البيانات الوصفية. يقسّم Pando تحديث البيانات الوصفية إلى جزء متزامن وآخر غير متزامن. يضمن التحديث المتزامن للبيانات الوصفية أن يتمكن العملاء فورًا من جلب الإيداع الذي أصبح المرجع يشير إليه. يكتب هذا التحديث إصدارًا جديدًا للمرجع ويحدّث ربط مؤشّر المرجع ليشير إليه، مع تخزين رأس الفرع الجديد، والاعتماديات المستخدمة في التفاوض على ملف الحزمة، ورابط إلى ملف الحزمة الذي دُفع.
وفي الوقت نفسه، تُسجَّل الأعمال غير المتزامنة ضمن المعاملة ذاتها، مثل جدولة دمج الحزم. في عمليات الدفع الكبيرة جدًا، لا يُكتب بصورة متزامنة إلا جزء من الإيداعات. يتيح لنا ذلك الاكتفاء بعدد ثابت من عمليات القراءة والكتابة في مخزن البيانات الوصفية، بغض النظر عن حجم عملية الدفع.
والأهم أن خوادم Pando تستطيع قبول عمليات الدفع إلى مراجع غير مترابطة بصورة مستقلة. المفتاح الوحيد الذي يحدث عليه تنافس هو مؤشّر المرجع، ونحميه بعملية ذرّية تشبه المقارنة والاستبدال. يمكننا زيادة إنتاجية الكتابة بإضافة خوادم Pando، أو وحدات توسعة لمخزن البيانات الوصفية، أو مخازن لكتل البيانات.
عندما ينفّذ عميل عملية جلب، يزوّد الخادم بمجموعتين من معرّفات الإيداعات، تُعرفان في البروتوكول باسم have وwant. في خادم Git التقليدي، يحدّد الخادم الكائنات التي يمكن الوصول إليها من الإيداعات المطلوبة، ثم يستبعد منها ما يمكن الوصول إليه من الإيداعات الموجودة لدى العميل، ثم يضغط الكائنات اللازمة ويرسل النتيجة إلى العميل. مع أن خادم Git مفتوح المصدر يمتلك فهارس محلية متنوعة لتسريع هذه العملية، فقد يكون إنشاء ملفات الحزم مكلفًا حسابيًا، ولا يسهل عادةً تخزينها مؤقتًا لإعادة استخدامها.
بدلًا من ذلك، يستخدم Pando خرائط ربط الإيداعات المسجّلة أثناء الدفع للبحث عن إيداعات العميل ضمن have وwant، وربطها بمجموعة من إصدارات المراجع التي تحتوي عليها. يرتبط كل إصدار من تلك المراجع بملف حزمة سبق أن تلقّاه Pando. يستخدم الخادم هذه المعلومات لتحديد احتياجات العميل، واختيار ملفات الحزم التي تغطي مجموعة الكائنات المطلوبة.
Planning fetches in Pando
Pando compares what a client has with what it wants, then selects the stored packfiles needed—including any missing branch dependencies.
Fetch 27 trunk packs.
Select a branch to include its trunk dependencies.
يربط Pando قوائم الإيداعات الموجودة والمطلوبة في بروتوكول Git بتسلسل من إصدارات المراجع. طلب فرع جانبي يعني أيضًا طلب السجل حتى النقاط التي يعتمد عليها في الفرع الرئيسي.
لا يسمح بروتوكول Git بإرسال أكثر من ملف حزمة واحد ضمن استجابة HTTP لعملية استنساخ أو جلب. لكن ميزة في بروتوكول Git لا تحظى بما تستحقه من اهتمام، هي packfile-uri، تتيح للخادم إعادة روابط لتنزيل حزم إضافية بصورة منفصلة. يعتمد Pando على هذه الميزة حصريًا، ويرسل ملف حزمة فارغًا ضمن الاستجابة. لدعم هذه الميزة، ساهمت OpenAI بعدد من التحسينات على packfile-uri في مشروع Git الأصلي.
تعمل أنظمة OpenAI في مناطق جغرافية عديدة، لذا نشغّل عناقيد Pando مع ذاكرات تخزين مؤقت محلية في كل منطقة. تتولى خدمة توصيل الحزم Express التخزين المؤقت لمحتوى مخزن كتل البيانات، وتتولى خدمة منفصلة التخزين المؤقت لمحتوى مخزن البيانات الوصفية. تتيح هذه الذاكرات لـPando تلبية طلبات معرّفات الإيداعات أو عمليات القراءة الخاصة للمراجع بالكامل من الحالة المخزّنة مؤقتًا في العنقود. نستخدم التجزئة المتسقة لتوجيه طلبات ملفات الحزم إلى خوادم Express، وتُلبّى أكثر من 99% من هذه الطلبات من ذاكرة التخزين المؤقت.
توفّر منظومة التخزين المؤقت هذه إنتاجية مرتفعة للغاية، تكفي لاستهلاك كامل سعة الشبكة بين العملاء وPando. باستخدام معرّفات URI لملفات الحزم وذاكرات التخزين المؤقت المحلية، تحقّقنا من أن عمليات الاستنساخ والجلب تستهلك كامل سعة وصلات شبكة بسرعة 10 غيغابت/ثانية، باستخدام أجهزة افتراضية قياسية فقط من مزوّدي الخدمات السحابية.
يتطلب الحفاظ على أداء المستودع مع نموه، في معظم خوادم Git، إعادة تجميع الحزم وتنظيف المستودع من الكائنات غير المستخدمة. تستعيد الخطوتان مساحة تخزين، لكنهما تستهلكان موارد حسابية وعمليات إدخال وإخراج كثيفة.
في مستودع سريع التغيّر مثل مستودع OpenAI الموحّد، ليس من العملي إرسال معرّف URI منفصل لملف حزمة لكل عملية دفع منذ آخر جلب. فسيحتاج العميل يوميًا إلى تنزيل عشرات الآلاف من ملفات الحزم لتحديث فرعه الرئيسي.
بدلًا من ذلك، يجمع Pando الحزم في الخلفية لتقليل هذا العبء، وفق تصميم مشابه لدمج البيانات في أشجار الدمج المهيكلة كسجل. تنتج كل عملية دفع ملف حزمة يسمّيه Pando «L0»، ويمثّل أصغر وحدة بيانات يمكن لـPando نقلها. بمرور الوقت، تجمع عمليات الخلفية هذه الملفات في مجموعات تغطية تشمل تسلسلًا من إصدارات المراجع، وتتزايد أحجامها بمتوالية هندسية انطلاقًا من L0. يستخدم مستودعنا الموحّد اليوم مجموعات تغطية لملفات الحزم تصل إلى المستوى L4.
وتؤدي مجموعات التغطية هذه غرضين. عندما تمتلئ مجموعة تغطية بما يكفي، تتولاها عملية دمج وتنتج منها ملف حزمة واحدًا. يحسّن ذلك نسبة الضغط بإعادة تنظيم الكائنات داخل ملف الحزمة. كما يحسّن أداء التفاوض على الجلب، إذ تعمل مجموعات التغطية كقائمة تخطٍّ عبر إصدارات المراجع. بمسح الفهارس في كل مستوى بالتزامن، يستطيع Pando اختيار مجموعة حزم لإرسالها إلى العميل في زمن لوغاريتمي. تتيح مجموعات تغطية ملفات الحزم، سواء أُعيد تجميعها أم لا، حلًا أكثر كفاءة لتخطيط الجلب، وتخفّض متطلبات الإنتاجية وزمن الاستجابة على Pando.
Compaction in Pando
Small packfiles are grouped into larger covers across levels, improving compression and speeding up fetches.
تُدمج في الخلفية ملفات الحزم الخاصة بإصدارات متعددة من المراجع، مما يتيح جلب البيانات بكفاءة أكبر باستخدام عدد أقل من الملفات.
على خلاف كثير من خوادم Git، يُحتفَظ بملفات الحزم في كل مستوى، ولا تُحذف أو تُزال بعمليات جمع البيانات غير المستخدمة. تُستخدم الحزم في كل مستوى لخدمة العملاء باستمرار، وفق قوائم have وwant التي يرسلونها في عملية الجلب. أما العملاء الذين يستخدمون Pando كواجهة API، فغالبًا ما تتطلب قراءة كائن عشوائي من ملف حزمة قراءة بيانات أقل من ملف L0 مقارنةً بملف L4، لأن احتمال ضغط الكائن فيه أقل.
يقلّل دمج الحزم في Pando إجمالي حجم البيانات المنقولة إلى العملاء بدرجة كبيرة، ويتيح أن تنمو كلفة تخطيط الحزم بمعدل دون خطي مع مقدار تأخر العميل عن أحدث حالة. وهذا مهم خصوصًا للمستودعات الكبيرة وسريعة التغيّر، التي نتوقع أن تصبح النمط السائد الجديد في تطوير البرمجيات.
بنينا Pando باستخدام Codex، وانتقلنا من انطلاق المشروع إلى التشغيل في بيئة الإنتاج خلال 10 أسابيع فقط. صمّم مهندس واحد جوهر Pando، ونفّذته فرق من الوكلاء، ونشره عدد قليل من المهندسين. بدأ Pando معالجة حركة الإنتاج في أبريل من هذا العام، وهو جزء من منظومة أكبر من أدوات إدارة الشيفرة المصدرية المستخدمة في OpenAI.
استخدمنا مجموعة متنوعة من الأساليب للتحقق من صحة تصميم Pando. طبّقنا مبادئنا بشأن بناء أسس برمجية أكثر أمانًا. ولأن Pando مشروع جديد من الصفر، اخترنا تنفيذه بلغة Rust ودون اعتماديات على مكوّنات قديمة. كتبنا مجموعة اختبارات توافق ليتمكن الوكلاء من اختبار تغييراتهم في ضوء السلوك القياسي لـGit. وطوّرنا أيضًا أُطرًا لتشغيل اختبارات المحاكاة الحتمية، أتاحت لنا التحقق من ثوابت النظام وخصائصه، مما مكّننا من اكتشاف حالات تسابق دقيقة وتراجعات في التفاوض وتخطيط الحزم، والحد منها.
ساهمت نماذج عديدة في هذا النظام؛ وكلما توفرت نماذج جديدة، استخدمناها لتحسين أداء Pando وموثوقيته. باستخدام GPT‑6 Astra، كتب الوكلاء شيفرة بلُغتي Lean وTLA+ لصياغة «نظرية Pando» بلغة رياضية صورية، واستخدمناها لاكتشاف أخطاء التنفيذ وإصلاحها وإثبات خصائص النظام. امتدت عدة جلسات متواصلة لمئات الساعات، بحثًا عن الحالات الحدّية في Pando وإصلاحها. ونشغّل أيضًا وكلاء على مدار الساعة لاختبار Git وPando بأساليب الفريق الأحمر، باستخدام نماذج تدعم قدرات Daybreak.
نشرنا Pando في البداية كوسيط يمرّر عمليات القراءة والكتابة إلى نظامنا القائم للتحكم في الشيفرة المصدرية. أتاح لنا ذلك إبقاء Pando والنظام السابق متزامنَين، ونقل حركة الطلبات إلى Pando بأمان ودون توقف الخدمة.
وضمن تصميم التخزين المؤقت، يستطيع Pando أيضًا إزالة الطلبات المكررة ودمج الطلبات قبل تمريرها. أتاح لنا ذلك الحفاظ على سلامة تشغيل أدواتنا القائمة أثناء نقل الأنظمة إلى Pando واحدًا تلو الآخر. مع نقل المزيد من أعباء العمل إلى Pando، ملأت عمليات الدفع الخاصة بها ذاكرته المؤقتة وحسّنت موثوقية نظامنا الخلفي السابق.
لتحسين الأداء عند استخدام Pando، بدأنا نشر توزيعتنا العامة من Git على أجهزة المطوّرين وضمن تطبيق ChatGPT لسطح المكتب. استخدمنا غلافًا داخليًا لواجهة سطر أوامر Git لنقل العملاء إلى Pando على مراحل. أنشأنا أدوات عديدة أخرى للتعامل مع طلبات التغيير ومراجعة الشيفرة، وواجهات API لعمليات الأتمتة. ونقلنا مئات التكاملات التي تعتمد على واجهات API قديمة وخطافات الويب وتنزيلات المستودعات، إلى جانب حركة Git.
ساعد Codex نفسه المطوّرين والفرق على الانتقال. أنشأنا مهارات لتهيئة نسخ العمل الحالية، ولتوجيه الوكلاء إلى استخدام النظام الجديد. نتيجةً لذلك، أصبح بإمكان المطوّرين ببساطة أن يطلبوا من Codex نقل بيئات التطوير والخدمات الخاصة بهم.
حسّن الانتقال إلى Pando موثوقية الدمج، وخفّض وسيط زمن استجابة الدفع بنحو 80%.
يستطيع Pando تقديم بيتابايت من بيانات Git يوميًا باستخدام عدد قليل فقط من أنوية المعالج. قدّم Pando أكثر من 57 تيرابايت من حزم Git في ساعة واحدة، وجاء أكثر من 99% من تلك البايتات من ذاكرة التخزين المؤقت على الأقراص المحلية.
تتيح إعادة استخدام الحزم لعدد كبير من العملاء تنزيل سجل المستودع نفسه بقدر ضئيل من العمليات الحسابية على عُقد تقديم البيانات. يعرض هذا المثال مستودعًا واحدًا، لكن Pando مصمّم لدعم مستودعات أكثر بكثير. مع نمو الطلب، يمكننا توسيع نطاق توصيل البيانات والتفاوض في Git كلًّا على حدة.
ساعدنا بناء Pando على بروتوكول Git وعميله في اكتشاف أخطاء وفرص لتحسين أداء Git نفسه.
فمثلًا، يقدّم Pando حزمًا قابلة لإعادة الاستخدام مباشرةً إلى العملاء، لذا أصبح تنزيل هذه الحزم وفهرستها عنصرًا أساسيًا في جعل النظام سريعًا وموثوقًا. كشف ذلك العمل حالات حدّية في التنزيلات المتزامنة، والتنظيف بعد انقطاع عمليات النقل، واستئناف تنزيلات كانت قد اكتملت بالفعل. أصلحنا مسارات التنفيذ تلك، وأضفنا فحوص تحقق عند قبول حزمة موجودة لعملية جلب، وساهمنا بهذه التعديلات في المشروع الأصلي.
واحتجنا أيضًا إلى معرفة أين يُستهلك الوقت وعرض النطاق، فأضفنا تتبّعًا لتنزيلات packfile-uri وعدادات للبايتات أثناء إنشاء الحزم، مما سهّل تحديد ما إذا كانت عملية الجلب البطيئة تقضي وقتها في إنشاء الحزم أم نقلها.
في نسخة Git من OpenAI(يفتح في نافذة جديدة)، يستطيع العميل تنزيل الحزم المستقلة وفهرستها بالتوازي. أضفنا مفهوم «الإيداعات الموجودة المُعلَنة صراحةً»، ليتمكن الخادم من الإعلان عن رأس الفرع الحالي بصورة منفصلة عن السجل الذي يمكن للعميل الدافع استبعاده من حزمته. في Pando، يتيح ذلك للعملاء استخدام --force-with-lease للتحقق من أن فرع الميزة لم يتغير، وبناء الحزمة بالاستناد إلى الفرع الرئيسي وحده. بعد ذلك، يستطيع أي عميل لديه سجل الفرع الرئيسي جلب الفرع باستخدام الحزمة نفسها.
أُدرج عدد من هذه التحسينات بالفعل في مشروع Git الأصلي، ويتوفر كثير غيرها في مستودعنا العام. نواصل العمل مع مجتمع Git لجعل هذه التحسينات مفيدة خارج نطاق Pando أيضًا. يمكنكم متابعة هذا العمل عبر متتبّع الرقع العام(يفتح في نافذة جديدة) الخاص بنا.
بإعادة النظر في الأفكار السائدة حول توسيع نطاق مستودعات Git، يتيح لنا Pando توسيع مستودعنا الموحّد دون تغيير سير عمل Git الذي يستخدمه مهندسونا ووكلاؤنا بالفعل. أتاح Pando درجة أكبر من التوازي في مستودعنا الموحّد، وحسّن موثوقية الدمج، وقلّص الوقت اللازم لنشر تغييرات الشيفرة. إلى جانب مستودعنا الموحّد، نستكشف استخدامات أخرى لـPando داخل OpenAI، منها أعباء عمل تتطلب تعاون أعداد كبيرة من الوكلاء على مستودع Git مشترك.
مع تزايد ما يُنتجه المهندسون والوكلاء من برمجيات، ستواجه فرق أكثر خارج OpenAI القيود التي واجهناها في التحكم في الشيفرة المصدرية. نستكشف حاليًا سبل مشاركة التقنية التي يقوم عليها Pando مع مستخدمينا.
يستند Pando إلى عقدين من خبرات مستخدمي مستودعات Git ومشغّليها. سنواصل تطوير Pando والعمل مع مجتمع Git مفتوح المصدر لمشاركة أي تحسينات تفيد مستخدمي Git على نطاق أوسع.


