Пять способов, которыми AirSHIFT улучшила производительность своего приложения React во время выполнения

Реальный пример оптимизации производительности React SPA.

Кенто Цудзи
Kento Tsuji
Сатоши Араи
Satoshi Arai
Юсуке Уцуномия
Yusuke Utsunomiya
Ёсуке Фурукава
Yosuke Furukawa

Производительность веб-сайта зависит не только от времени загрузки. Крайне важно обеспечить пользователям быстроту и оперативность реагирования, особенно в случае настольных приложений, которые люди используют каждый день. Команда инженеров Recruit Technologies провела проект рефакторинга, чтобы улучшить одно из своих веб-приложений AirSHIFT и повысить производительность пользовательского ввода. Вот как они это сделали.

Медленный отклик, меньшая производительность

AirSHIFT — это настольное веб-приложение, которое помогает владельцам магазинов, например ресторанов и кафе, управлять сменной работой своих сотрудников. Одностраничное приложение, созданное с помощью React, предоставляет богатые клиентские функции, включая различные таблицы с графиками смен, организованными по дням, неделям, месяцам и т. д.

Скриншот веб-приложения AirSHIFT.

Когда команда инженеров Recruit Technologies добавила новые функции в приложение AirSHIFT, они начали получать больше отзывов о низкой производительности. Технический руководитель AirSHIFT Йосуке Фурукава сказал:

В ходе исследования пользователей мы были шокированы, когда одна из владельцев магазина сказала, что она вставала со своего места, чтобы заварить кофе после нажатия кнопки, просто чтобы убить время в ожидании загрузки сменного стола.

Проведя исследование, команда инженеров поняла, что многие из их пользователей пытались загрузить большие таблицы смен на компьютеры с низкими характеристиками, такие как ноутбук Celeron M с тактовой частотой 1 ГГц, выпущенный 10 лет назад.

Бесконечный спиннер на бюджетных устройствах.

Приложение AirSHIFT блокировало основной поток дорогостоящими сценариями, но команда разработчиков не осознавала, насколько дорогими были сценарии, поскольку они разрабатывали и тестировали на компьютерах с богатыми характеристиками и быстрым подключением Wi-Fi.

Диаграмма, показывающая активность приложения во время выполнения.
При загрузке таблицы смен около 80% времени загрузки занимало выполнение скриптов.

После профилирования их производительности в Chrome DevTools с включенным регулированием ЦП и сети стало ясно, что необходима оптимизация производительности. AirSHIFT сформировала рабочую группу для решения этой проблемы. Вот 5 вещей, на которых они сосредоточились, чтобы сделать свое приложение более отзывчивым на действия пользователя.

1. Виртуализируйте большие таблицы

Для отображения таблицы смен потребовалось несколько дорогостоящих шагов: создание виртуальной модели DOM и ее отображение на экране пропорционально количеству сотрудников и временным интервалам. Например, если в ресторане работает 50 человек и вы хотите проверить график их ежемесячных смен, это будет таблица из 50 (участников), умноженных на 30 (дней), что приведет к рендерингу 1500 компонентов ячеек. Это очень дорогая операция, особенно для устройств с низкими характеристиками. В действительности дела обстояли хуже. В результате исследования они узнали, что в некоторых магазинах работают 200 сотрудников, и им требуется около 6000 клеточных компонентов в одном ежемесячном столе.

Чтобы снизить стоимость этой операции, AirSHIFT виртуализировала таблицу смен. Приложение теперь монтирует компоненты только внутри области просмотра и отключает компоненты за пределами экрана.

Скриншот с аннотациями, демонстрирующий, как AirSHIFT используется для рендеринга контента за пределами области просмотра.
До: Отрисовка всех ячеек таблицы смен.
Скриншот с аннотациями, демонстрирующий, что AirSHIFT теперь отображает только контент, видимый в области просмотра.
После: рендеринг только ячеек внутри области просмотра.

В этом случае AirSHIFT использовал виртуализацию реагирования , поскольку существовали требования по включению сложных двумерных таблиц сетки. Они также изучают способы преобразования реализации для использования облегченного окна реагирования в будущем.

Результаты

Только виртуализация таблицы сократила время выполнения сценариев на 6 секунд (при четырехкратном замедлении ЦП + быстром 3G-регулировании среды Macbook Pro). Это было самое значительное улучшение производительности в проекте рефакторинга.

Аннотированный скриншот записи панели Chrome DevTools Performance.
До: около 10 секунд написания сценария после ввода пользователя.
Еще один аннотированный скриншот записи панели Chrome DevTools Performance.
После: 4 секунды написания сценария после ввода пользователя.

2. Аудит с помощью User Timing API

Затем команда AirSHIFT провела рефакторинг сценариев, которые запускаются при вводе данных пользователем. Диаграмма пламени Chrome DevTools позволяет проанализировать, что на самом деле происходит в основном потоке. Но команде AirSHIFT оказалось проще анализировать активность приложений на основе жизненного цикла React.

React 16 предоставляет трассировку производительности через User Timing API , который вы можете визуализировать в разделе «Тайминги» Chrome DevTools. AirSHIFT использовал раздел «Тайминги», чтобы найти ненужную логику, выполняемую в событиях жизненного цикла React.

Раздел «Тайминги» на панели «Производительность» Chrome DevTools.
События пользовательского времени React.

Результаты

Команда AirSHIFT обнаружила, что ненужное согласование дерева React происходило непосредственно перед каждой навигацией по маршруту. Это означало, что React без необходимости обновлял таблицу смен перед навигацией. Эту проблему вызывало ненужное обновление состояния Redux. Исправление сэкономило около 750 мс времени написания сценария. AirSHIFT также провел другие микрооптимизации, которые в конечном итоге привели к общему сокращению времени написания сценариев на 1 секунду.

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-кратным регулированием ЦП).

Скриншот записи панели Chrome DevTools Performance, показывающий, что выполнение сценариев теперь выполняется в веб-работнике, а не в основном потоке.

AirSHIFT в настоящее время изучает возможность ленивой загрузки других компонентов и перегрузки большего количества логики веб-работникам для дальнейшего уменьшения количества зависаний.

4. Установление бюджета производительности

После реализации всех этих оптимизаций было крайне важно обеспечить, чтобы приложение оставалось производительным с течением времени. AirSHIFT теперь использует размер пакета , чтобы не превышать текущий размер файла JavaScript и CSS. Помимо установки этих базовых бюджетов, они создали информационную панель, на которой отображаются различные процентили времени загрузки таблицы смен, чтобы проверить, работает ли приложение даже в неидеальных условиях.

  • Время завершения скрипта для каждого события Redux теперь измеряется.
  • Данные о производительности собираются в Elasticsearch.
  • Производительность 10-го, 25-го, 50-го и 75-го процентиля каждого события визуализируется с помощью Kibana.

AirSHIFT теперь отслеживает событие загрузки таблицы смен, чтобы убедиться, что оно завершится в течение 3 секунд для пользователей 75-го процентиля. На данный момент это неисполненный бюджет, но они рассматривают возможность автоматических уведомлений через Elasticsearch, когда они превышают свой бюджет.

Диаграмма показывает, что 75-й процентиль завершается примерно за 2500 мс, 50-й процентиль — примерно за 1250 мс, 25-й процентиль — примерно за 750 мс и 10-й процентиль — примерно за 500 мс.
Панель управления Kibana показывает ежедневные данные о производительности в процентилях.

Результаты

Из графика выше видно, что AirSHIFT теперь в основном достигает 3-секундного бюджета для пользователей 75-го процентиля, а также загружает таблицу смен в течение секунды для пользователей 25-го процентиля. Собирая данные о производительности RUM из различных условий и устройств, AirSHIFT теперь может проверить, действительно ли новый выпуск функции влияет на производительность приложения или нет.

5. Хакатоны производительности

Несмотря на то, что все эти усилия по оптимизации производительности были важными и результативными, не всегда легко заставить инженерные и бизнес-команды уделять приоритетное внимание нефункциональной разработке. Частично проблема заключается в том, что некоторые из этих оптимизаций производительности невозможно спланировать. Они требуют экспериментирования и мышления методом проб и ошибок.

AirSHIFT сейчас проводит внутренние однодневные хакатоны по производительности, чтобы дать инженерам возможность сосредоточиться только на работе, связанной с производительностью. На этих хакатонах они устраняют все ограничения и уважают творческий подход инженеров, а это означает, что любая реализация, способствующая скорости, заслуживает рассмотрения. Чтобы ускорить хакатон, AirSHIFT разбивает группу на небольшие команды, и каждая команда соревнуется, чтобы узнать, кто сможет добиться большего улучшения производительности Lighthouse . Команды становятся очень конкурентоспособными! 🔥

Фотографии хакатона.

Результаты

Для них подход хакатона работает хорошо.

  • Узкие места производительности можно легко обнаружить, опробовав несколько подходов во время хакатона и измерив каждый из них с помощью Lighthouse.
  • После хакатона довольно легко убедить команду, какую оптимизацию следует сделать приоритетной при выпуске продукта.
  • Это также эффективный способ пропагандировать важность скорости. Каждый участник может понять взаимосвязь между тем, как вы пишете код, и тем, как это приводит к производительности.

Хорошим побочным эффектом стало то, что многие другие инженерные команды в Recruit заинтересовались этим практическим подходом, и теперь команда AirSHIFT проводит внутри компании несколько скоростных хакатонов.

Краткое содержание

Для AirSHIFT определенно было непросто работать над этими оптимизациями, но это определенно окупилось. Теперь AirSHIFT загружает таблицу смен в среднем за 1,5 секунды, что в 6 раз лучше, чем до проекта.

После запуска оптимизации производительности один пользователь сказал:

Огромное спасибо за быструю загрузку таблицы смен. Организация сменной работы теперь стала намного эффективнее.