5 sposobów poprawienia wydajności aplikacji React dzięki funkcji AirSHIFT w czasie działania

Studium przypadku optymalizacji wydajności SPA w React w rzeczywistych warunkach.

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

Wydajność witryny nie zależy tylko od czasu wczytywania. Ważne jest, aby zapewnić użytkownikom szybkie i wygodne działanie aplikacji, zwłaszcza w przypadku aplikacji biurowych, których użytkownicy używają codziennie. Zespół inżynierów firmy Recruit Technologies przeprowadził projekt refaktoryzacji, aby ulepszyć jedną ze swoich aplikacji internetowych, AirSHIFT, pod kątem wydajności wprowadzania danych przez użytkowników. Oto jak to zrobili.

Powolna reakcja, mniejsza produktywność

AirSHIFT to aplikacja internetowa na komputer, która pomaga właścicielom sklepów, np. restauracji i kawiarni, zarządzać pracą zmianową pracowników. Ta aplikacja jednostronicowa została stworzona w ramach React i zawiera bogate funkcje klienta, w tym różne tabele siatki harmonogramów zmian uporządkowane według dnia, tygodnia, miesiąca itp.

Zrzut ekranu aplikacji internetowej AirSHIFT

Gdy zespół inżynierów Recruit Technologies dodawał nowe funkcje do aplikacji AirSHIFT, zaczął otrzymywać więcej opinii dotyczących jej niskiej wydajności. Menedżer ds. inżynieryjnych w AirSHIFT, Yosuke Furukawa, powiedział:

W ramach badań opinii użytkowników byliśmy zdumieni, gdy jeden z właścicieli sklepów powiedział, że po kliknięciu przycisku wstanie z krzesła, aby zaparzyć kawę, tylko po to, aby zabić czas oczekiwania na załadowanie tabeli zmian.

Po przeanalizowaniu wyników zespół inżynierów doszedł do wniosku, że wielu użytkowników próbuje wczytywać ogromne tabele na komputerach o niskich specyfikacjach, takich jak laptop Celeron M o taktowaniu 1 GHz sprzed 10 lat.

Niekończący się spinner na urządzeniach niskiej klasy.

Aplikacja AirSHIFT blokowała główny wątek za pomocą drogich skryptów, ale zespół inżynierów nie zdawał sobie sprawy, jak drogie są te skrypty, ponieważ programiści opracowywali i testowali je na komputerach o wysokiej specyfikacji z szybkim połączeniem Wi-Fi.

Wykres przedstawiający aktywność aplikacji w czasie działania.
Podczas wczytywania tabeli zmian około 80% czasu wczytywania zajęły skrypty.

Po przeprowadzeniu profilowania wydajności w Narzędziech deweloperskich Chrome z włączonym ograniczeniem procesora i ograniczeniem przepustowości sieci okazało się, że konieczna jest optymalizacja wydajności. Aby rozwiązać ten problem, AirSHIFT utworzył zespół zadaniowy. Oto 5 rzeczy, na których skupili się, aby ich aplikacja była bardziej responsywna na działania użytkowników.

1. Wirtualizacja dużych tabel

Wyświetlanie tabeli zmian wymagało wykonania wielu kosztownych kroków: tworzenia wirtualnego DOM-u i renderowania go na ekranie proporcjonalnie do liczby pracowników i przedziałów czasowych. Jeśli np. restauracja zatrudnia 50 pracowników i chce sprawdzić ich miesięczny harmonogram zmian, będzie to tabela z 50 osobami (pracownicy) pomnożona przez 30 dni, co da 1500 komponentów komórek do renderowania. Jest to bardzo kosztowna operacja, zwłaszcza w przypadku urządzeń o niskich specyfikacjach. W rzeczywistości było jeszcze gorzej. Z badania wynika, że w sklepach, w których pracuje 200 osób, w jednej tabeli miesięcznej jest około 6000 elementów komórki.

Aby zmniejszyć koszty tej operacji, AirSHIFT zwirtualizował tabelę zmian. Aplikacja montuje teraz tylko komponenty w widocznym obszarze i odłącza komponenty poza ekranem.

Zrzut ekranu z komentarzem pokazującym, że AirSHIFT renderował treści poza obszarem widoku.
Przed: renderowanie wszystkich komórek tabeli przesunięcia.
Zaznaczony zrzut ekranu pokazujący, że AirSHIFT renderuje teraz tylko treści, które są widoczne w widocznym obszarze.
Po: renderowanie tylko komórek w widocznym obszarze.

W tym przypadku firma AirSHIFT używała react-virtualized, ponieważ wymagała to włączenia złożonych dwuwymiarowych tabel siatki. Badacze szukają też sposobów na przekształcenie implementacji, aby w przyszłości używać lekkiego pakietu react-window.

Wyniki

Sam proces wirtualizacji tabeli skrócił czas tworzenia skryptu o 6 sekund (przy 4-krotnym spowolnieniu procesora i szybkim ograniczeniu szybkości Macbooka Pro do sieci 3G). Było to najbardziej znaczące polepszenie wydajności w ramach projektu refaktoryzacji.

Zrzut ekranu z adnotacjami przedstawiający nagranie z panelu Wydajność w Narzędziach deweloperskich w Chrome.
Przed: około 10 sekund skryptu po wprowadzeniu danych przez użytkownika.
Kolejny zrzut ekranu z adnotacjami przedstawiający nagranie z panelu Wydajność w Narzędziach deweloperskich w Chrome.
Po: 4 sekundy skryptu po wprowadzeniu danych przez użytkownika.

2. Kontrola za pomocą interfejsu User Timing API

Następnie zespół AirSHIFT przeprowadził refaktoryzację skryptów, które działają na podstawie danych wejściowych użytkownika. Grafika słupkowaNarzędziach deweloperskich w Chrome umożliwia analizę tego, co dzieje się na głównym wątku. Zespół AirSHIFT stwierdził jednak, że łatwiej jest analizować aktywność aplikacji na podstawie cyklu życia React.

React 16 udostępnia ślad wydajności za pomocą interfejsu User Timing API, który możesz wizualizować w sekcji Czasy w Narzędziach deweloperskich w Chrome. Firma AirSHIFT korzystała z sekcji Czasy, aby znaleźć niepotrzebną logikę działającą w zdarzeniach cyklu życia React.

Sekcja Czasy w panelu Wydajność w Narzędziach deweloperskich w Chrome
Zdarzenia Czas użytkownika w React.

Wyniki

Zespół AirSHIFT odkrył, że przed każdą nawigacją trasy występuje niepotrzebne pojednanie drzewa React. Oznacza to, że React niepotrzebnie aktualizował tabelę shift przed nawigacją. Problem powodował niepotrzebny update stanu Redux. Dzięki temu udało się skrócić czas skryptu o około 750 ms. AirSHIFT wprowadził też inne mikrooptymalizacje, które ostatecznie doprowadziły do skrócenia czasu tworzenia skryptu o 1 sekundę.

3. Leniwe ładowanie komponentów i przenoszenie kosztownej logiki do web workerów

AirSHIFT ma wbudowaną aplikację do czatu. Wielu właścicieli sklepów komunikuje się z pracownikami za pomocą czatu, jednocześnie korzystając z tabeli zmian. Oznacza to, że użytkownik może wpisywać wiadomość, gdy tabela jest wczytywana. Jeśli wątek główny jest zajęty skryptami renderującymi tabelę, dane wejściowe użytkownika mogą być niestabilne.

Aby ulepszyć ten proces, AirSHIFT korzysta teraz z React.lazy i Suspense, aby wyświetlać elementy zastępcze dla zawartości tabeli podczas leniwego wczytywania rzeczywistych komponentów.

Zespół AirSHIFT przeniósł też część kosztownej logiki biznesowej z komponentów wczytywanych z opóźnieniem do elementów obsługiwanych przez przeglądarkę. Rozwiązało to problem z zawieszaniem się aplikacji podczas wprowadzania danych przez użytkownika, ponieważ zwolnił to główny wątek, który mógł się skupić na reagowaniu na dane wejściowe użytkownika.

Zwykle deweloperzy mają problemy z korzystaniem z robotów, ale tym razem Comlink wykonał za nich ciężką pracę. Poniżej znajduje się pseudokod, który pokazuje, jak firma AirSHIFT zautomatyzowała jedną z najdroższych operacji, jaką miała do wykonania: obliczenie łącznych kosztów pracy.

W App.js użyj React.lazy i Suspense, aby wyświetlać treści zastępcze podczas wczytywania

/** 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>
 
)
}

W komponencie Cost użyj funkcji comlink do wykonania logiki obliczeń

/** 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>;
}

Wdróż logikę obliczeń, która działa w procesie roboczym, i ujawnij ją za pomocą funkcji 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);

Wyniki

Pomimo ograniczonej ilości logiki przeniesionej do instancji roboczej w ramach testów, AirSHIFT przeniósł około 100 ms kodu JavaScript z głównego wątku do wątku roboczego (symulowanego z 4-krotnym ograniczeniem procesora).

Zrzut ekranu przedstawiający nagranie z panelu Wydajność w Narzędziach deweloperskich w Chrome, które pokazuje, że skrypt jest teraz wykonywany w procesie web worker, a nie w głównym wątku.

Zespół AirSHIFT bada obecnie, czy można wczytywać inne komponenty z opóźnieniem i przekazywać więcej logiki do procesów w tle, aby jeszcze bardziej ograniczyć zacięcia.

4. Ustawianie budżetu wydajności

Po wdrożeniu wszystkich tych optymalizacji należało zadbać o to, aby aplikacja nadal działała sprawnie. AirSHIFT używa teraz parametru bundlesize, aby nie przekraczać bieżącego rozmiaru plików JavaScript i CSS. Oprócz ustawienia tych podstawowych budżetów zespół stworzył panel, który pokazuje różne wartości percentylowe czasu wczytywania tabeli zmian, aby sprawdzić, czy aplikacja działa prawidłowo nawet w niesprzyjających warunkach.

  • Czas zakończenia skryptu w przypadku każdego zdarzenia Redux jest teraz mierzony
  • Dane o wydajności są zbierane w Elasticsearch
  • Wydajność każdego zdarzenia w 10., 25., 50. i 75. centylu jest wizualizowana za pomocą Kibana.

AirSHIFT monitoruje teraz zdarzenie wczytywania tabeli zmian, aby upewnić się, że kończy się ono w ciągu 3 sekund w przypadku użytkowników w 75. perycencie. Na razie jest to budżet nieobowiązkowy, ale rozważamy automatyczne powiadomienia za pomocą Elasticsearch, gdy przekroczymy budżet.

Wykres pokazujący, że 75 centyl osiąga wartość w około 2500 ms, 50 centyl w około 1250 ms, 25 centyl w około 750 ms, a 10 centyl w około 500 ms.
Panel Kibana z danymi o codziennej skuteczności według wartości procentylowych.

Wyniki

Z wykresu powyżej można wywnioskować, że aplikacja AirSHIFT osiąga obecnie budżet 3 sekund w przypadku użytkowników z 75. percentyla oraz wczytuje tabelę zmian w ciągu sekundy w przypadku użytkowników z 25. percentyla. Dzięki rejestrowaniu danych o wydajności RUM w różnych warunkach i na różnych urządzeniach AirSHIFT może teraz sprawdzać, czy nowa wersja funkcji rzeczywiście wpływa na wydajność aplikacji.

5. Hackathony dotyczące wydajności

Mimo że wszystkie te działania związane z optymalizacją wydajności były ważne i miały duży wpływ, nie zawsze łatwo jest przekonać zespoły inżynierów i biznesowe do nadania priorytetów rozwojowi niefunkcjonalnemu. Część problemu polega na tym, że niektórych optymalizacji skuteczności nie można zaplanować. Wymagają eksperymentowania i testowania metodą prób i błędów.

Zespół AirSHIFT prowadzi obecnie wewnętrzne jednodniowe hackathony dotyczące wydajności, aby umożliwić inżynierom skupienie się wyłącznie na pracy związanej z wydajnością. Podczas tych hackathonów nie ma żadnych ograniczeń i szanujemy kreatywność inżynierów, co oznacza, że warto rozważyć każde wdrożenie, które przyspiesza działanie. Aby przyspieszyć hackathon, AirSHIFT dzieli grupę na małe zespoły, które rywalizują ze sobą o to, kto uzyska największą poprawę wyniku w Lighthouse. Drużyny stają się bardzo konkurencyjne. 🔥

zdjęcia z hackathonu,

Wyniki

Podejście w ramach hackathonu sprawdza się w ich przypadku.

  • Wszelkie wąskie gardła w zakresie wydajności można łatwo wykryć, testując podczas hackathonu różne podejścia i mierząc je za pomocą Lighthouse.
  • Po hackathonie łatwo przekonać zespół, które optymalizacje powinny być priorytetem w przypadku wersji produkcyjnej.
  • Jest to też skuteczny sposób na zachęcanie do szybszego działania. Każdy uczestnik może zrozumieć korelację między sposobem kodowania a wynikającym z tego wpływem na skuteczność.

Miłym skutkiem ubocznym było to, że wiele innych zespołów programistycznych w Recruit zainteresowało się tym praktycznym podejściem, a zespół AirSHIFT prowadzi obecnie w firmie wiele hackathonów.

Podsumowanie

Praca nad tymi optymalizacjami nie była łatwa, ale opłaciła się. Teraz aplikacja AirSHIFT wczytuje tabelę zmian w średnim czasie 1,5 sekundy, co oznacza 6-krotne przyspieszenie w porównaniu z czasem wczytywania przed rozpoczęciem projektu.

Po wprowadzeniu optymalizacji skuteczności jeden z użytkowników powiedział:

Dziękujemy za szybkie wczytanie tabeli zmian. Organizowanie pracy zmianowej jest teraz znacznie bardziej wydajne.