Autentyczne studium przypadku dotyczące optymalizacji skuteczności React SPA.
Wydajność witryny nie zależy tylko od czasu jej wczytywania. Zapewnienie użytkownikom szybkiego działania i elastyczności jest bardzo ważne, zwłaszcza w przypadku aplikacji komputerowych, z których korzystają na co dzień. Zespół inżynierów z firmy Recruit Technologies przeprowadził projekt refaktoryzacyjny, aby ulepszyć jedną ze swoich aplikacji internetowych, AirSHIFT, i zwiększyć wydajność wprowadzania danych przez użytkowników. Zobacz, jak to zrobili.
Długi czas reakcji, mniejsza produktywność
AirSHIFT to aplikacja internetowa na komputer, która pomaga właścicielom sklepów, takich jak restauracje i kawiarnie, zarządzać pracą zmianową pracowników. Oparta na technologii React aplikacja jednostronicowa zapewnia zaawansowane funkcje klienckie, w tym różne tabele siatki z harmonogramami zmian porządkowanych według dnia, tygodnia, miesiąca i nie tylko.
Gdy zespół inżynierów Recruit Technologies dodał do aplikacji AirSHIFT nowe funkcje, zaczęło się pojawiać więcej opinii o niskiej wydajności. Yosuke Furukawa, kierownik zespołu ds. inżynierii w AirSHIFT, powiedział:
Przeprowadzone badanie opinii użytkowników zaskoczyło nas, gdy pewna właścicielka sklepu stwierdziła, że po kliknięciu przycisku woli wstać i zaparzyć kawę, żeby nie czekać na załadowanie stołu.
Po przeprowadzeniu badań zespół inżynierów zauważył, że wielu użytkowników próbowało wczytywać tabele z masowymi zmianami na komputerach o niskiej wydajności, takich jak laptop 1 GHz Celeron M sprzed 10 lat.
Aplikacja AirSHIFT blokowała główny wątek za pomocą kosztownych skryptów, ale zespół inżynierów nie zdawał sobie sprawy, jak drogie są te skrypty, ponieważ były opracowywane i testowane na komputerach o zaawansowanych specyfikacjach i szybkim połączeniu Wi-Fi.
Po profilowaniu wydajności w Narzędziach deweloperskich w Chrome z włączonym ograniczaniem wykorzystania procesora i sieci stało się jasne, że potrzebna jest optymalizacja wydajności. Do rozwiązania tego problemu zespół AirSHIFT utworzył grupę zadaniową. Oto 5 rzeczy, na których skupili się, aby Twoja aplikacja lepiej reagowała na działania użytkowników.
1. Wirtualizacja dużych tabel
Wyświetlenie tabeli zmian wymagało wielu kosztownych kroków: skonstruowania wirtualnego DOM i wyrenderowania go na ekranie proporcjonalnie do liczby pracowników i przedziałów czasu. Jeśli na przykład restauracja ma 50 pracowników i chce sprawdzić swój miesięczny harmonogram zmian, otrzymana tabela to 50 osób (członków) pomnożona przez 30 (dni), co daje 1500 komponentów do wyrenderowania. To bardzo kosztowna operacja, zwłaszcza w przypadku urządzeń o niskiej specyfikacji. W rzeczywistości sytuacja była gorsza. Z badań wynika, że były tam sklepy zarządzające 200 pracownikami,które wymagały około 6000 komponentów w jednej miesięcznej tabeli.
Aby zmniejszyć koszt tej operacji, AirSHIFT wirtualizowała tabelę zmian. Aplikacja podłącza teraz tylko komponenty w widocznym obszarze i odłącza komponenty poza ekranem.
W tym przypadku AirSHIFT zastosowała wirtualizację z użyciem reakcji, ponieważ istniały wymagania dotyczące włączania skomplikowanych dwuwymiarowych tabel siatki. Analizuje też sposoby przekształcenia implementacji tak, aby w przyszłości wykorzystywała uproszczony okno reakcji.
Wyniki
Samo wirtualizowanie tabeli skróciło czas wykonywania skryptów o 6 sekund (przy czterokrotnym spowolnieniu procesora i ograniczonym środowisku Macbooka Pro 3G). Była to najistotniejsza poprawa wydajności w projekcie refaktoryzacyjnym.
2. Kontrola za pomocą interfejsu User Timing API
Następnie zespół AirSHIFT zrefaktoryzował skrypty uruchamiane z wykorzystaniem danych wejściowych użytkownika. Wykres płomieniowy Narzędzi deweloperskich w Chrome umożliwia analizę tego, co faktycznie dzieje się w wątku głównym. Zespół AirSHIFT ułatwił analizowanie aktywności aplikacji na podstawie cyklu życia React.
React 16 udostępnia śledzenie wydajności za pomocą interfejsu User Timing API, który można wizualizować w sekcji Timings (Czas) Narzędzi deweloperskich w Chrome. AirSHIFT użyła sekcji Czasy, aby znaleźć niepotrzebną logikę działającą w zdarzeniach cyklu życia React.
Wyniki
Zespół AirSHIFT odkrył, że tuż przed każdą nawigacją po trasie miało miejsce niepotrzebne uzgadnianie drzewa React. Oznaczało to, że React niepotrzebnie aktualizował tabelę shift przed rozpoczęciem nawigacji. Przyczyną problemu była niepotrzebna aktualizacja stanu Redux. Jej naprawienie zaoszczędziło około 750 ms czasu na tworzeniu skryptów. Wprowadzono też inne mikrooptymalizacje, które doprowadziły do skrócenia łącznego czasu tworzenia skryptów o 1 sekundę.
3. Leniwe ładowanie komponentów i przenoszenie kosztownych rozwiązań logicznych do pracowników sieciowych
AirSHIFT ma wbudowaną aplikację czatu. Wielu właścicieli sklepów komunikuje się z pracownikami na czacie, korzystając z tabeli zmian, co oznacza, że użytkownik może pisać wiadomość, gdy tabela się wczytuje. Jeśli wątek główny zawiera skrypty renderujące tabelę, dane wejściowe użytkownika mogą być nieprawidłowe.
Aby zwiększyć wygodę korzystania z tabeli, AirSHIFT używa teraz komponentów React.lazy i Susense do wyświetlania obiektów zastępczych zawartości tabeli oraz leniwego ładowania komponentów.
Zespół AirSHIFT przeniósł też niektóre drogie logiki biznesowe w ramach leniwego ładowania komponentów do pracowników internetowych. Rozwiązało to problem z zacinaniem się danych wejściowych użytkownika, zwolnił wątek główny, dzięki czemu mógł on skupić się na reagowaniu na to, co użytkownik chce przekazać.
Zwykle deweloperzy mają problemy z zatrudnieniem pracowników, ale tym razem Comlink wykonała dla nich najtrudniejsze zadania. Poniżej znajduje się pseudokod pokazujący, jak zespół AirSHIFT przeprowadził jedną z najdroższych operacji – obliczał łączne koszty pracy.
W App.js używaj plików React.lazy i Susense, aby podczas wczytywania wyświetlać treści zastępcze
/** 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 Koszt do wykonania logiki obliczeniowej użyj narzędzia 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>;
}
Wdróż logikę obliczeń, która działa w instancji roboczej i udostępniaj ją za pomocą 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, jaką firma używała w okresie próbnym, AirSHIFT przesunęła około 100 ms kodu JavaScript z wątku głównego do wątku roboczego (przy symulowaniu czterokrotnego ograniczania procesora).
AirSHIFT sprawdza obecnie, czy może leniwie ładować inne komponenty i przeciążyć więcej logiki pracownikom sieci, aby jeszcze bardziej ograniczyć obciążenie.
4. Ustawianie budżetu skuteczności
Po wprowadzeniu wszystkich tych optymalizacji kluczowe było dbanie o wydajność aplikacji przez dłuższy czas. AirSHIFT używa teraz atrybutu bundlesize, aby nie przekraczać obecnego rozmiaru plików JavaScript i CSS. Poza określeniem tych podstawowych budżetów zespół utworzył panel do wyświetlania różnych centyli czasu wczytywania tabeli zmian, aby sprawdzić, czy aplikacja działa nawet w nieidealnych warunkach.
- Teraz jest mierzony czas ukończenia skryptu dla każdego zdarzenia Redux
- Dane o skuteczności są zbierane w narzędziu Elasticsearch.
- Skuteczność każdego zdarzenia na 10, 25, 50 i 75 centylu jest wizualizowana za pomocą narzędzia Kibana.
AirSHIFT monitoruje teraz zdarzenie wczytywania tabeli shift, aby mieć pewność, że kończy się w ciągu 3 sekund w przypadku 75 centyla użytkowników. W tej chwili jest to nieegzekwowany budżet, ale firma rozważa automatyczne otrzymywanie powiadomień przez Elasticsearch, gdy przekroczą one budżet.
Wyniki
Na podstawie powyższego wykresu widać, że AirSHIFT wykorzystuje teraz głównie 3-sekundowy budżet dla 75 centyla użytkowników i wczytuje tabelę zmian w ciągu sekundy w przypadku użytkowników z 25 centylem. Dzięki zbieraniu danych o wydajności RUM z różnych warunków i urządzeń AirSHIFT może teraz sprawdzić, czy nowa funkcja faktycznie wpływa na wydajność aplikacji.
5. Hackathony na temat wydajności
Wszystkie wysiłki w zakresie optymalizacji wydajności były ważne i skuteczne, nie zawsze jest łatwo zmusić zespoły inżynierów i zespoły biznesowe, aby potraktowały priorytetowo niefunkcjonalne projekty. Niestety część z tych optymalizacji skuteczności nie jest możliwa do planowania. Wymagają eksperymentowania oraz metody prób i błędów.
AirSHIFT przeprowadza teraz 1-dniowe wewnętrzne maratony związane z wydajnością, aby inżynierowie mogli skupić się tylko na pracy związanej z wydajnością. W tych hackathonach eliminują wszystkie ograniczenia i szanują kreatywność inżynierów, co oznacza, że warto rozważyć każde wdrożenie, które zwiększa szybkość. Aby przyspieszyć hackathon, AirSHIFT dzieli grupę na małe zespoły, a każda z nich rywalizuje o to, kto może uzyskać największy wzrost skuteczności w Lighthouse. Drużyny są bardzo rywalizujące! 🔥
Wyniki
Takie podejście się sprawdza.
- Wąskie gardła wydajności można łatwo wykryć, testując różne podejścia podczas hackathonu i mierząc je za pomocą narzędzia Lighthouse.
- Po zakończeniu hackathonu trudno jest przekonać zespół, który optymalizację powinien potraktować priorytetowo w odniesieniu do wersji produkcyjnej.
- To także skuteczny sposób promowania znaczenia szybkości. Każdy uczestnik może zrozumieć zależność między sposobem kodowania a jego skutecznością.
Dobrym efektem ubocznym było to, że wiele innych zespołów inżynierów w Recruit zainteresowało się tym praktycznym podejściem, a zespół AirSHIFT przeprowadził teraz w firmie liczne hackathony.
Podsumowanie
Optymalizacja w AirSHIFT z pewnością się opłaciła. Teraz AirSHIFT wczytuje tabelę zmian w medianie w ciągu 1,5 sekundy, co stanowi sześciokrotny wzrost w stosunku do wyników sprzed projektu.
Po uruchomieniu optymalizacji skuteczności jeden z użytkowników powiedział:
Dziękuję za pomoc w szybkim wczytywaniu tabeli zmian. Organizowanie pracy zmianowej jest teraz znacznie wydajniejsze.