Всем привет. Меня зовут Илья, я фронтенд-разработчик в команде 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 и раскаченные в прод кнопки, поэтому результаты любой из двух этих групп меня устраивают. Пожелайте мне удачи, будем надеятся, что это действительно последний перезапуск теста!

Комментарии (29)


  1. sinneren
    20.10.2021 13:42

    У меня один вопрос. На кой авито сдались метрики вебвиталс?
    Суть вебвиталс - тирания гугла с навязыванием метрик ради продвижения в поиске. Но вам же нет дела до поисковой выдачи, или есть?


    1. IlyaNikk Автор
      20.10.2021 17:11

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


      1. sinneren
        20.10.2021 18:17

        А как вы обходите вред от ГА, ГТМ и прочей чуши маркетинговой? Ставили эксперимент грузить или по таймауту или по первому действию (скролл\тачстарт), но аналитика сильно просела - не вариант. Сейчас стоит на onload, но толку от этого ноль, особенно затроттленные устройства страдают.


        1. IlyaNikk Автор
          21.10.2021 11:45

          У нас сейчас тоже стоит инициализация аналитики gtm на onload. Сам не замерял насколько сильно оно влияет на производительность, но, видимо, стоит обратить внимание. Спасибо за наводку


          1. sinneren
            21.10.2021 15:09
            +2

            Ну вот я замеряю в дев режиме, когда даже сервах плохой по сути, и выбиваю почти сотку. Стоит включить аналитику, и метрики, tbt особенно, улетают в космос, а перворманс скор падает на 2 порядка. То есть если было 85, стало 40. tbt был 150, стал 2400(!!!). + разные рекламные штуки типа флоктори сторонние, которые портят cls до кучи. Казалось бы, событие onload, пока оно загрузится, да еще и пока стриггерится по таймауту - не должно влиять, но если у юзера очень плохой инет, то вполне себе возможно, crux аналитика показывает провалы cls именно на этих страницах, где реклама, при том, что ручные замеры не показывают этого, даже с тротлингом.


  1. nin-jin
    20.10.2021 14:40

    Вот открываю я ваш чат и вижу удручающую картину.

    Вам определённо есть куда стремиться.


    1. IlyaNikk Автор
      20.10.2021 18:04
      +2

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


      1. nin-jin
        20.10.2021 18:55
        -3

        Ну вот второй пример сделан за двое суток двумя фронтендерами. Неужели на Реакте так сложно сразу сделать нетормозящее приложение? Не верю!


        1. IlyaNikk Автор
          20.10.2021 20:55
          +1

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


          1. nin-jin
            21.10.2021 10:02
            -2

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


            1. IlyaNikk Автор
              21.10.2021 11:54
              +1

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


        1. sinneren
          21.10.2021 10:52
          +6

          а вы добавьте яндекс метрику, а потом ГА, потом ГТМ, потом ВК пиксель, мейл, и прочие штуки аналитики, а потом расскажите как вы импрувили


          1. nin-jin
            21.10.2021 18:59
            -2

            И зачем мне добавлять этот мусор в приложение?


            1. sinneren
              22.10.2021 09:48

              вам в вашем петпроекте чатика за 2 дня не знаю, а в наших серьёзных магазинах - нужно.

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


              1. nin-jin
                22.10.2021 12:21
                -2

                Ну, вы сами решили работать с идиотами. Не самое светлое решение, конечно же.


                1. anna_lesnykh
                  22.10.2021 17:05

                  Давайте не будем друг с другом ругаться :)


    1. sinneren
      20.10.2021 18:16

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


      1. nin-jin
        20.10.2021 18:52
        +3

        А вы сайты для гугла делаете или для пользователей?


        1. sinneren
          21.10.2021 10:51

          это только метрика и она совсем не отражает реальность


          1. nin-jin
            21.10.2021 18:59
            -2

            На глазок одно грузится несколько секунд, другое мгновенно.


            1. sinneren
              22.10.2021 09:47

              более 20 секунд выкачивает Я.М.


              1. nin-jin
                22.10.2021 12:23

                У меня ЯМ юблоком режется, как и у многих ваших пользователей.


                1. sinneren
                  22.10.2021 13:37

                  юблок и большинство уже противоречие, тогда уж адблок, а там, вроде, не режет аналитику по умолчанию, только рекламу (ну или опция часто есть эта) + приоритет мобилке, а там никаких юблоков, адблоков в 98% нет, 1 % какое-то приложение , 1% оператор, который тоже не порежет ЯМ


                  1. nin-jin
                    22.10.2021 17:45

                    У вас парсер барахлит, я не говорил про большинство.


    1. icecube092
      21.10.2021 01:40

      Что за утилита?


      1. nin-jin
        21.10.2021 04:58

        Chrome Dev Tools > Lighthouse


      1. TangoPM
        21.10.2021 10:32

        Google Chrome Devtools )))


  1. miha92
    20.10.2021 17:00
    +2

    Было очень интересно читать! Спасибо, что поделились с нами всеми и успехов в дальнейших трудах!


  1. muturgan
    22.10.2021 00:32
    -2

    Фронтенд это просто, а фронтендеры недопрограммисты ;)