دراسة حالة واقعية حول تحسين أداء React SPA
لا يقتصر أداء الموقع الإلكتروني على وقت التحميل فقط. من الضروري توفير تجربة سريعة وسريعة الاستجابة للمستخدمين، خاصةً لتطبيقات الكمبيوتر المكتبي للإنتاجية التي يستخدمها الأشخاص كل يوم. أجرى فريق المهندسين في شركة Recruit Technologies مشروع إعادة صياغة لتحسين أحد تطبيقات الويب، وهو AirSHIFT، لتحسين أداء إدخال المستخدم. إليك كيفية تحقيق ذلك.
استجابة بطيئة وإنتاجية أقل
AirSHIFT هو تطبيق ويب لأجهزة سطح المكتب يساعد مالكي المتاجر، مثل المطاعم والمقاهي، على إدارة نوبات العمل لموظفيهم. تم إنشاء تطبيق الصفحة الواحدة باستخدام React، ويقدّم ميزات غنية للعملاء، بما في ذلك جداول شبكية مختلفة لجداول النوبات مرتبة حسب اليوم والأسبوع والشهر وغير ذلك.
عندما أضاف فريق هندسة Recruit Technologies ميزات جديدة إلى تطبيق AirSHIFT، بدأ الفريق في تلقّي المزيد من الملاحظات حول الأداء البطيء. قال المدير الهندسي لشركة AirSHIFT، "يوسوكي فوروكاوا":
في إحدى دراسات أبحاث المستخدمين، فوجئنا عندما قالت إحدى صاحبات المتاجر إنّها تغادر مقعدها لتحضير القهوة بعد النقر على زر، وذلك لقتل الوقت أثناء انتظار loading لجدول النوبات.
بعد خوض البحث، أدرك الفريق الهندسي أن العديد من المستخدمين كانوا يحاولون تحميل جداول تبديل ضخمة على أجهزة كمبيوتر منخفضة المواصفات، مثل كمبيوتر محمول بتردد 1 غيغاهيرتز Celeron M قبل 10 سنوات.
كان تطبيق AirSHIFT يحظر سلسلة التعليمات الرئيسية باستخدام نصوص برمجية باهظة الثمن، ولكن الفريق الهندسي لم يدرك مدى تكلفة النصوص البرمجية بسبب تطويرها واختبارها على أجهزة كمبيوتر ذات مواصفات غنية ومزودة باتصالات Wi-Fi سريعة.
بعد تحليل الأداء في "أدوات مطوّري البرامج في Chrome" مع تفعيل ميزة الحدّ من معدّل نقل البيانات في وحدة المعالجة المركزية والشبكة، أصبح من الواضح أنّه يجب تحسين الأداء. شكّلت AirSHIFT فريق عمل لمعالجة هذه المشكلة. في ما يلي 5 أمور ركّز عليها الفريق لجعل تطبيقه أكثر استجابةً لطلبات المستخدمين.
1. إنشاء جداول كبيرة افتراضية
تطلّب عرض جدول نوبات العمل العديد من الخطوات المكلفة: إنشاء نموذج DOM الافتراضي وعرضه على الشاشة بما يتناسب مع عدد الموظفين والخانات الزمنية. على سبيل المثال، إذا كان لدى مطعم 50 موظفًا وأراد الاطّلاع على جدول النوبات الشهري، سيكون جدولاً يتضمّن 50 (موظفًا) مضروبًا في 30 (يومًا)، ما سيؤدي إلى عرض 1,500 عنصر خلية. وهذه عملية مكلفة للغاية، خاصة للأجهزة ذات المواصفات المنخفضة. في الواقع، كانت الأمور أسوأ. من خلال البحث، تعرّف الفريق على أنّ هناك متاجر تدير 200 موظف، ما يتطلّب حوالي 6,000 عنصر خلية في جدول شهري واحد.
لتقليل تكلفة هذه العملية، فعّلت AirSHIFT جدول النوبات الافتراضية. لا يثبِّت التطبيق الآن سوى المكوّنات داخل إطار العرض ويلغي تثبيت المكوّنات خارج الشاشة.
في هذه الحالة، استخدمت AirSHIFT react-virtualized لأنّ هناك متطلبات حول تفعيل جداول الشبكة ثنائية الأبعاد المعقدة. ويبحث الفريق أيضًا عن طرق لتحويل عملية التنفيذ لاستخدام react-window الخفيفة في المستقبل.
النتائج
أدّى استخدام تقنية المحاكاة الافتراضية للجدول وحدها إلى تقليل وقت تحليل النصوص البرمجية بمقدار 6 ثوانٍ (في بيئة Macbook Pro التي تبطئ وحدة المعالجة المركزية (CPU) بمقدار 4 أضعاف وشبكة الجيل الثالث السريعة). كان هذا هو التحسين الأكثر تأثيرًا في الأداء في مشروع إعادة التشكيل.
2. التدقيق باستخدام User Timing API
بعد ذلك، أعاد فريق AirSHIFT صياغة النصوص البرمجية التي يتم تشغيلها استنادًا إلى إدخال المستخدم. يتيح مخطّط المخطّطات البيانية في أدوات مطوّري البرامج في Chrome تحليل ما يحدث فعليًا في سلسلة المهام الرئيسية. لكنّ فريق AirSHIFT وجد أنّه من الأسهل تحليل نشاط التطبيق استنادًا إلى دورة حياة React.
يقدّم React 16 تتبُّع الأداء من خلال User Timing API، والذي يمكنك عرضه بشكل مرئي من قسم "المُدد الزمنية" في "أدوات مطوّري البرامج في Chrome". استخدمت AirSHIFT قسم "التوقيتات" للعثور على منطق غير ضروري يعمل في أحداث مراحل نشاط React.
النتائج
اكتشف فريق AirSHIFT أنّه قبل كل عملية انتقال إلى مسار، كان يتم تنفيذ عملية React Tree Reconciliation غير ضرورية. وهذا يعني أنّ React كان يعدّل جدول التبديل بدون داعٍ قبل عمليات التنقّل. كان تحديث حالة Redux غير الضروري يتسبب في حدوث هذه المشكلة. وقد أدّى إصلاح هذا الخطأ إلى توفير حوالي 750 ملي ثانية من وقت التشغيل. أجرت شركة AirSHIFT تحسينات دقيقة أخرى أيضًا أدّت في النهاية إلى تقليل إجمالي وقت التنفيذ بمقدار ثانية واحدة.
3- تحميل المكوّنات بشكل كسول ونقل المنطق المكلف إلى عمال الويب
يتضمّن AirSHIFT تطبيق محادثة مُدمَجًا. يتواصل العديد من مالكي المتاجر مع موظفيهم من خلال المحادثة أثناء الاطّلاع على جدول النوبات، ما يعني أنّ المستخدم قد يكون يكتب رسالة أثناء تحميل الجدول. إذا كانت سلسلة المحادثات الرئيسية مشغولة بنصوص برمجية تعرض الجدول، قد يكون إدخال المستخدم متقطّعًا.
لتحسين هذه التجربة، تستخدم AirSHIFT الآن React.lazy وSuspense لعرض العناصر النائبة لمحتويات الجدول مع التحميل الكسول للمكوّنات الفعلية.
نقل فريق AirSHIFT أيضًا بعض الإجراءات الباهظة الثمن للأنشطة التجارية ضمن المكوّنات التي يتم تحميلها بطريقة كسولة إلى العاملين على الويب. وقد أدّى ذلك إلى حلّ مشكلة التقطُّع في البيانات التي يُدخلها المستخدم من خلال تحرير سلسلة المهام الرئيسية كي تتمكّن من التركيز على الاستجابة لبيانات المستخدم.
يواجه المطوّرون عادةً تعقيدًا في استخدام "العمال"، ولكن هذه المرة تمكّنت شركة Comlink من إنجاز المهمة الشاقة نيابةً عنهم. في ما يلي الرمز البرمجي الاصطناعي لكيفية استخدام AirSHIFT للعمال في إحدى العمليات الأكثر تكلفة التي أجروها: احتساب إجمالي تكاليف العمالة.
في App.js، استخدِم React.lazy وSuspense لعرض المحتوى الاحتياطي أثناء التحميل
/** App.js */
import React, { lazy, Suspense } from 'react'
// Lazily loading the Cost component with React.lazy
const Hello = lazy(() => import('./Cost'))
const Loading = () => (
<div>Some fallback content to show while loading</div>
)
// Showing the fallback content while loading the Cost component by Suspense
export default function App({ userInfo }) {
return (
<div>
<Suspense fallback={<Loading />}>
<Cost />
</Suspense>
</div>
)
}
في مكوّن التكلفة، استخدِم comlink لتنفيذ منطق الحساب
/** Cost.js */
import React from 'react';
import { proxy } from 'comlink';
// import the workerlized calc function with comlink
const WorkerlizedCostCalc = proxy(new Worker('./WorkerlizedCostCalc.js'));
export default async function Cost({ userInfo }) {
// execute the calculation in the worker
const instance = await new WorkerlizedCostCalc();
const cost = await instance.calc(userInfo);
return <p>{cost}</p>;
}
تنفيذ منطق العمليات الحسابية الذي يتم تشغيله في العامل وعرضه باستخدام أمر comlink
// WorkerlizedCostCalc.js
import { expose } from 'comlink'
import { someExpensiveCalculation } from './CostCalc.js'
// Expose the new workerlized calc function with comlink
expose({
calc(userInfo) {
// run existing (expensive) function in the worker
return someExpensiveCalculation(userInfo);
}
}, self);
النتائج
على الرغم من العدد المحدود من عمليات المنطق التي تم تحويلها إلى سلسلة مهام فرعية كتجربة، نقلت AirSHIFT حوالي 100 ملي ثانية من رمز JavaScript إلى السلسلة الفرعية (تم اختبار ذلك من خلال خفض سرعة وحدة المعالجة المركزية بمقدار 4 مرات).
تبحث شركة AirSHIFT حاليًا في إمكانية تحميل المكوّنات الأخرى بشكلٍ بطيء وإلغاء تحميل المزيد من المنطق إلى عمال الويب للحدّ من الارتباك بشكلٍ أكبر.
4. ضبط ميزانية الأداء
وبعد تنفيذ كل هذه التحسينات، كان من الضروري التأكد من بقاء التطبيق بأداء جيد بمرور الوقت. تستخدِم AirSHIFT الآن bundlesize لتجنُّب تجاوز حجم ملفَي JavaScript وCSS الحاليَين. بصرف النظر عن وضع هذه الميزانيات الأساسية، أنشأت الشركة لوحة بيانات لعرض شرائح مئوية مختلفة لوقت تحميل جدول التبديل بهدف التحقّق ممّا إذا كان التطبيق ذا أداء جيد حتى في الحالات غير المثالية.
- يتم الآن قياس وقت اكتمال النص البرمجي لكل حدث في Redux
- يتم جمع بيانات الأداء في Elasticsearch.
- يتمّ عرض أداء الشريحة المئوية العاشرة والخامسة والعشرين والخمسين والسابعة والخمسين لكلّ حدث باستخدام Kibana.
تتتبّع AirSHIFT الآن حدث تحميل جدول النوبات للتأكّد من اكتماله في 3 ثوانٍ لمستخدمي الربع المئوي الخمسين. هذه ميزانية غير مفروضة في الوقت الحالي، ولكنّهم يفكرون في إرسال إشعارات تلقائية عبر Elasticsearch عند تجاوز ميزانيتهم.
النتائج
من الرسم البياني أعلاه، يمكنك معرفة أنّ AirSHIFT تحقّق الآن في الغالب الحدّ الأقصى لمدة 3 ثوانٍ لمستخدمي الشريحة المئوية التسعون، كما تحمّل جدول التحويلات خلال ثانية واحدة لمستخدمي الشريحة المئوية الخامسة والعشرين. من خلال جمع بيانات أداء RUM من حالات وأجهزة مختلفة، يمكن لأداة AirSHIFT الآن التحقّق ممّا إذا كان إصدار ميزة جديدة يؤثِّر فعليًا في أداء التطبيق أم لا.
5- فعاليات هاكاثون الأداء
على الرغم من أنّ كلّ هذه الجهود المبذولة لتحسين الأداء كانت مهمّة ومؤثرة، ليس من السهل دائمًا إقناع فِرق الهندسة والأعمال بمنح الأولوية للتطوير المتعلّق بالجانب غير الوظيفي. يتمثّل جزء من التحدي في عدم إمكانية التخطيط لبعض عمليات تحسين الأداء هذه. وتتطلّب هذه الأفكار إجراء تجارب واعتماد أسلوب التجربة والخطأ.
تُجري شركة AirSHIFT حاليًا ورش عمل داخلية لتحسين الأداء تستمر لمدة يوم واحد للسماح للمهندسين بالتركيز فقط على العمل المرتبط بالأداء. وفي مسابقات الهاكاثون هذه، يزيلون جميع القيود ويحترمون إبداع المهندسين، مما يعني أن أي تنفيذ يساهم في السرعة يستحق الاهتمام. لتسريع عملية الهاكاثون، قسَّمت AirSHIFT المجموعة إلى فِرق صغيرة يتنافس كل فريق لمعرفة من يمكنه الحصول على أكبر نتيجة لتحسين نتيجة Lighthouse. تصبح الفرق تنافسية جدًا. 🔥
النتائج
يحقّق نهج "هاكاثون" نجاحًا جيدًا لهم.
- يمكن رصد نقاط الاختناق في الأداء بسهولة من خلال تجربة طرق متعددة أثناء هاكاثون وقياس كل منها باستخدام Lighthouse.
- بعد انتهاء الفعالية، من السهل إقناع الفريق بتحديد التحسينات التي يجب أن يُعطى لها الأولوية في الإصدار العلني.
- وهي أيضًا طريقة فعّالة للترويج لأهمية السرعة. يمكن لكل مشارك فهم الارتباط بين طريقة الترميز وتأثيرها في الأداء.
كان من الآثار الجانبية الجيدة أن العديد من الفرق الهندسية الأخرى داخل Recruit مهتمة بهذا النهج العملي، ويعمل فريق AirSHIFT الآن على تسهيل فعاليات هاكاثون السرعة المتعددة داخل الشركة.
ملخّص
لم تكن هذه الرحلة هي الأسهل على AirSHIFT، ولكنّها بالتأكيد كانت مثمرة. الآن، تحمّل AirSHIFT جدول النوبات خلال 1.5 ثانية في المتوسط، ما يمثّل تحسُّنًا بمقدار 6 مرّات مقارنةً بأداء الشركة قبل المشروع.
بعد إطلاق تحسينات الأداء، قال أحد المستخدمين:
نشكرك على سرعة تحميل جدول النوبات. أصبح من الأسهل الآن تنظيم العمل في نوبات.