DevGang
Авторизоваться

Создание высокопроизводительных крупномасштабных веб-приложений

Этот пост о правильной производительности при создании больших и сложных веб-приложений. Существует много советов по оптимизации для каждой последней унции производительности в интернете, но это не очень поможет, если у нас уже нет "хорошего" начала.

Оптимизация (существительное) действие наилучшего или наиболее эффективного использования ситуации или ресурса

- Оксфордский словарь английского языка

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

Этот пост о достижении «хорошо»; попадание в положение, в котором оно работает, по крайней мере, довольно хорошо, без крупных или фундаментальных проблем, которые трудно и / или дорого решить. Это включает в себя все аспекты доставки программного обеспечения. Затем можно обратить внимание на прибыльную оптимизацию, а не на оптимизацию с низкой отдачей или переделку.

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

Загрузка нужного количества нужных данных

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

Загрузка данных сопряжена со стоимостью всего стека приложений: нагрузка на базу данных, обработка / память сервера, полезная нагрузка / время сети и обработка внешнего интерфейса. Нагрузки гораздо больше, чем требуется, лучше всего избегать. Массовая загрузка, кратная необходимой сумме при запуске или при навигации, плохо масштабируется в зависимости от рабочей нагрузки приложения или доступного объема данных, что влияет как на внешний интерфейс, так и на внутренний .

Ожидаемый объем данных необходимо учитывать при разработке и реализации функций; например, для сетки данных или диаграммы, сколько всего строк / точек и насколько велика каждая из них со значениями своих атрибутов. Может потребоваться разбиение на страницы или другая форма инкрементальной загрузки по требованию. Модернизация этих систем часто бывает трудной и отнимает много времени (особенно после того, как другие функции были наслоены сверху), поэтому выбирайте с хорошим знанием текущих / прогнозируемых объемов данных и пользовательской нагрузки, а также будущих планов функциональности. В любом случае запросы данных (как на стороне клиента, так и на стороне API) должны быть ограничены, чтобы предотвратить непреднамеренные ресурсоемкие запросы для выгрузки всей базы данных через API и интерфейс.

Производительность обработки браузера значительно улучшилась в течение большей части последнего десятилетия, но это все еще не то место, где можно выполнять тяжелую обработку для вычисления / получения отображаемых данных из загруженных данных. Существует только один основной поток, и это «поток пользовательского интерфейса», тесно связанный с циклом рендеринга - когда он заблокирован (занят работой), обновления экрана приостанавливаются, что приводит к ухудшению работы пользователя. Загружайте необходимые данные, а не исходные данные, из которых они получены. Если это неизбежно, выгрузка обработки обработки веб-работнику может помочь или исследовать WebAssembly.

Существующие API, которые необходимо использовать, могут не соответствовать вышеупомянутым принципам; возможно, они были разработаны для сценариев, отличных от клиентских приложений, или для тяжелых / толстых собственных клиентских приложений. В таких случаях может потребоваться серверное приложение «backend for frontend» . Это фасад над теми API, которые выполняют работу по обработке / вычислению/преобразованию и предоставляют API, более подходящий для потребления frontend-клиентом. Имейте в виду, однако, что это может не помочь с внутренними последствиями загрузки слишком большого количества данных или увеличения нагрузки на системы, не предназначенные для обслуживания пользовательских приложений.

Потоковые данные

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

Интерфейс должен, как правило, подписываться на определенные данные, которые ему нужны в настоящее время, и получать только эти данные. Это не исключает трансляцию; Возможно, практическое правило заключается в том, что трансляция предназначена для событий / уведомлений, а не для значительных фрагментов данных.

Поскольку сервер контролирует частоту отправляемых данных, а также контент, необходимо убедиться, что веб-интерфейс не требует принудительного ввода большего объема, чем он может обработать. Помимо проблем с производительностью (сетевой трафик, блокировка основного потока), при отправке / отображении более нескольких обновлений в секунду пользовательское значение невелико (или даже отрицательно). В быстро обновляемых источниках данных (например, финансовых торговых ценах или значениях датчиков) частоту нажатия можно смягчить путем регулирования (на стороне сервера). Соответствующая частота может быть выбрана на основе характера данных и контекста, в котором они используются; Не все нужно обновлять так быстро.

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

API дизайн

Разработка API с учетом вариантов использования приложений. Разъединения здесь могут привести к чрезмерным сетевым вызовам и создать трудности в эффективной реализации бэкенда, помимо проблем с производительностью.

Когда API-интерфейсы являются сильно нормализованными или детализированными, пользовательские интерфейсы, как правило, должны совершать множественные вызовы, чтобы получить необходимые данные или выполнить логические единицы работы. Это приводит к «болтливому» взаимодействию, когда действия пользователя вызывают много вызовов, некоторые из которых не могут быть выполнены до тех пор, пока другие не завершат (если их параметры зависят от ответов на предыдущие вызовы). И наоборот, когда операции API являются слишком широкими / грубыми, пользовательские интерфейсы вынуждены извлекать больше данных, чем необходимо в конкретном контексте. Для извлечения данных GraphQL может использоваться для запроса только необходимых полей данных и указания связанных данных для возврата вместе с ним.

Параллельное выполнение множества отдельных запросов - гораздо меньшая проблема с мультиплексированием в HTTP / 2 , однако конкретные API-интерфейсы пакетной обработки могут предоставить возможности для эффективной обработки запросов на стороне сервера. Согласование дизайна API с вариантами пакетного использования также имеет преимущества помимо производительности, основным из которых, вероятно, является транзакционность. API поиска может возвращать согласованный набор данных в один момент времени (например, остатки на счетах), или API операций может предоставлять элементарные (все или ничего) операции (например, размещать эти заказы).

Устройства и сети

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

Это во-первых требует изучения того, что такое устройства/сети. Для внутренних приложений это довольно просто, но для пользовательских приложений требуется некоторая комбинация знания вашей аудитории, сбора данных из существующих приложений (где это возможно) и любых других доступных / собираемых данных и идей (например, скорости интернета по странам).

Зная это, регулярно используйте похожие устройства и условия сети во время разработки, тестирования и демонстрации. Делая это, каждый испытывает приложение так, как пользователи, и быстро заметит любые проблемы. Типичные компьютеры для разработки имеют более высокие характеристики, чем большинство пользователей, и находятся в той же локальной сети, что и бэкэнд. Ваш телефон может иметь более высокие характеристики, чем у обычного пользователя, чаще заменяться и работать в отличной сети 4G в городе. Эмулированные мобильные браузеры на настольных компьютерах работают намного лучше, чем настоящие мобильные устройства среднего уровня. Инструменты разработчика браузера могут частично имитировать более медленные устройства посредством регулирования, но это скорее удобство, чем замена.

Существуют различные инструменты для моделирования задержки сети / пропускной способности сети на уровне браузера (devtools, расширения) и на уровне операционной системы (например, win-shaper). Однако, вероятно, не практично включать их постоянно во время работы. При локальной разработке рассмотрите возможность добавления искусственных задержек запросов с помощью прокси-сервера (например, встроенного в веб-пакет devserver ) или программно в вашем приложении. Это помогает привлечь внимание к тому, когда делаются сетевые запросы. С почти мгновенными ответами локально и сложным кодом, где причина и следствие отделены (например, Redux), иногда может быть легко не понять, что то, что вы делаете, на самом деле включает сетевой вызов (или цепочку из них). Это может быть неожиданным / ненужным, или может потребоваться добавить состояние загрузки / индикатор.

Среды разработки и тестирования

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

Разработка эффективного программного обеспечения требует ситуации, когда ему может быть разрешено работать эффективно. Когда окружающая среда предотвращает это, проблемы с производительностью программного обеспечения трудно отличить от тех, которые вызваны окружающей средой. Замедление становится нормальным и терпимым. Новые проблемы с производительностью не выделяются. Любая конкретная медлительность объясняется, например, медленным бэкендом среды тестирования или неадекватно подготовленной виртуальной машиной разработки. Контраст с быстрой средой разработки / тестирования: все, что идет медленно, необычно, выделяется и вряд ли будет отклонено без расследования.

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

Возможность наблюдения

Мы можем видеть только производительность, как мы ее испытываем. Чтобы понять это и увидеть / понять, как пользователи воспринимают это, его нужно сделать видимым. Только тогда мы действительно сможем узнать, как у нас дела в реальном мире и почему.

Мониторинг реального пользователя (RUM) это пассивная коллекция измерений производительности из внешнего интерфейса, пока пользователь его использует. Само приложение измеряет время, необходимое для выполнения различных значимых действий пользователя, и отправляет их в хранилище данных вместе с некоторой контекстной информацией. Затем их можно сравнивать с ожиданиями, анализировать и отслеживать на предмет внезапных изменений или тенденций. Их также можно изучить, чтобы помочь диагностировать проблемы или найти области для улучшения: разбивка временных интервалов вместе с контекстными данными позволяет понять производительность (хорошую или плохую) и приписать конкретным причинам. Например, мы могли бы обнаружить, что функция неэффективна на мобильных устройствах, что определенные действия пользователя являются медленными для некоторых типов пользователей, потому что вызов API медленно отвечает,

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

Дорогостоящая работа во время выполнения

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

Некоторые вещи дорогие, другие вещи дешевые. Вероятно, основные дорогостоящие вещи, о которых следует знать в веб-интерфейсе: блокировка основного потока большими кусками работы (блокировка цикла обработки событий и рендеринга), манипулирование DOM и сетевые запросы. Об этом нужно подумать. К дешевым вещам относятся итеративные массивы; Используемый подход не окажет существенного влияния на производительность, поэтому не нужно беспокоиться - можно использовать самый простой / чистый подход.

Библиотеки и структуры приносят свои дорогостоящие вещи, о которых нужно знать, поэтому стоит иметь представление о том, что происходит под капотом и конкретными рекомендуемыми практиками. Например, создание объектов Moment.js является дорогостоящим по масштабу, а различные операции в jQuery (вам, скорее всего, это все равно не нужно) имеют дорогие побочные эффекты для устранения ошибок браузера. Будьте особенно внимательны с новыми фреймворками (прошлый пример: AngularJS с его наблюдателями и циклами дайджеста) или новыми шаблонами / техниками (например, микро-интерфейсами ), чьи возможности еще не так хорошо изучены . Большинство фреймворков, вероятно, выполнят намного больше кода, чем вы могли бы ожидать в ответ даже на самые простые события приложения, и, как правило, это не будет проблемой в современных браузерах.

Код, который трудно изменить, требует особой осторожности, конкретный пример - код утилиты или фреймворка, используемый другим приложением во всем приложении. Мы можем оптимизировать его внутренние компоненты без изменения поведения, но фундаментальные неэффективности в его поведении могут быть трудными и рискованными для изменения из-за того, что другой код полагается на это неэффективное (и, возможно, непреднамеренное / недокументированное) поведение. Здесь стоит профилировать и проверять поведение заранее, даже если это (пока) не вызывает проблем с производительностью - это не будет преждевременной оптимизацией.

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

Тестирование и пользовательские персонажи

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

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

Персоны пользователей часто используются при рассмотрении функциональных требований и UX, и они также могут применяться к производительности. Определив ориентированные на производительность (или добавив их в качестве аспекта к существующим), мы можем лучше учитывать производительность на протяжении всего процесса разработки. Например: пользователь с бюджетным телефоном, сельский пользователь с медленной сетью, пользователь из другой части света, опытный пользователь, накопивший большой объем данных, профессиональный пользователь с интенсивным использованием. Мы также можем использовать их для настройки соответствующих готовых к использованию пользователей в средах разработки / тестирования, которые можно использовать для тестирования приложения в сценариях этих персонажей.

Культура и требования

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

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

В больших приложениях нецелесообразно определять и тестировать требования к производительности для каждого возможного взаимодействия и сценария. Однако могут быть определены общие нефункциональные требования к производительности. Время отклика и завершения - это пример - время, необходимое для подтверждения и завершения действия пользователя. Следуя модели RAIL , мы могли бы, например, потребовать, чтобы щелчок был подтвержден в течение 100 мс, а сработавшее действие (например, изменение вида, размещение заказа) было выполнено в течение 700 мс. Иногда должны быть исключения, в других случаях, однако, могут быть разработаны альтернативные взаимодействия, которые также приемлемы, но творчески обходят ограничения (например, медленные восходящие системы).

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

Заключительные мысли

Производительность может быть сложным аспектом создания программного обеспечения. Иногда это будет хорошо, если особо ничего не делать, в других случаях это может вызвать большие проблемы, если этому не уделять внимание на каждом этапе пути. Довольно легко избавиться от фундаментальных проблем (или ярлыков) на ранней стадии, только для того, чтобы они постепенно начали кусаться позже, когда приложение растет, - в этот момент основные проблемы и решения трудно исправить.

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

Источник:

Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу