ترقية Discourse إلى Zeitwerk

يأتي Rails 6 مع وضعين للتحميل التلقائي: zeitwerk و classic. في طلب السحب هذا https://github.com/discourse/discourse/pull/8083، قمت بترقية Rails إلى الإصدار 6.0.0 باستخدام محمل الكود الكلاسيكي كمرحلة انتقالية. سيكون من المثير للاهتمام محاولة التبديل إلى Zeitwerk.

يعتبر Zeitwerk محمل كود فعال وآمن للمواضيع (thread-safe) لـ Ruby. طالما أن المشروع يتبع اتفاقيات التسمية، يمكن لـ Zeitwerk العثور على الملفات الصحيحة وتحميلها عند الطلب أو مسبقًا دون الحاجة إلى أي require أو require_dependency. علاوة على ذلك، قد يمنح ذلك دفعة أداء صغيرة للتطبيق وفقًا لهذا المقال https://weblog.rubyonrails.org/2019/2/22/zeitwerk-integration-in-rails-6-beta-2/

هناك عدة خطوات أحتاج إلى القيام بها لجعلها تعمل:

  1. تغيير اسم بعض الفئات لتتوافق مع اتفاقية تسمية Rails. على سبيل المثال، يجب أن يحدد الملف canonical_url.rb فئة CanonicalUrl بدلاً من CanonicalURL. وبالمثل، يجب أن يحدد الملف ondiff.rb فئة Onpdiff بدلاً من ONPDiff. ستكون هناك نهج بديل وهو ربط مُعرِّف مخصص (custom inflector) بالمشروع، إلا أنني أعتقد أن اتباع الاتفاقية قد يكون خيارًا أفضل - https://github.com/fxn/zeitwerk#custom-inflector

  2. وبالمثل، وفقًا للاتفاقية، يجب تغليف عمليات التحقق المخصصة (custom validations) الموجودة في مجلد validations بوحدة Validations. بالإضافة إلى ذلك، ترث بعض عمليات التحقق من EachValidator ويجب أن تكون قابلة للوصول بدون مساحة أسماء (namespace). أخطط لنقلها إلى مجلد منفصل وإضافتها إلى مسارات التحميل التلقائي.

  3. إزالة جميع require_dependency والتأكد من عمل المشروع.

  4. إزالة جميع require والتأكد من عمل Discourse.

  5. التأكد من أن جميع الإضافات (plugins) يمكنها الوصول إلى التبعيات المطلوبة. لا أعرف كيفية تحقيق ذلك بعد. أريد أولاً جعل Discourse يعمل دون أي إضافات.

بالطبع، لا تزال هناك العديد من المجهولات التي يجب حلها. سأبقيكم على اطلاع بالتقدم المحرز، ولكن إذا كنتم ترغبون في الاطلاع، فقد بدأت في تجربة ذلك هنا https://github.com/lis2/discourse/commits/zeitwerk

يرجى إخباري إذا لاحظتم أي عيوب في تنفيذ Zeitwerk أو إذا اعتقدتم أنني أغفلت شيئًا ما.

لقد حققت بعض التقدم فيما يتعلق بـ Zeitwerk، ومع ذلك، غيّرت نهجي. كان خططي الأصلي هو تغيير كل مكان لا يلتزم فيه Discourse باتفاقية تسمية الملفات الخاصة بـ Zeitwerk. بعد بعض الإصلاحات، أدركت أن هذا هو مجرد جزء بسيط من المشكلة، ولاحظت أنه إذا سرت في هذا المسار، فسيكون من الصعب قراءة طلب الدمج (pull request) ودمجه بثقة في الفرع الرئيسي (master). على سبيل المثال، يجب أن تحتوي جميع فئات الوظائف الموجودة في المجلدات العادية على مساحة اسم Regular، وكذلك Onceoff وScheduled.

قررت أن أرتد خطوة إلى الخلف قليلاً وأفكر في نهج تطوري أكثر من ثوري.

قررت أنه سيكون من الأفضل تقديم مُعرّف مخصص (custom Inflector) سيغطي جميع الملفات التي لا تتبع اتفاقية Zeitwerk. ستكون الفائدة الأكبر هي أننا سنتمكن من نشر هذا التغيير الصغير، وبمجرد أن نشعر بالرضا عن Zeitwerk ولا نواجه أي تدهور في الأداء، يمكننا البدء في إصلاح الاتفاقية ملفًا تلو الآخر في طلبات دمج صغيرة ومعقولة.

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

لا يزال طلب الدمج قيد التقدم، ومع ذلك، في هذه المرحلة، يمكنني تشغيل Discourse مع Zeitwerk والإضافات الافتراضية، وتشغيل جميع الاختبارات (specs) وتشغيل المقاييس (benchmark) دون أي مشكلة.

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

في الوقت الحالي، إذا كنت مهتمًا بالتقدم، يمكنك الاطلاع على مسودة طلب الدمج (draft PR) هنا - DEV: Upgrading Discourse to Zeitwerk by KrisKotlarek · Pull Request #8098 · discourse/discourse · GitHub

الملف الأهم هو مُعرّف Zeitwerk المخصص - DEV: Upgrading Discourse to Zeitwerk by KrisKotlarek · Pull Request #8098 · discourse/discourse · GitHub

لجعل الإضافات تعمل، كان علي إنشاء عدد قليل من طلبات السحب الصغيرة. بمجرد دمجها، أعتقد أن الاختبارات في Discourse ستمر بنجاح.

لقد قمت أيضًا بفحص الأداء على Rails 6.0.0 مع Classic autoloader و Rails 6.0.0 مع Zeitwerk.

الاختبار Classic Zeitwerk النسبة المئوية
categories-50 32 26 81.25
categories-75 37 29 78.38
categories-90 47 35 74.47
categories-99 67 49 73.13
home-50 30 29 96.67
home-75 37 31 83.78
home-90 44 40 90.91
home-99 67 52 77.61
topic-50 35 35 100.00
topic-75 36 36 100.00
topic-90 48 36 75.00
topic-99 57 58 101.75
categories_admin-50 51 48 94.12
categories_admin-75 62 50 80.65
categories_admin-90 89 66 74.16
categories_admin-99 135 101 74.81
home_admin-50 48 47 97.92
home_admin-75 58 49 84.48
home_admin-90 67 64 95.52
home_admin-99 101 81 80.20
topic_admin-50 48 48 100.00
topic_admin-75 55 49 89.09
topic_admin-90 63 65 103.17
topic_admin-99 92 69 75.00
load_rails 2617 2165 82.73
rss_kb 282428 315684 111.78
pss_kb 270491 303504 112.20

النتائج ليست متسقة دائمًا، لذا يجب التعامل معها بحذر.

إن مقدار عدم الاتساق في الوسيط هنا غريب بعض الشيء، أتساءل لماذا تتقلب النتائج بهذا القدر

من المفاجئ أن يكون للمحمل أي تأثير

جربت الأمر مرة أخرى، وهذه هي النتائج

الاختبار الكلاسيكي Zeitwerk النسبة المئوية
categories-50 25 25 100.00
categories-75 26 26 100.00
categories-90 37 33 89.19
categories-99 57 48 84.21
home-50 26 26 100.00
home-75 27 28 103.70
home-90 38 35 92.11
home-99 60 50 83.33
topic-50 27 26 96.30
topic-75 35 27 77.14
topic-90 41 33 80.49
topic-99 54 50 92.59
categories_admin-50 48 50 104.17
categories_admin-75 60 61 101.67
categories_admin-90 76 71 93.42
categories_admin-99 122 122 100.00
home_admin-50 47 46 97.87
home_admin-75 58 55 94.83
home_admin-90 66 63 95.45
home_admin-99 99 121 122.22
topic_admin-50 50 49 98.00
topic_admin-75 62 50 80.65
topic_admin-90 72 65 90.28
topic_admin-99 103 74 71.84
load_rails 2675 2216 82.84
rss_kb 279924 315240 112.62
pss_kb 267659 303026 113.21

يمكننا تجربة طريقة أخرى لإجراء اختبار الأداء. ماذا تقول عن تكرارات أكثر، شيء يستغرق ساعة؟ بالإضافة إلى ذلك، بدلاً من أخذ أفضل نتيجة، قارن المتوسط من كل تجربة. قد يعطي ذلك أرقامًا أكثر اتساقًا. ما رأيك؟

في طلبات السحب (Pull Requests) الخاصة بالإضافات، ستلاحظ أن الكثير من الإصلاحات تدور حول البحث في مساحة الاسم العالمية.

لقد غيّرت كودًا مثل:

module ::Jobs
  class TranslatorMigrateToAzurePortal < Jobs::Onceoff

إلى:

module ::Jobs
  class TranslatorMigrateToAzurePortal < ::Jobs::Onceoff

شيء واحد أزعجني في هذا الحل: لماذا كان يعمل من قبل مع Zeitwerk؟ السؤال الذي يطرح نفسه عندما يعمل شيء ما لا ينبغي أن يعمل، هو دائمًا سؤال محير :slight_smile:

أعتقد أنني وجدت إجابة محتملة في وصف محمل التلقائي الكلاسيكي (https://guides.rubyonrails.org/autoloading_and_reloading_constants_classic_mode.html#resolution-algorithms) - “إذا لم يتم العثور عليه، فإن الخوارزمية تصعد سلسلة الأسلاف الخاصة بـ cref”

Zeitwerk أكثر صرامة. بمجرد أن حاولت تحميل الكود قبل إصلاحه، اشتكى من عدم إمكانية العثور على Jobs::Jobs::Onceoff.

اقترح سام في طلب السحب https://github.com/discourse/discourse-prometheus-alert-receiver/commit/ef9c238a6fabc3a9ca238664adb3dc594dc709ee، أنه بدلاً من استخدام < ::Jobs::Onceoff، يمكننا ببساطة استخدام < Onceoff، وهو محق. لقد تحققت من أن الكود يعمل أيضًا بدون مساحة الاسم. أفكر أن استخدام :: يعني صراحة أننا نرث من فئة Discourse Core، ومع ذلك، يمكننا اتباع أي من الطريقتين.

أعتقد أنه عندما يكون الكود قريبًا جدًا من بعضه، فإن هذا يبدو ممتازًا:

module ::Jobs
  class TranslatorMigrateToAzurePortal < Onceoff

أما إذا بدأ الكود في التوزع على مسافات أكبر، فإن الوضوح التام يصبح أكثر منطقية… على سبيل المثال:

module ::Jobs
  [ 50 lines omitted]
  class TranslatorMigrateToAzurePortal < ::Jobs::Onceoff

ومع ذلك، أنا متردد في هذا الشأن، لذا أنا راضٍ بأي من الخيارين. ::Jobs::Onceoff قصير بما يكفي وواضح للغاية، لذا يمكننا الاعتماد عليه حاليًا.

أود حقًا البدء في دمج هذا. @kris.kotlarek هل أصبح هذا جاهزًا للدمج الآن؟ التوقيت ممتاز لأننا أجرينا إصدارًا تجريبيًا للتو

دعني أعيد تجميع (rebase) الفرع الرئيسي (master) الأخير في ذلك وأتأكد من أنه لا يزال يعمل، سأقوم بذلك اليوم

@سام أعتقد أننا جاهزون، لقد قمت بإعادة التأسيس مع أحدث نسخة من الفرع الرئيسي (master) وأجريت تعديلاً بسيطاً على Webauthn.
لقد تحققت من ثلاث نقاط:
تشغيل الخادم محلياً وتجربة بعض الأزرار للتأكد من عمله كما هو متوقع
تشغيل الاختبارات (specs)
تنزيل جميع الإضافات الرسمية والتأكد من نجاح اختبارات هذه الإضافات (نحتاج أولاً إلى دمج التعديلات الخاصة بالإضافات)

رائع، سأقوم بدمج هذا. لنرَ ما سيحدث!

هل يمكننا دمج إصلاحات الإضافات أيضًا؟ وإلا، إذا حاولت تشغيل Discourse مع الإضافات، فستفشل العملية

مرحبًا، تفضل بالدمج!

تم الدمج الآن! إذا كان هناك أي مطوري إضافات يواجهون صعوبات هنا، فأخبرونا بذلك في موضوع مخصص جديد!

عمل رائع يا كريس!!!