Как мы сделали наше приложение на 80% быстрее

563
Как мы сделали наше приложение на 80% быстрее
Как мы сделали наше приложение на 80% быстрее

В мире, к которому я привык, быстрое соединение WiFi жизненно необходимо. Но когда удовлетворение моих потребностей, подпитываемых Google, перестает быть таким мгновенным, становится ясно, насколько важна эта «быстрая» составляющая.

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

Одно из исследований показало, что 61% пользователей ожидают, что мобильное приложение запустится через 4 секунды или меньше, а еще 37% пользователей заявили, что любые сбои, ошибки или проблемы с производительностью в мобильном приложении заставят их меньше думать о бренде компании.

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

Следствием несоответствия ожиданиям пользователей является не только отказ от приложений — это также приводит к снижению репутации бренда и упущенным возможностям получения прибыли от текущих и будущих пользователей.

Тревога, связанная с производительностью

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

Но для приложения Birdie все обстоит иначе.

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

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

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

Пришло время действовать.

Искать как иголку в стоге сена

Нашей первой и самой насущной проблемой была нехватка данных.

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

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

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

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

Приложение Birdie работает на React Native с Redux и Redux-Persist, поэтому инструмент, который мы искали, должен был хорошо работать с этой системой и обеспечивать глубокий анализ, который нам был необходим. Попробовав несколько различных инструментов, мы остановились на наборе инструментов RUM от Datadog, который позволил нам отслеживать производительность на очень детальном уровне, от производительности конкретных процессов Redux до среднего использования памяти на экране.

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

Панель мониторинга приложений Datadog RUM
Панель мониторинга приложений Datadog RUM

Множество мелких проблем объединиться в одну

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

Некоторые ключевые моменты в наших усилиях по улучшению производительности, мы…

  • Заморозка нашего навигационного стека, когда приложение углублялось в многоступенчатые формы или экраны.
  • Мы активно использовали  React.useMemo  в наших компонентах и мемоизировали селекторы Redux, чтобы избежать ненужных повторных рендеров экрана.
  • Обновление основных зависимостей, таких как react-navigation, чтобы мы могли использовать нативные навигационные стеки (@react-navigation/native-stack).
  • Экспериментировали с переходом от Redux к WatermelonDB для хранения данных на устройстве.

Подавляющее большинство наших изменений привели к определенным изменениям и остаются частью приложения Birdie и по сей день. Но ни одно из них не стало панацеей от проблем с производительностью, к которой мы так стремились.

Больше данных, больше проблем

Мы попытались улучшить производительность за счет уменьшении объема обрабатываемых данных.

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

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

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

Данные, сохраняемые на устройстве с помощью redux-persist, должны проходить через мост React-Native Bridge
Данные, сохраняемые на устройстве с помощью redux-persist, должны проходить через мост React-Native Bridge

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

И… в итоге? Заметной разницы практически не было.

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

Просите о прощении, а не о разрешении

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

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

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

Когда приложение Birdie было только запущено, список разрешений насчитывал максимум 15 или 20. К тому времени, когда мы занялись этим вопросом, для крупной компании этот список мог исчисляться тысячами.

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

Хорошим примером проверки разрешений в приложении является наш компонент PermissionControl, который проверяет заданное разрешение перед отображением любых дочерних компонентов.
Хорошим примером проверки разрешений в приложении является наш компонент PermissionControl, который проверяет заданное разрешение перед отображением любых дочерних компонентов.

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

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

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

Разрешения преобразуются из длинного списка в более оптимизированный объект
Разрешения преобразуются из длинного списка в более оптимизированный объект

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

Время загрузки значительно сократилось, а скорость отклика возросла. Пользователи перестали сообщать о каких-либо проблемах с производительностью. Мониторинг, который нам удалось наладить, сообщил о невероятном увеличении скорости во всех местах, где проверялись разрешения, а скорость некоторых селекторов Redux и рендеринга компонентов увеличилась на 90%, что привело к общему улучшению скорости на 80% на некоторых устройствах!

Сравнение двух версий приложений, проведенное нашими тестировщиками с невероятным количеством данных
Сравнение двух версий приложений, проведенное нашими тестировщиками с невероятным количеством данных

Вот и все. Мы нашли решение. Но в очень неожиданном месте.

Размышления

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

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

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

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

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

На самом деле, мой коллега, который сейчас возглавляет тот самый отдел мобильных приложений, выступил на конференции React Native с докладом именно на эту тему, и я настоятельно рекомендую вам ознакомиться с ним!

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

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

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