Как стать автором
Обновить
269.55
AvitoTech
У нас живут ваши объявления

Как на React две кнопки переводить полтора года

Время на прочтение8 мин
Количество просмотров12K

Всем привет. Меня зовут Илья, я фронтенд-разработчик в команде BuyerX. Раньше я публиковал статью о том, как мы пришли к использованию монорепозитория в нашем юните и какие проблемы решило его использование. В этот раз хочу поделиться чуть менее радостным опытом и рассказать, как получилось так, что потребовалось почти полтора года, чтобы перевести две кнопки со страницы объявления на React.

Причины перевода

Начало 2020 года. Для фронтовых задач в Авито используется две технологии: шаблонизатор Twig и библиотека React

Twig используется давно, и в целом он решает часть проблем:

  • Большая часть кода бэкенда реализована в монолите на PHP. Для рендеринга удобно использовать библиотеку шаблонизатора, совместимую с этим языком. 

  • Шаблонизатор позволяет разделять код на переиспользуемые блоки.

  • Итог работы шаблонизатора — готовая HTML-страница, которую можно вернуть пользователям и поисковым роботам.

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

Кроме того, разрабатывать новые компоненты только на React можно с определёнными ограничениями: если компонент важен для SEO, необходимо верстать его в Twig-шаблоне. Есть решение для этого с использованием SSR, но у него свои ограничения, о которых я расскажу чуть дальше. 

Часть кода написана с использованием нативного JS, часть — с использованием своей библиотеки для работы с React + Twig, а третья — ещё и с использованием внутренней библиотеки наподобие React. Всё это приводило к тому, что иногда код карточки объявления было очень сложно воспринимать. Кроме того, начала развиваться новая платформа для работы с клиентским кодом, которая использовала только React и была отвязана от релизов монолита и могла катиться в любое время.

В зоне ответственности нашего юнита находятся четыре страницы: главная, вертикальная главная (страницы Транспорта, Недвижимости, Работы и Услуг), поиска и карточки объявления. Благодаря титаническим усилиям коллег из других команд юнита, главная, вертикальные главные и поиск уже были переписаны на React и готовы к тому, чтобы выехать из монолита на новую платформу. Однако этого нельзя было сказать о карточке объявления. 

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

Первая попытка

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

Кнопки контактов на странице объявления
Кнопки контактов на странице объявления
Открытие мессенджера после нажатия на кнопку «Написать сообщение»
Открытие мессенджера после нажатия на кнопку «Написать сообщение»

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

  1. После того, как кнопки будут переписаны, необходимо с помощью A/B-теста убедиться, что с метриками контактов всё хорошо. 

  2. Метрики тестов должны быть серыми или зелёными. Серые метрики говорили бы о том, что пользователи не увидели изменений, и изменения не повлияли на их обычное поведение. А зелёные — что пользователям стало лучше. 

Дизайн первого A/B-теста был довольно прост: две группы, контрольная и тестовая. В тестовой группе кнопки контактов не меняли своего поведения. Они были переписаны на React и рендерились сначала с помощью SSR-сервиса, а затем уже на клиенте через гидрацию компонента начинал работать обычный React-компонент. Результат теста оказался красным. 

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

Ухудшился перформанс. Чтобы объяснить проблемы перформанса в первом A/B-тесте, необходимо рассказать, как работает сервис SSR-a в монолите. SSR-сервис работает только с npm-пакетами, поэтому для кнопок был заведён свой пакет. В дальнейшем предполагалось расширить его до всей карточки объявления. В пакете должен существовать файл index-ssr.js, который будет вызываться на стороне SSR-сервиса.

Когда публикуется новая версия npm-пакета, который должен работать с SSR-ом, агент TeamCity триггерит специальную сборку, названную BaaS — Bundle as a Service. BaaS делает сборку пакета с входной точкой index-ssr.js и кладёт её в специальное место, путь к которому возвращает в хранилище статики. В шаблоне монолита заведены специальные теги, которые во время построения страницы парсятся PHP-кодом. Этот код сначала делает запрос к сервису статики (CDN), чтобы получить ссылку на сборку SSR-пакета, а потом с этими данными делает запрос уже к самому SSR-сервису, чтобы получить строку, которая будет подставлена в шаблон.

Упрощённая схема работы SSR в монолите
Упрощённая схема работы SSR в монолите

Основная проблема такого подхода в том, что описанные выше события происходят в шаблоне во время запроса за контентом страницы. А значит, сам запрос отрабатывает дольше. Кроме того, схема не поддерживает отправку нескольких запросов за раз, поэтому добавление нового пакета будет ещё сильнее замедлять загрузку страницы. В итоге такие метрики как TTFB, response, dom_content_load ухудшились на 3-5%. 

Часть функциональности потеряна. В Авито пять направлений или вертикалей: Транспорт, Недвижимость, Услуги, Работа и Товары. В Товары входит всё, что не относится к другим вертикалям. 

У каждой вертикали — свои особенности при запросе контактов. К примеру:

  • В категории «Резюме» при запросе контактов необходимо списывать их из пакета контактов, который заранее покупает пользователь.

  • В категории «Вакансии» при запросе телефона нужно создавать чат в мессенджере.

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

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

При первом запуске переписанных на React кнопок часть сценариев была потеряна. Стало понятно, что в такой конфигурации раскатить A/B-тест не получится, и нужно искать другие пути решения.

На этот момент с начала работ прошло 2 месяца.

Вторая попытка. Фикс перформанса

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

Так как кнопки контактов уже переписаны на React, то одна из новых тестовых групп — это удаление всего Twig-шаблона и рендер вместо него пустого div, в который будут отрендерены кнопки. Такая группа проста в реализации, но, скорее всего, у неё будут проблемы с CLS, ведь контент под кнопками будет сдвигаться вниз. Группу с походом в SSR оставили в тесте для того, чтобы сравнивать метрики с ней и не опасаться за сдвиг контента. 

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

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

Решение проблемы с разделением кода было простым: просто сделать загрузку скриптов опциональной и асинхронной. Вместе с этим поступило предложение от бэкенд-разработчиков вынести получение данных для кнопок контактов в отдельный API-метод. Это решение возможно, так как кнопки контактов не влияют на SEO. Также оно помогло бы и бэкендерам, и фронтендерам. У первых таким образом получалось бы уменьшить количество походов за данными в контроллере, что снижает вероятность падения изначального запроса за страницей объявления. У фронтендеров же отложенная загрузка кнопок улучшила бы перформанс.

С этой мыслью я сделал ещё одну итерацию A/B-теста с асинхронным методом для получения данных о кнопке. Результат наконец-то начал радовать. Да, часть метрик все ещё проседала, например, DOM Complete, но просадки были небольшими. А CLS и DOM Load даже удалось улучшить. 

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

Третья попытка. Фикс продуктовых метрик

Для правок продуктовых метрик я взял за основу последний успешный перезапуск теста с асинхронным запросом. Хоть в этом A/B и удалось поправить перформанс, продуктовые метрики ухудшились ещё сильнее, чем раньше. Так, ключевая метрика нашего юнита — покупатели (buyers) — уменьшились в тестовой группе на 5,5%. 

Для первоначального анализа использовали внутренний инструмент расчёта метрик — A/B-централ. Кроме анализа общих метрик он позволяет посмотреть отдельные метрики каждой вертикали. Выяснилось, что больше всего пользователей терялось в категориях с вакансиями и недвижимостью. Взяв эту информацию на вооружение, я нашёл различия в поведении за прошедшее с начала работ время и внёс их в новую итерацию A/B-теста. Результат стал лучше, но не сильно — падение теперь составило 5%.

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

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

  2. Можно сравнить количество отрисовок кнопок контактов в старой — контрольной — группе и новой.

Результаты A/B-теста оказались интересными: мы действительно теряли часть пользователей на каждом этапе логирования, но суммарные потери не превышали 1%. К тому же факт отрисовки кнопок не гарантирует того, что пользователь на них нажмёт. Тогда откуда потери в 5%?

В отчёте A/B-централа есть пункт, который показывает здоровье A/B-теста. Его суть в том, что если пользователь попал в контрольную или тестовую группу, то он должен быть помечен как участник теста. Здоровье говорит о том, что количество пользователей во всех группах совпадает, то есть они равномерно распределены. Проблема моего A/B-теста заключалась как раз в том, что количество пользователей в группах было неравномерным: в контрольной группе их было больше. 

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

Причина падения метрик выяснилась, осталось понять, как её решить. Здесь на помощь пришла команда DWH, которая занимается обработкой больших объёмов данных, в том числе для A/B-тестов. Ребята решили исследовать то, какие правки необходимо внести в механизм подсчёта метрик. Благодаря ним удалось поменять механизм подсчёта ботов, и с новым механизмом результаты были уже впечатляющими — всего 0,8% покупателей терялось в тестовой группе. При этом метрика была серой, а значит, это падение неоднозначное.

Здесь я оказался спустя 16 месяцев с начала работ.

Результаты спустя 16 месяцев

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

Добро на раскатку теста в таком виде от менеджеров продукта и аналитиков я не получил. Однако удалось договориться что я подготовлю последнюю итерацию A/B-теста c двумя тестовыми группами. Одна — с асинхронными кнопками контактов, вторая — с обычным синхронным, без серверного рендеринга, но с учётом правок перформанса. 

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

Теги:
Хабы:
Всего голосов 14: ↑14 и ↓0+14
Комментарии29

Публикации

Информация

Сайт
avito.tech
Дата регистрации
Дата основания
2007
Численность
5 001–10 000 человек
Местоположение
Россия