Back home

تتطلب موثوقية وقت تشغيل Rust/Wasm التعامل مع حالة الذعر واسترداد الإجهاض

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

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

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

بعد اجتياز العرض التوضيحي، يبدأ النموذج الخاطئ للتو.

ليس من الصعب فهم الأعطال في مكالمة واحدة. يؤدي النقر فوق الزر إلى تشغيل مكالمة Wasm. إذا فشلت، سيتم الإبلاغ عن خطأ في العملية. قم بتحديث الصفحة وحاول مرة أخرى. التكلفة لا تزال تحت السيطرة.

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

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

في هذه المرحلة، لم يعد محور المناقشة هو “ما إذا كان كود Rust سيصاب بالذعر”، ولكن “ما إذا كان وقت التشغيل هذا مؤهلاً لمواصلة تقديم المكالمة التالية بعد الذعر”.

من الممكن اكتشاف الذعر، ولا يمكن للإجهاض سوى تغيير الحالات.

أهم شيء يجب فصله في Rust/Wasm هو دلالات الفشل: الذعر والإجهاض.

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

بمجرد خلط الاثنين معًا أثناء وقت التشغيل، ستحدث مشكلات بالتأكيد في المعالجة اللاحقة:

  • ابتلاع الإجهاض كاستثناء عادي، وسيستمر تجمع المثيلات في إعادة استخدام الكائنات التي فقدت مصداقيتها.
  • تعامل مع جميع حالات الذعر كما لو كان يجب تدمير المثيل، وسيتم تقليل الإنتاجية دون داع
  • يعرف مضيف JS فقط “فشل المكالمة”، ولكنه لا يعرف ما إذا كان يجب إعادة المحاولة، أو فقدان المثيل، أو قطع الجلسة الحالية

وهذا أيضًا هو الشيء الأكثر واقعية فيما يتعلق بموثوقية وقت تشغيل Wasm: يجب تحديد دلالات الاسترداد أولاً قبل تنفيذ العزل والجدولة اللاحقة.

إذا كانت طبقة الربط لا توفر دلالات الاسترداد، فستأخذ الطبقة المضيفة الحالة السيئة وتستمر في قبول العمل.

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

ما يجب إصلاحه حقًا ليس مجرد المحاولة/الالتقاط، بل إجراءات المعالجة بعد الفشل. لقد بدأ للتو المنطق المشابه لما يلي في الدخول إلى تصميم الموثوقية:

async function runWithRecovery(instance, input) {
  try {
    return await instance.exports.handle(input)
  } catch (error) {
    if (isAbort(error)) {
      pool.replace(instance.id)
    }
    throw error
  }
}

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

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

ستعمل المثيلات المشتركة على تضخيم مشكلة الاسترداد إلى مشكلة استراتيجية التجميع

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

إذا كانت تهيئة المثيل باهظة الثمن، فمن الطبيعي أن يميل النظام إلى إعادة استخدامها قدر الإمكان. ولكن بمجرد التأكد من إعادة الاستخدام، يجب ترقية عزل الأخطاء في وقت واحد:

  • الحالات التي لا يمكن تعليقها إلا في مكالمة واحدة، وسيتم التخلص منها مع المكالمة بعد الفشل
  • ما هي ذاكرات التخزين المؤقت المسموح لها بالاحتفاظ بها عبر المكالمات، وما هي ذاكرات التخزين المؤقت التي يجب إبطالها تمامًا بمجرد مواجهة الإجهاض
  • بعد استبدال المثيل، كيف سيتم ترحيل المهام الموجودة في قائمة الانتظار؟ هل ستتسبب إعادة المحاولة في آثار جانبية مرتين؟

هذه ليست إجابات سترسلها طبقة اللغة تلقائيًا. إنها تصميمات وقت التشغيل.

ولهذا السبب، إذا توقفت مناقشة موثوقية Rust/Wasm عند “هل يمكن اكتشاف الذعر؟”، فمن السهل التقليل من أهمية المشكلة. إن ما يوسع فجوة تكلفة الصيانة حقًا هو ما إذا كان مجمع المثيلات يمكنه الحفاظ على حدود ثقة واضحة بعد الفشل.

ترتبط الحدود المطبقة ارتباطًا وثيقًا بدورة الحياة

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

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

بمجرد أن يتمتع النظام بالخصائص التالية، ستتغير دلالات الاسترداد بسرعة من “عنصر التحسين” إلى “عنصر البنية التحتية”:

  • يبقى المثيل لفترة طويلة ولا يتم تدميره مع دورة حياة صفحة واحدة
  • تقوم نفس الحالة بإجراء جولات متعددة من المكالمات بشكل مستمر
  • تحتاج طبقة الاستضافة إلى استخدام التجميع مقابل وقت بدء التشغيل والإنتاجية
  • حماية حالة الجلسة وحالة ذاكرة التخزين المؤقت والمهام في قائمة الانتظار بعد الفشل

عندما تقوم الفرق المتنقلة بنقل الإمكانات الأصلية إلى الويب، فمن المرجح أن تتم مواجهة هذه الحدود. غالبًا ما كان يتعين ملء علاقة العزل التي تم إنشاؤها افتراضيًا في عملية التطبيق مرة أخرى بعد الوصول إلى حدود مضيف JS/Wasm.

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

FAQ

What to read next

Related

Continue reading