חמש דרכים שבהן 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 שבהם הופעלה ויסות נתונים (throttle) במעבד (CPU) וברשת, ברור היה צורך באופטימיזציה של הביצועים. 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 מאפשר לנתח מה קורה בפועל ב-thread הראשי. אבל לצוות 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 יש אפליקציית צ'אט מובנית. בעלי חנויות רבים מתכתבים עם חברי הצוות שלהם בצ'אט בזמן שהם צופים בטבלת המשמרות. לכן, יכול להיות שמשתמש יתקליד הודעה בזמן שהטבלה נטענת. אם ה-thread הראשי מכיל סקריפטים שמבצעים עיבוד של הטבלה, הקלט של המשתמשים עלול להיות משובש.

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

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

תוצאות

גישת ההאקאתון עובדת טוב בשבילם.

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

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

סיכום

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

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

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