Всем привет! Меня зовут Саша Тотилас и я крашу иконки руковожу командой разработки в hh.ru. Хочу поделиться с Хабром результатами A/B-эксперимента: при оптимизации одного из экранов нашего приложения мы ускорили загрузку контента и выяснили, как это влияет на продуктовые метрики, а также собрали интересные инсайты.

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

Про SRE в мобильной разработке

Мы уделяем мобильной разработке много внимания. Больше миллиона уникальных пользователей ежедневно заходят в приложения hh на Android и iOS, над которыми усердно трудятся 14 команд. Показатель crash-free у нас – 99,98%. На таких масштабах качество приложений начинает играть важную роль, при этом обеспечивать это качество без хороших практик становится всё сложнее. Здесь на сцену выходит SRE, а наш эксперимент по оптимизации – лишь часть этого большого направления.

В двух словах, SRE или Site Reliability Engineering – это обеспечение надёжности, доступности, производительности и масштабируемости сервисов. Чаще эту аббревиатуру связывают с миром серверов и бэкенда, но по нашему опыту это актуально и для мобильной разработки. Чтобы погрузиться в эту тему, можно почитать небольшую книжку от ребят из Google (буквально 30 страниц).

Почему SRE заинтересовал нас:

  1. Главная причина – конечно же, деньги. Технические проблемы влекут за собой падение продуктовых метрик, а с ними и выручки. А техническая оптимизация, наоборот, увеличивает эти метрики (но это не точно, надо экспериментировать).

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

  3. SRE-практики помогают нам с релизной безопасностью. Они включают фича-тогглы, проведение A/B-экспериментов, постепенную раскатку – всё, что даёт инженерам спать чуть спокойнее. 

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

При этом действительно развитый, мощный SRE – это многолетний путь. Выглядит этот путь примерно так:

  1. Сначала команды «тушат пожары» – реагируют на жалобы пользователей, но часто с повторением инцидентов где-то в другом или даже в том же самом месте. 

  2. Потом переходят к более зрелому подходу: вводят метрики, собирают логи, трассировки. Определяют SLI и SLO (Service Level Indicators, Service Level Objectives), то есть цели по собираемым метрикам. Появляются алертинг, документация по инцидентам, бюджетирование ошибок.

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

  4. Далее SRE становится неотъемлемым элементом проектирования, код-ревью, планирования ещё до разработки продуктовых фич. При этом выявляются узкие места в надёжности, прогнозируется бюджет ошибок, появляются SRE-платформы, которые сами определяют SLI, вычисляют SLO и помогают в коррекции каких-то ошибок.

  5. Ну и заканчивается всё на хайповой теме: относительно недавно появилось AI-driven SRE. Про неё рассказывали, например, представители Nvidia и Cleric: ИИ помогает им в дедупликации событий, корреляции алертов, сливая их в один алерт, чтобы избавиться от лишнего шума. Платформа ищет похожие инциденты и аномалии, анализирует и оценивает первопричины, выявляет критичность проблем, ищет и назначает исполнителей для их решения. И при этом дежурным даётся доступ к чату, в котором можно запросить весь контекст по проблеме, задать вопросы. Есть даже автовосстановление для низкорисковых ошибок!

Где на этой карте находимся мы? А мы зачастую всё ещё тушим пожары. И при этом задаемся вопросом: а стоит ли вкладываться в развитие SRE-практик? Чтобы определиться, нужен эксперимент, о котором расскажу далее. С его помощью можно измерить профит и понять, стоит ли овчинка выделки. 

Подготовка A/B-эксперимента

Метрика

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

Сегодня нас интересует Time to Content (также известный как Largest Contentful Paint) – время от инициализации экрана до появления основного контента. Есть UX-исследования о влиянии скорости загрузки на поведение пользователей. А значит, метрика Time to Content может коррелировать с продуктовыми показателями и даже приводить к росту выручки – просто за счёт технических оптимизаций. 

градиентом выделены те метрики, которые мы уже так или иначе мониторим
градиентом выделены те метрики, которые мы уже так или иначе мониторим

Замысел

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

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

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

  • Результат загрузки: пользователь увидел вакансии, ошибку, пустую выдачу или вообще ушёл с экрана, не дождавшись контента.

  • Триггер загрузки контента: открытие экрана, рефреш, ретрай по кнопке после ошибки.

  • Триггер логирования: юзер дождался загрузки или, наоборот, не стал ждать её окончания, перезагрузил страницу или ушёл с экрана.

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

  • Тип сети: Wi-Fi, мобильная сеть, был ли включен VPN у пользователя. 

  • Качество сигнала: от очень плохого до отличного.

Инструменты

Хорошо, с собираемыми данными определились. А чем нам их собирать и как  анализировать?

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

Не хотелось вдаваться в подробности реализации базы данных – но тут всё-таки важно немного углубиться.

Под продовой БД я имею в виду Hadoop. Это экосистема для хранения больших данных, которая круто масштабируется и дёшево обходится при локальном развёртывании. Но для хранения временны́х метрик она не подходит. Для них лучше использовать time-series базы данных вроде InfluxDB или Prometheus. Они позволяют делать реал-тайм мониторинг и алертинг, они более эффективны для хранения временны́х данных и для их стягивания, у них есть механизмы ретеншена (время жизни данных) и даунсемплинга (уменьшение размерности данных для их эффективного хранения и быстрого стягивания).  

Как бы они ни были хороши, мы всё равно выбрали Hadoop. Просто это позволяло дёшево и быстро запустить эксперимент, так как вся инфраструктура уже была готова. А чтобы доставать из Hadoop данные SQL-like запросами мы воспользовались движком Trino.

Что ещё мы использовали:

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

  • Querybook (коллаборативный SQL-редактор). Позволяет выполнять SQL-запросы к данным, агрегировать их и вручную считать метрики. Использовался для расчёта Time to Content, так как технические метрики ещё не были заведены в ABT.

  • ChatGPT (reasoning). Помогает генерировать SQL-запросы, строить графики, анализировать и подсчитывать данные, а также формулировать гипотезы и выводы.

  • Grafana. Обеспечивает построение дашбордов по техническим метрикам. При работе с Hadoop графики загружаются медленно, поэтому планируется переход на InfluxDB.

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

Результаты и их интерпретация

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

красное – не всегда плохое
красное – не всегда плохое

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

По технической стороне у нас, конечно, сплошной позитив: половина загрузок теперь проходит на 5% быстрее, а самые долгие загрузки (99-й перцентиль) сократились в 2 раза. Помимо ускорения мы получили много полезной информации – подробнее об этом ниже.

Зарываемся в хвосты

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

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

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

А вот другой странный хвост. Эта цифра – 99-й перцентиль времени загрузки контента в миллисекундах после смерти процесса.

Это примерно 100 минут. Как будто пользователь 100 минут ждал загрузки контента! Это редкая аномалия, но она случается – это баг самого сбора метрик. И такое тоже следует расследовать и фиксить.

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

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

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

  3. Гранулярные замеры. Например, от инициализации экрана до отправки запроса на бэк или от получения ответа от бэка до рендера контента. Это позволило бы более точно локализовать проблему и найти бутылочное горлышко во флоу. 

Любопытные подробности

Что нам ещё удалось вытянуть из полученных данных?

Посмотрим на медиану времени загрузки в зависимости от качества сигнала. Казалось бы, чем лучше сигнал, тем быстрее должен грузиться контент, НО по факту мы увидели, что с плохим уровнем сигнала пользователи загружались быстрее, чем с хорошим, а со средним – сопоставимо с очень плохим. Странно? Ещё бы!

Всё просто: если для Wi-Fi «качество сигнала» определяется более-менее точно по скорости соединения и мощности сигнала (гуглить RSSI), то для мобильных сетей довольно эмпирически по поколению сети. Такое «качество» может быть далеко от действительности. Почему бы также как и для Wi-Fi не опираться на мощность сигнала? Дело в том, что для этого потребуется запрос дополнительного разрешения у пользователя, и на такое мы пока идти были не готовы.

А теперь посмотрим на влияние оптимизаций в зависимости от триггера загрузки по 90-му перцентилю: при простом открытии экрана контент стал отображаться на 7% быстрее, при рефреше свайпом – на 13%, а при клике по кнопке перезагрузки после ошибки загрузка контента... замедлилась аж на 26%, Карл! Не так уж всё радужно, да? На самом деле есть НЮАНС. Открытий экранов были миллионы, рефрешей сотни тысяч, а перезагрузок – всего тысячи. 

К тому же, если посмотреть распределение пользователей по контрольной и экспериментальной группам, то выяснится, что событий перезагрузок по кнопке на 13% меньше в группе с оптимизациями. Следовательно, нельзя сходу доверять процентам после сегментации. Сначала нужно их валидировать на достаточность (набрали значимое количество событий) и на достоверность (в эксперименте и в контроле примерно одинаковое количество событий).

Ещё мы узнали, что пользователи с Wi-Fi грузятся на 12% быстрее, а VPN замедляет загрузку на 12% для мобильной сети и 16% для Wi-Fi. Что любопытно: пользователи с VPN чаще не дожидаются окончания загрузки, в то же время пользователи с Wi-Fi дожидаются чаще всех. 

После эксперимента пользователи стали чаще дожидаться окончания загрузки: не дождавшихся было 1.6%, а стало 1.4% – UX-сигнал улучшился. Казалось бы, всего на 0.2%? Но на самом деле в масштабе миллионов событий кумулятивно на продолжительном периоде это даёт неплохой профит.

Если посмотреть распределение вот этих не дождавшихся, то мы увидим, что практически половина пользователей уходит в течение 1 секунды. При этом 90% пользователей уходят с экрана, не дождавшись загрузки, в течение 12 секунд. Что бьётся с UX-исследованиями, согласно которым 1 секунда – это предел, в течение которого пользователи всё ещё воспринимают флоу как единое целое, а 10 секунд – это предел человеческого внимания при использовании ПО.

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

Можем взять, например, две секунды – увидим пересечение где-то на уровне 99%. То есть 2 секунды может подождать 99% пользователей, а 1% уходит с экрана. На девятой секунде уже только 30% пользователей дожидаются загрузки. Но что за скачки происходят дальше? А это циклы тайм-аутов и ретраев. То есть в фоне происходят ошибки, в фоне происходит перезапрос, и, возможно, со вторым запросом пользователь всё-таки дождётся контента и увидит его.

По этому графику можно достаточно тонко настраивать таймауты и ретраи так, чтобы больше пользователей дожидались окончания загрузки и продолжали пользоваться фичей. Также по survival-кривой можно выставлять алертинг. Например, можно задать такой SLO: 90-й перцентиль времени загрузок должен соответствовать не менее 90% по survival-кривой. А если человеческим языком: в 90% загрузок мы допускаем от 0 до 10% «недождавшихся» пользователей.

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

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

Итоги приключения

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

Что со всем этим мы будем делать дальше? Расследовать и фиксить аномалии метрик и баги. Проведём ещё несколько экспериментов, определим наши SLI, SLO, бюджеты ошибок, улучшим мониторинг, алертинг. Наконец, добавим тесты производительности и включим эти технические метрики в нашу A/B-платформу, чтобы отслеживать технический регресс при выпуске будущих продуктовых фичей. 

Что я могу посоветовать тем, кто всё ещё тушит пожары:

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

  • Экспериментируйте с низковисящими фруктами. Бывает, что за 20% усилий вы можете добиться 80% оптимизаций – только нужно понять, куда именно их приложить. Когда ещё нет перформанс-тестов или мониторинга технических метрик, стоит начать с популярных и критичных функциональностей, а также обратить внимание на то, что вызывает больше всего жалоб от пользователей.

  • Анализируйте данные, ищите инсайты. 

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

Вот такой эксперимент. Если у вас есть мысли, как ещё можно интерпретировать предложенные результаты, или есть собственные истории подобных экспериментов – было бы интересно их услышать, напишите в комменты ;-)

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


  1. TachyonTi
    17.12.2025 06:04

    К слову сказать hh ваш давно стал "помойкой" вакансий. Однотипные вакансии по типу консультанты, программисты, банкиры и тд.. Гораздо больше полезных вакансии от крупных компаний на сайте работа России, но приложение у них глючное.


  1. Zara6502
    17.12.2025 06:04

    есть несколько вопросов:

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

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

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

    4. расскажите в какой параллельной реальности данные на смартфоне должны показывать контент за 1 секунду? или это чисто московский прикол? У меня на полёт ICMP пакета до яндекса тратится четверть секунды (250 мс), а размер этого пакета просто микроскопический. Каким же образом вы умудряетесь пропихнуть ваш контент за секунду если это метрика вообще не зависящая от вашего сайта или приложения? Например hh.ru в браузере ПК, но через мобильный интернет, без кеша, страница загружается 12 секунд, что вполне сопоставимо с тем объёмом данных что у вас размещён на странице.

    5. ну и конечно же риторическое замечание - 99% вакансий абсолютно или пустые или мошеннические. Пустые - это значит работник отдела кадров отрабатывает свою зарплату и создаёт вид активной деятельности складывая всё в стол (типичный результат, когда тебе перезванивают через пол года - год). Как по мне у таких вакансий должен быть рейтинг, чтобы я как в такси мог поставить число звёзд и быстро уронить рейтинг той вакансии которая пустая. Но вы так не сделаете никогда, так как я вам не плачу, а они платят, и было бы странным кусать руку которая платит. Мошеннические - это практически всё временное или вакансии не на полный день. Как правило клиента уводят в мессенжеры и пытаются обработать там. В лучшем случае вы потратите немного/много времени, в худшем на вас даже кредит повесят.

    В конце нулевых отклик на вакансию практически железно приводил или к разговору по телефону или приглашению на собеседование. Сейчас реальная возможность сходить на реальное собеседование - это только государственные ЦЗН, но там нет огромных зарплат, но хотя бы можно трудоустроиться (лично я через ЦЗН нахожу работу с 2011 года, с сайтов типа hh не то что найти работу, я на собеседование ни разу не попадал). Но да, как бизнес по заработку - вполне годный проект.


  1. BoomerCore
    17.12.2025 06:04

    У меня, в отличие от предыдущих выступающих, чисто технические вопросы

    1. Если это тест, то нде нулевая гипотеза, которую вы тестировали?

    2. Если это тест, то где числа, на основании которых можно делать (посторонним) какие-то выводы о качестве теста, и не было ли там "В результате опроса на сайте установлен, что 100% пользователей пользуются интернетом". Ну там p-value, коэффициент детерминации, CI, прочие скучные слова...

    3. Что, кроме синтетических непонятных "продуктовых метрик" сделал полезного в итоге ваш тест? Миллионы посетителей с мобил — это соискатели, они деньги вам несут несравнимо меньше, чем контракты работодателей

    Нет верифицируемых чисел, нет описания эксперимента ("изменили экран" — не описание) и методики — нет доверия, написать-то можно все что угодно.

    Ну и на посошок, в

    метрика Time to Content может коррелировать с продуктовыми показателями и даже приводить к росту выручки R

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