חמש דרכים שבהן AirSHIFT שיפרה את הביצועים בזמן הריצה של אפליקציית React

מקרה לדוגמה בעולם האמיתי של אופטימיזציה של ביצועים ב-React SPA.

Kento Tsuji
Kento Tsuji
Satoshi Arai
Satoshi Arai
Yusuke Utsunomiya
Yusuke Utsunomiya
Yosuke Furukawa
Yosuke Furukawa

ביצועי האתר לא קשורים רק לזמן הטעינה. חשוב מאוד לספק למשתמשים חוויה מהירה ורספונסיבית, במיוחד באפליקציות למחשב שאנשים משתמשים בהן כל יום. צוות המהנדסים ב-Recruit Technologies השלים פרויקט של טיוב קוד כדי לשפר את אחת מאפליקציות האינטרנט שלהם, AirSHIFT, ולשפר את הביצועים של קלט המשתמש. כך הם עשו זאת.

תגובה איטית, ירידה בפרודוקטיביות

AirSHIFT היא אפליקציית אינטרנט למחשב שבעזרתה בעלי עסקים, כמו מסעדות ובתי קפה, יכולים לנהל את משמרות העבודה של צוות העובדים שלהם. האפליקציה הזו, שנוצרה באמצעות React, היא אפליקציה עם דף יחיד שמספקת תכונות לקוח עשירות, כולל טבלאות שונות של לוחות זמנים של משמרות שמאורגנות לפי יום, שבוע, חודש ועוד.

צילום מסך של אפליקציית האינטרנט של AirSHIFT.

כשצוות המהנדסים של Recruit Technologies הוסיף תכונות חדשות לאפליקציית AirSHIFT, הם התחילו לקבל יותר משוב על ביצועים איטיים. מנהל ההנדסה של AirSHIFT, יוסוקה פורוקאווה, אמר:

במחקר שערכנו בנושא התנהגות משתמשים, נדהמנו כשאחת מבעלי החנויות אמרה שהיא תעזוב את הכיסא כדי להכין קפה אחרי שתלחץ על לחצן, רק כדי להעביר את הזמן בזמן ההמתנה לטבלת המשמרות.

אחרי שבחנו את המחקר, צוות המהנדסים הבין שרבים מהמשתמשים מנסים לטעון טבלאות שינוי ענקיות במחשבים עם מפרט נמוך, כמו מחשב נייד עם מעבד Celeron M של 1GHz מלפני 10 שנים.

ספינר אינסופי במכשירים ברמה נמוכה.

אפליקציית AirSHIFT חסמה את השרשור הראשי באמצעות סקריפטים יקרים, אבל צוות המהנדסים לא הבין כמה הסקריפטים יקרים כי הם פיתחו ובדקו אותם במחשבים עם מפרט עשיר וחיבורי Wi-Fi מהירים.

תרשים שבו מוצגת הפעילות של האפליקציה בסביבת זמן הריצה.
כשטענו את טבלת המשמרות, כ-80% מזמן הטעינה הוקצו להרצת סקריפטים.

אחרי יצירת פרופיל הביצועים שלהם בכלים למפתחים ב-Chrome עם הפעלת הצמצום של המעבד והרשת, התברר שהם זקוקים לאופטימיזציה של הביצועים. AirSHIFT הקימה צוות משימות כדי לטפל בבעיה הזו. ריכזנו כאן 5 דברים שהם התמקדו בהם כדי לשפר את התגובה של האפליקציה שלהם לקלט של המשתמשים.

1. וירטואליזציה של טבלאות גדולות

כדי להציג את טבלת המשמרות נדרשו כמה שלבים יקרים: בניית ה-DOM הווירטואלי והצגתו במסך בהתאם למספר חברי הצוות ולתורים. לדוגמה, אם במסעדה יש 50 עובדים ורוצים לבדוק את לוח המשמרות החודשי שלהם, זה יהיה טבלה של 50 (עובדים) כפול 30 (ימים), וכתוצאה מכך יידרשו 1,500 רכיבי תאים לעיבוד. זו פעולה יקרה מאוד, במיוחד במכשירים עם מפרט נמוך. בפועל, המצב היה גרוע יותר. מהמחקר הם הבינו שיש חנויות שמנהלות 200 עובדים, ושהן צריכות כ-6,000 רכיבי תאים בטבלה חודשית אחת.

כדי לצמצם את העלות של הפעולה הזו, AirSHIFT הווירטואליזציה את טבלת המשמרות. האפליקציה מעכשיו מחברת רק את הרכיבים שנמצאים באזור התצוגה ומנתקת את הרכיבים שנמצאים מחוץ למסך.

צילום מסך עם הערות שמדגים ש-AirSHIFT שימש לעיבוד תוכן מחוץ לאזור התצוגה.
לפני: עיבוד הגרפי של כל התאים בטבלת המעבר.
צילום מסך עם הערות שמראה ש-AirSHIFT מעבד עכשיו רק תוכן שגלוי באזור התצוגה.
אחרי: רק עיבוד התאים בתוך אזור התצוגה.

במקרה הזה, ב-AirSHIFT השתמשו ב-react-virtualized כי היו דרישות בנוגע להפעלת טבלאות רשת דו-מימדיות מורכבות. הם גם בודקים דרכים להמיר את ההטמעה כך שתשתמש ב-react-window הקל בעתיד.

תוצאות

הווירטואליזציה של הטבלה לבדה צמצמה את זמן הכתיבה של הסקריפט ב-6 שניות (בסביבה של Macbook Pro עם האטה פי 4 במהירות התגובה של המעבד ועם 3G מהיר עם הגבלת קצב העברת נתונים). זה היה שיפור הביצועים המשמעותי ביותר בפרויקט הרפורמה.

צילום מסך עם הערות של הקלטה בחלונית 'ביצועים' בכלי הפיתוח ל-Chrome.
לפני כן: כ-10 שניות של סקריפט אחרי קלט המשתמש.
צילום מסך נוסף עם הערות של הקלטה בחלונית 'ביצועים' בכלי הפיתוח ל-Chrome.
אחרי: 4 שניות של סקריפט אחרי קלט של משתמש.

2. ביקורת באמצעות User Timing API

לאחר מכן, צוות AirSHIFT ביצע עיבוד מחדש של הסקריפטים שפועלים על קלט של משתמשים. תרשים הלהבות בכלים למפתחים של Chrome מאפשר לנתח את מה שקורה בפועל בשרשור הראשי. אבל לצוות AirSHIFT היה קל יותר לנתח את פעילות האפליקציה על סמך מחזור החיים של React.

ב-React 16 אפשר לראות את מעקב הביצועים באמצעות User Timing API, וניתן להציג אותו בקטע Timings בכלי הפיתוח ל-Chrome. ב-AirSHIFT השתמשו בקטע Timings כדי למצוא לוגיקה מיותרת שפועלת באירועים של מחזור החיים של React.

הקטע 'זמנים' בחלונית 'ביצועים' בכלי הפיתוח ל-Chrome.
אירועי תזמון משתמש של React.

תוצאות

צוות AirSHIFT גילה שReact Tree Reconciliation מתבצע באופן מיותר ממש לפני כל ניווט בנתיב. כלומר, React עדכנה את טבלת ההזזות ללא צורך לפני הניווטים. הבעיה נגרמה כתוצאה מעדכון מיותר של מצב Redux. התיקון חוסך כ-750 אלפיות השנייה בזמן הכתיבה של הסקריפט. ב-AirSHIFT בוצעו גם אופטימיזציות מיקרו אחרות, שבסופו של דבר הובילו לקיצור של שנייה אחת בסך הכול בזמן הכתיבה של הסקריפט.

3. טעינה מדורגת של רכיבים והעברת לוגיקה יקרה ל-web workers

ל-AirSHIFT יש אפליקציית צ'אט מובנית. הרבה בעלי חנויות מתכתבים עם הצוות שלהם בצ'אט בזמן שהם צופים בטבלת המשמרות, כך שיכול להיות שמשתמש יתקליד הודעה בזמן שהטבלה נטענת. אם השרשור הראשי תפוס בסקריפטים שמרינדרים את הטבלה, יכול להיות שהקלדה של המשתמש תהיה לא חלקה.

כדי לשפר את החוויה הזו, AirSHIFT משתמש עכשיו ב-React.lazy וב-Suspense כדי להציג תוכן placeholder לתוכן הטבלה בזמן הטעינה האיטית של הרכיבים בפועל.

צוות AirSHIFT העביר גם חלק מהלוגיקה העסקית היקרה בתוך הרכיבים שנטענים באיטרציה לWeb Workers. כך פתרנו את הבעיה של קפיצות בקלט של המשתמש על ידי שחרור של שרשור הראשי כדי שיוכל להתמקד בתגובה לקלט של המשתמש.

בדרך כלל, מפתחים נתקלים בבעיות מורכבות כשהם משתמשים ב-workers, אבל הפעם 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>
 
)
}

ברכיב Cost, משתמשים ב-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>;
}

הטמעת הלוגיקה של החישוב שפועלת ב-worker וחשיפה שלה באמצעות 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);

תוצאות

למרות הכמות המוגבלת של הלוגיקה שהם העבירו ל-worker בתור ניסיון, ב-AirSHIFT העבירו כ-100 אלפיות השנייה של JavaScript מהשרשור הראשי לשרשור ה-worker (בסימולציה עם צמצום של 4x ב-CPU).

צילום מסך של הקלטה בחלונית הביצועים של כלי הפיתוח ל-Chrome, שמראה שהכתיבה ב-Script מתבצעת עכשיו ב-web worker ולא בשרשור הראשי.

ב-AirSHIFT בודקים כרגע אם אפשר להשתמש בטעינה איטית של רכיבים אחרים ולהעביר יותר לוגיקה ל-web workers כדי לצמצם עוד יותר את התנודות.

4. הגדרת תקציב ביצועים

אחרי שהטמענו את כל פעולות האופטימיזציה האלה, היה חשוב לוודא שהאפליקציה תמשיך לפעול בצורה טובה לאורך זמן. AirSHIFT משתמש עכשיו ב-bundlesize כדי לא לחרוג מהגודל הנוכחי של קובצי ה-JavaScript וה-CSS. בנוסף להגדרת התקציבים הבסיסיים האלה, הם יצרו לוח בקרה כדי להציג אחוזונים שונים של זמן הטעינה של טבלת המשמרות, כדי לבדוק אם הביצועים של האפליקציה טובים גם בתנאים לא אידיאליים.

  • מעכשיו, זמן השלמת הסקריפט של כל אירוע Redux נמדד
  • נתוני הביצועים נאספים ב-Elasticsearch
  • הביצועים של כל אירוע בפרמטרים 10%, 25%, 50% ו-75% מוצגים באופן חזותי באמצעות Kibana

AirSHIFT עוקב עכשיו אחרי אירוע הטעינה של טבלת המשמרות כדי לוודא שהוא מסתיים תוך 3 שניות אצל 75% מהמשתמשים. זהו תקציב שלא נאכף כרגע, אבל הם שוקלים לשלוח התראות אוטומטיות דרך Elasticsearch כשהם חורגים מהתקציב.

תרשים שבו מוצג שהשלמת האחוזון ה-75 מתבצעת תוך כ-2,500 אלפיות שנייה, שהשלמת האחוזון ה-50 מתבצעת תוך כ-1,250 אלפיות שנייה, שהשלמת האחוזון ה-25 מתבצעת תוך כ-750 אלפיות שנייה והשלמת האחוזון ה-10 מתבצעת תוך כ-500 אלפיות שנייה.
לוח הבקרה של Kibana שבו מוצגים נתוני הביצועים היומיים לפי אחוזונים.

תוצאות

לפי התרשים שלמעלה, אפשר לראות ש-AirSHIFT משיג עכשיו ברוב המקרים את התקציב של 3 שניות למשתמשים באחוזון ה-75, וגם טבלת המעבר נטענת תוך שנייה למשתמשים באחוזון ה-25. בעזרת תיעוד נתוני הביצועים של RUM בתנאים ובמכשירים שונים, AirSHIFT יכול עכשיו לבדוק אם השקת תכונה חדשה משפיעה בפועל על ביצועי האפליקציה או לא.

5. האקתונים בנושא ביצועים

כל המאמצים האלה לשיפור הביצועים היו חשובים ומשמעותיים, אבל לא תמיד קל לגרום לצוותים של מהנדסים ושל עסקים לתת עדיפות לפיתוח לא פונקציונלי. חלק מהאתגר הוא שאי אפשר לתכנן חלק מהשיפורים האלה בביצועים. הם דורשים ניסוי וטעייה.

ב-AirSHIFT מתקיימים עכשיו אירועי האקתון פנימיים של יום אחד בנושא ביצועים, כדי לאפשר למהנדסים להתמקד רק בעבודה שקשורה לביצועים. באירועי ה-hackathon האלה הם מסירים את כל האילוצים ומכבדים את היצירתיות של המהנדסים, כלומר כדאי לשקול כל הטמעה שתורמת למהירות. כדי לזרז את ההתקדמות בהאקתון, צוות AirSHIFT מפצל את הקבוצה לצוותים קטנים, וכל צוות מתחרה על השיפור הגדול ביותר בשיעור הביצועים ב-Lighthouse. התחרות בין הקבוצות הולכת ומתחממת! 🔥

תמונות מהאקאתון.

תוצאות

הגישה של ה-hackathon עובדת מצוין בשבילם.

  • כדי לזהות בקלות צווארי בקבוק בביצועים, אפשר לנסות כמה גישות במהלך ה-hackathon ולמדוד כל אחת מהן באמצעות Lighthouse.
  • אחרי ההאקתון, קל למדי לשכנע את הצוות באילו שיפורים צריך לתת עדיפות לקראת השקת המוצר בסביבת הייצור.
  • זו גם דרך יעילה להדגיש את החשיבות של המהירות. כל המשתתפים יכולים להבין את הקשר בין האופן שבו כותבים קוד לבין התוצאות שלו מבחינת הביצועים.

תופעת לוואי חיובית הייתה שצוותי מהנדסים רבים אחרים ב-Recruit התעניינו בגישה המעשית הזו, וצוות AirSHIFT מפעיל עכשיו כמה אירועי speed hackathon בחברה.

סיכום

לא הייתה זו דרך קלה עבור AirSHIFT לבצע את האופטימיזציות האלה, אבל התוצאות בהחלט הצדיקו את המאמץ. עכשיו AirSHIFT טוענת את טבלת המשמרות תוך 1.5 שניות בממוצע, שיפור של פי 6 בהשוואה לביצועים שלה לפני הפרויקט.

אחרי השקת האופטימיזציות לביצועים, משתמש אחד אמר:

תודה רבה על הטעינה המהירה של טבלת המשמרות. עכשיו הרבה יותר קל לתזמן את משמרות העבודה.