تحديات قواعد الكتابة

تقدّم هذه الصفحة نظرة عامة عالية المستوى حول المشاكل والتحديات المحدّدة لكتابة قواعد Bazel فعّالة.

ملخّص المتطلبات

  • الافتراض: السعي إلى تصحيح الأداء ووقت الاستجابة وسهولة الاستخدام &وقت الاستجابة
  • افتراض: مستودعات كبيرة الحجم
  • افتراض: BUILD تشبه لغة الوصف
  • في المرحلة السابقة: تم فصل الفصل بين التحميل والتحليل والتنفيذ قديمًا، ولكنه لا يزال يؤثر في واجهة برمجة التطبيقات
  • الجوهر: التنفيذ الصعب والتخزين المؤقت صعب
  • الجوهرة: يتطلب استخدام معلومات التغيير للبنادات المتزايدة والسريعة أنماط ترميز غير معتادة
  • الجوهرة: من الصعب تجنّب استهلاك الوقت التربيعي والذاكرة

الفرضيّات

في ما يلي بعض الافتراضات المتعلّقة بنظام التصميم، مثل الحاجة إلى تصحيحها وسهولة استخدامها ومستوى سرعة المعالجة ومستودعات البيانات الكبيرة الحجم. تتناول الأقسام التالية هذه الافتراضات وتعرض إرشادات لضمان كتابة القواعد بطريقة فعالة.

تهدف إلى التحقّق من صحة المعلومات ووقت الاستجابة وسهولة الاستخدام &وقت الاستجابة

نفترض أنّ نظام التصميم يجب أولاً أن يكون صحيحًا للغاية من حيث الإصدارات. بالنسبة إلى شجرة المصدر المحددة، يجب أن تكون مخرجات الإصدار نفسه متطابقة دائمًا، بغض النظر عن شكل شجرة النتائج. في التقريب الأول، يعني ذلك أنّ Bazel بحاجة إلى معرفة كل إدخال يتم إدخاله في خطوة إصدار معيّنة، بحيث يمكنه إعادة تشغيل تلك الخطوة في حال تغيّر أي من مصادر الإدخال. هناك قيود على الطريقة التي يمكن بها لتطبيق Bazel الصحيح، حيث إنه يتسرّب بعض المعلومات، مثل تاريخ / وقت الإصدار، كما يتجاهل أنواعًا معيّنة من التغييرات، مثل التغييرات على سمات الملف. يساعد وضع الحماية في ضمان صحة المعلومات من خلال منع عمليات القراءة من الوصول إلى ملفات الإدخال غير المعلَنة. بالإضافة إلى الحدود الجوهرية للنظام، هناك بعض المشاكل المعروفة المتعلقة بالصحّة، معظمها تتعلق بقواعد مجموعة الملفات أو قواعد C++ ، وهما من المشاكل الصعبة. نبذل جهودًا طويلة المدى لإصلاح هذه المشاكل.

والهدف الثاني من نظام التشغيل هو تحقيق سرعة عالية في معالجة البيانات، فنحن نسعى دائمًا إلى تجاوز حدود ما يمكن إنجازه ضمن تخصيص الجهاز الحالي لخدمة تنفيذ عن بُعد. إذا أصبح التحميل الزائد عن بُعد خدمة زائد، لا يمكن لأحد إنجاز العمل.

سهولة الاستخدام هي الخطوة التالية. ومن خلال تعدد الأساليب الصحيحة ذات البصمة نفسها (أو ما شابه) في خدمة التنفيذ عن بُعد، نختار المنهج الذي يسهل استخدامه.

يشير وقت الاستجابة إلى الوقت المستغرَق منذ بدء الإصدار إلى الحصول على النتيجة المقصودة، سواء كان سجلّ اختبار من عملية اجتياز الاختبار أو تعذّر اجتيازه، أو رسالة خطأ تفيد بأنّ ملف BUILD يتضمّن خطأً إملائيًا.

وتجدر الإشارة إلى أن هذه الأهداف غالبًا ما تتداخل، حيث إن وقت الاستجابة يمثّل إحدى وظائف معالجة بيانات خدمة التنفيذ عن بُعد، حيث يكون من الملائم ضمان سهولة الاستخدام.

مستودعات كبيرة الحجم

يجب أن يعمل نظام التصميم على نطاقٍ كبير من المستودعات الكبيرة، حيث يعني ذلك أنه لا يمكن ملاءمته على محرك أقراص ثابتة واحد، لذا لا يمكن إجراء عملية الدفع الكاملة على جميع أجهزة مطوّري البرامج تقريبًا. سيحتاج الإصدار المتوسط الحجم إلى قراءة عشرات الآلاف من ملفات BUILD وتحليلها، وتقييم مئات الآلاف من الكرات. من الممكن نظريًا قراءة جميع الملفات البالغ عددها BUILD على جهاز واحد، إلا أننا لم نتمكن حتى الآن من قراءة هذه الملفات خلال فترة معقولة من الوقت والذاكرة. في هذه الحالة، من المهم تحميل ملفات BUILD وتحليلها بشكل مستقل.

لغة وصف تشبه BUILD

في هذا السياق، نفترض أنّ هناك لغة ضبط تشبه إلى حدٍّ كبير ملفات BUILD في بيان المكتبة والقواعد الثنائية وعمليات الترابط بينها. يمكن قراءة ملفات BUILD وتحليلها بشكل مستقل، وتجنّب حتى البحث في الملفات المصدر متى أمكننا ذلك (باستثناء توفّر هذه الملفات).

تاريخي

هناك اختلافات بين إصدارات Bazel التي تتسبب في تحديات، وبعضها موضّح في الأقسام التالية.

الفصل التام بين التحميل والتحليل والتنفيذ قديم ولكنه لا يزال يؤثر في واجهة برمجة التطبيقات

من الناحية الفنية، يكفي أن تتعرّف القاعدة على ملفات الإدخال والإخراج الخاصة بإجراء ما قبل إرسال الإجراء إلى التنفيذ عن بُعد مباشرةً. ومع ذلك، كان لقاعدة رموز Bazel الأصلية فصل كامل لحزم التحميل، ثم تحليل القواعد باستخدام الإعدادات (علامات سطر الأوامر بشكل أساسي)، وبعدها تنفيذ أي إجراءات فقط. ولا يزال هذا الفارق جزءًا من واجهة برمجة التطبيقات للقواعد اليوم، على الرغم من أن جوهر Bazel لم يعد يتطلبه (يُرجى الاطّلاع على المزيد من التفاصيل أدناه).

وهذا يعني أن واجهة برمجة التطبيقات للقواعد تتطلب وصفًا معلنًا للواجهة التابعة للقاعدة (السمات التي تحتوي عليها وأنواع السمات). وهناك بعض الاستثناءات التي تسمح فيها واجهة برمجة التطبيقات بتشغيل الرمز المخصّص أثناء مرحلة التحميل لاحتساب الأسماء الضمنية لملفات الإخراج والقيم الضمنية للسمات. على سبيل المثال، تنشئ قاعدة java_library باسم 'foo' ضمنيًا مخرجات باسم 'libfoo.jar' والتي يمكن الإشارة إليها من قواعد أخرى في الرسم البياني للإصدار.

بالإضافة إلى ذلك، لا يمكن لتحليل القاعدة قراءة أي ملفات مصدر أو فحص مخرجات إجراء، وبدلاً من ذلك، تحتاج إلى إنشاء رسم بياني ثنائي جزئي موجَّه لخطوات الإصدار وأسماء ملفات الإخراج التي يتم تحديدها من خلال القاعدة نفسها وتبعياتها فقط.

الجوهرية

هناك بعض الخصائص الجوهرية التي تجعل من كتابة القواعد أمرًا صعبًا، وبعض القواعد الأكثر شيوعًا موضحة في الأقسام التالية.

من الصعب تنفيذ عملية التخزين عن بُعد والتخزين المؤقت

ويساهم التنفيذ عن بُعد والتخزين المؤقت في تحسين أوقات الإنشاء في مستودعات كبيرة تقريبًا بمقدار اثنين من حيث الحجم مقارنةً بتشغيل الإصدار على جهاز واحد. ومع ذلك، فإن النطاق الذي يجب تنفيذه بشكل مذهل هو: خدمة Google عن بُعد مصمّمة للتعامل مع عدد كبير من الطلبات في الثانية، ويتجنب البروتوكول رحلات ذهاب وعودة غير ضرورية بالإضافة إلى العمل غير الضروري من جهة الخدمة.

في الوقت الحالي، يتطلب البروتوكول أن يتعرّف نظام الإصدار على جميع البيانات التي يتم إدخالها مسبقًا لإجراء ما تم تقديمه مسبقًا، وبعد ذلك يحتسب نظام الإصدار ملفًا مرجعيًا لإجراء فريد ويطلب من أداة الجدولة قياس نتيجة ذاكرة التخزين المؤقت. وفي حال العثور على نتيجة لذاكرة التخزين المؤقت، ترد أداة الجدولة مع ملخصات ملفات الناتج، وستتم معالجة الملفات نفسها عن طريق الملخّص لاحقًا. ومع ذلك، يفرض ذلك قيودًا على قواعد Bazel، والتي تحتاج إلى الإعلان عن جميع ملفات الإدخال مسبقًا.

يتطلب استخدام معلومات التغيير للإصدارات التدريجية الصحيحة والسريعة أنماط ترميز غير معتادة

وأوضّحنا أنّه من أجل تصحيح الأخطاء، يحتاج Bazel إلى معرفة كل ملفات الإدخال التي تتضمّن خطوة إصدار لمعرفة ما إذا كانت خطوة الإصدار هذه لا تزال محدّثة أم لا. وينطبق الأمر نفسه على تحميل الحزمة وتحليل القواعد، فقد صممنا Skyframe للتعامل مع هذا الأمر بشكل عام. Skyframe هي مكتبة رسومات بيانية وإطار تقييم يستند إلى عقدة هدف (مثل 'build //foo باستخدام هذه الخيارات')، ويقسّمها إلى أجزاءها الأساسية التي يتم تقييمها بعد ذلك ودمجها لتحقيق هذه النتيجة. وكجزء من هذه العملية، يقرأ Skyframe الحزم ويحلل القواعد وينفّذ الإجراءات.

في كل عُقدة، يتتبّع تطبيق Skyframe العُقد التي يتم استخدامها لأي عُقدة معيّنة يتم استخدامها لاحتساب الناتج الخاص بها، بدءًا من عُقدة الهدف وصولاً إلى ملفات الإدخال (التي تُعتبر أيضًا عُقد Skyframe). إنّ تمثيل هذا الرسم البياني بشكل واضح في الذاكرة يسمح لنظام التصميم بتحديد العُقد التي تتأثر بتغيير معيّن في ملف الإدخال (بما في ذلك إنشاء ملف إدخال أو حذفه)، ما يؤدي إلى الحدّ الأدنى من الجهود المبذولة لإعادة العرض التدرّجي لجهاز العرض إلى حالته المقصودة.

وفي إطار ذلك، تُنفِّذ كل عُقدة عملية اكتشاف تبعية. يمكن أن تشير كل عُقدة إلى تبعيات، ثم استخدام محتوى تلك التبعيات للإعلان عن المزيد من التبعيات. من حيث مبدأ، يتم ربط هذه العلامة التجارية بنموذج سلسلة عُقد. ومع ذلك، تحتوي الإصدارات متوسطة الحجم على مئات الآلاف من عُقَد Skyframe التي لا يمكن تنفيذها بسهولة باستخدام تكنولوجيا Java الحالية (ولأسباب تاريخية، لا يمكننا حاليًا استخدام لغة Java، ما يؤدي إلى استخدام سلاسل محادثات بسيطة بدون أي تسلسلات).

وبدلاً من ذلك، يستخدم Bazel مجموعة سلاسل محادثات ثابتة الحجم. ويعني ذلك أنّه إذا أعلنت العُقدة عن اعتمادية غير متاحة بعد، قد نضطر إلى إلغاء هذا التقييم وإعادة تشغيله (ربما في سلسلة محادثات أخرى)، عندما تكون الاعتمادية متاحة. وهذا بدوره يعني أن العُقد يجب ألا تفعل ذلك بشكل مفرط، لأن العقدة التي تعلن عن تبعيات N بشكل تسلسلي يمكن إعادة تشغيلها N مرة، بتكلفة O(N^2). وبدلاً من ذلك، نهدف إلى التعريف بشكل مسبق مُجمَّع للاعتماديات التي تتطلّب أحيانًا إعادة تنظيم الرمز أو حتى تقسيم عقدة إلى عُقد متعددة للحدّ من عدد عمليات إعادة التشغيل.

يُرجى العِلم أنّ هذه التقنية غير متوفّرة حاليًا في واجهة برمجة التطبيقات للقواعد، ولكنّه لا يزال يتم تحديد واجهة برمجة التطبيقات للقواعد باستخدام المفاهيم القديمة لتحميل البيانات وتحليلها وتنفيذها. ومع ذلك، هناك تقييد أساسي هو أن جميع عمليات الوصول إلى العُقد الأخرى يجب أن تمر بإطار العمل حتى تتمكّن من تتبّع المهام التابعة. بغض النظر عن اللغة التي تم بها تنفيذ نظام الإصدار أو التي كُتبت بها القواعد (ليس من الضروري أن تكون هي نفسها) يجب ألا يستخدم مؤلّفو القواعد المكتبات أو الأنماط العادية التي تتجاوز إطار السماء. وبالنسبة إلى جافا، هذا يعني تجنب java.io.File بالإضافة إلى أي شكل من أشكال الانعكاس، وأي مكتبة تنفِّذ أيًا منهما. تجدر الإشارة إلى أنّ المكتبات التي توفّر إمكانية الاعتماد على هذه الواجهات المنخفضة المستوى لا تزال بحاجة إلى الإعداد بشكل صحيح لمنصة Skysky.

ويساعد ذلك بشدّة على تجنّب عرض مؤلّفي القواعد لوقت تشغيل كامل للغة في المقام الأول. إنّ المخاطر التي تتعلّق بالاستخدام غير المقصود لواجهات برمجة التطبيقات هذه هي كبيرة جدًا، فكانت العديد من أخطاء Bazel في الماضي ناتجة عن قواعد تستخدم واجهات برمجة تطبيقات غير آمنة، على الرغم من أنّ القواعد مكتوبة بواسطة فريق Bazel أو خبراء نطاقات آخرين.

من الصعب تجنّب استهلاك مقياس التربيع المربّع واستهلاك الذاكرة.

وما يجعل الأمور أسوأ، بغض النظر عن المتطلبات التي تفرضها ميزة Skyframe، والقيود المفروضة على استخدام لغة Java السابقة، وعصر واجهة برمجة التطبيقات للقواعد، فإنّ إدراج وقت تربيعي أو استهلاك للذاكرة عن طريق الخطأ هو مشكلة أساسية في أي نظام إنشاء يستند إلى المكتبة والقواعد الثنائية. هناك نمطَان شائعَان جدًا يعرّفان الاستهلاك الرُبعي للذاكرة (وبالتالي الاستهلاك التربيعي).

  1. سلاسل قواعد المكتبة: مراعاة حالة سلسلة قواعد المكتبة (أ) تعتمد على (ب) وتعتمد على (ج) وما إلى ذلك. بعد ذلك، نريد احتساب بعض الخصائص من خلال الإغلاق العام لهذه القواعد، مثل مسار فئة وقت تشغيل Java، أو أمر رابط C++ لكل مكتبة. باختصار، قد نستخدم تنفيذ القائمة العادية، إلا أنّ ذلك يوفّر استهلاكًا تربيعيًا للذاكرة: تحتوي المكتبة الأولى على إدخال واحد في مسار الصف والثاني والثالث والثالث وما إلى ذلك، إجمالاً، إدخال 1+2+3...+N = O(N^2).

  2. القواعد الثنائية استنادًا إلى قواعد المكتبة نفسها - فكّر في حالة مجموعة برامج ثنائية تعتمد على قواعد المكتبة نفسها، مثل ما إذا كان لديك عدد من قواعد الاختبار التي تختبر رمز المكتبة نفسه. لنفترض أن قواعد N هي، نصف القواعد هي قواعد ثنائية، وقواعد المكتبة الأخرى. والآن، فكّر في أنّ كل برنامج ثنائي ينشئ نسخة من بعض المواقع الإلكترونية التي تم احتسابها من خلال الإغلاق العام لقواعد المكتبة، مثل مسار فئة وقت تشغيل Java أو سطر الأوامر في C++. على سبيل المثال، يمكن أن يوسّع تمثيل سلسلة سطر الأوامر لإجراء رابط C++. N/2 نُسخ من عناصر N/2 هي ذاكرة O(N^2).

فئات المجموعات المخصّصة لتجنّب تعقيدات الصور التربيعية

إنّ تطبيق Bazel متأثر بشكل كبير بأيّ من هذين السيناريوَين، لذلك قدّمنا مجموعة من فئات التجميع المخصّصة التي تضغط بشكل فعّال على المعلومات في الذاكرة من خلال تجنّب نسخ البيانات في كل خطوة. وضعت جميع بُنى البيانات هذه تقريبًا دلاليًا، لذلك أطلقنا على هذه العملية اسم depset (والمعروفة أيضًا باسم NestedSet في التنفيذ الداخلي). إنّ معظم التغييرات التي أدّت إلى تقليل معدّل استهلاك الذاكرة لدى شركة Bazel& .

للأسف، لا يؤدي استخدام المجموعات الفرعية إلى حل جميع المشاكل تلقائيًا، خصوصًا حتى عند تكرار تكرار التجربة في كل قاعدة، ما يؤدي إلى إعادة استخدام الاستهلاك التربيعي للوقت. داخليًا، يتضمّن برنامج NestedSets أيضًا بعض الطرق المساعدة لتسهيل إمكانية التشغيل التفاعلي مع فئات التجميع العادية، وللأسف، يؤدي إرسال NestedSet بدون قصد إلى إحدى هذه الطرق إلى نسخ السلوك وإعادة استخدام استهلاك الذاكرة التربيعية.