27 февраля в 11:07

Аналитические данные за пределами аналитики в Wrike


Дата инженер в ожидании задачи на спарке.


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


  • Почему уходят клиенты?
  • Какие пользователи приносят нам деньги?
  • Как развивать продукт дальше?

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


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


Наше решение


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


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

Чтобы превратить это в вид, удобный для SQL, нужно придумать метрики для этих логов и найти измерения, по которым мы будем группировать эти метрики.


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


Время — потому что мы следим за изменением значений метрик.
Идентификатор пользователя — потому что он есть почти во всех логах, и информацию по нему легко агрегировать. Если просуммировать метрики всех пользователей в аккаунте, получим активность аккаунта.


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


  • Если мы хотим оценить количество пользователей, которые купили каждую из подписок, мы спросим: «Сколько пользователей купило бизнес подписку?» «Сколько пользователей купило подписку для маркетологов?».
  • Если мы хотим оценить, конверсию, то спросим: «Сколько человек видело эту форму?», «Сколько человек выбрало какой-то план на этой форме?»

Если подробно изучить структуру приложения, задать все важные вопросы и извлечь на них ответы в логах, мы получим такую структуру:


user_id|date|видел ли пользователь форму?|выбрал ли план на этой форме?|...

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


Мы соединяем все метрики, группируем по идентификатору пользователя, обогащаем их измерениями: страна, локаль пользователя, сколько человек в его аккаунте, платит ли этот аккаунт нам деньги. И загружаем это в orc-таблицу в PostgreSQL.


Структура этой таблицы выглядит так:


user_id|date|metric1|metric2|metric3|metricN|dimension1|dimension2|dimensionN

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


Как это помогает аналитикам?


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


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


Витрина расширяема


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


(user_id, {metric_1_key: metric_1_value, metric_2_key: metric_2_value})

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


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


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


Как именно мы разрабатываем эти модули и с какими трудностями мы столкнулись я расскажу в следующей статье.


Витрина — простая для аналитиков


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


С этой точки зрения питон казался идеальным решением: есть простой язык программирования и Pandas с датафреймами и PandasSQL. Решение казалось идеальным потому, что не все аналитики знают и любят питон, но с SQL или датафреймами умеют работать все.


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


В витрине продуманный нейминг


Мы хотели, чтобы имя метрик было уникальным для похожих метрик, даже когда их станет много. Сейчас у нас 750 метрик, каждый месяц мы добавляем по 50 новых. И пока ни разу не столкнулись с пересечением имён.


Имя метрики состоит из семи частей:


  1. entity — сущность, к которой относится эта метрика:
    act (активность), search (поиск), i (интеграции).
  2. event — что произошло?
    btn_clckd (нажали на кнопку), dashb_open (открыли дашборд)
  3. source — в каком приложении?
    ws (workspace, наше веб приложение), andr (андроид), ios (‘nough said), x (не важно в каком).
  4. path — в какой части приложения произошло действия?
    К примеру, редактирование задачи могло произойти после поиска search или на дашборде dbord.
  5. measure — какое действие мы применяем для агрегации?
    sum, flg, str (конкатенация строк), json (описываем события в json).
  6. unit — какая единица измерения?
    суммировать мы можем количество событий ev, количество задач t или пользователей usrs
  7. details — что-то ещё?
    если одно и то же события можно посчитать по-разному или прочитать из разных логов, стоит указать это здесь.

На деле названия метрик выглядит так:


entity__event__source__path__measure__unit__details — составные части (тикеры) мы разделяем двумя подчёркиваниями, а слова одним, если часть описания не применима к метрике ставим X, например:


view__reports_open__ws__x__cnt__ev__x — количество событий открытия вьюшки репорты с веб приложения (читать лучше с конца).


act__assignment__x__user__flg__fct__non_self_assignment — факт-метрика: если пользователь асайнил другого пользователя, мы пишем 1, иначе 0. Такие метрики отлично агрегируются по аккаунту или по дименшенам.


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


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


И как успехи?


Сейчас у нас 22 модуля, которые написали 7 человек (это больше в два раза, чем команда, которая поддерживает эту витрину).


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


Как это помогает нашим коллегам?


Мы собрали много полезных данных в одном месте и хотели дать к ним доступ заинтересованным людям в обход аналитиков. Для этого мы сделали автоматически обновляющийся дашборд в Tableau Online. В нём не знакомые с программированием люди могут агрегировать метрики, разрезать по измерениям и отвечать на вопросы по использованию продуктов, к примеру:


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


«я нашёл багу в IE, сколько пользователей с ней могли столкнуться?» — можно посмотреть на отдельном дашборде, сколько человек пользуется каждой версией IE.


«сколько раз кликнули на эту кнопочку?» — можно попросить затрекать эту кнопочку и добавить это событие в витрину.


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


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


Как мы работаем с табло?


У нас не получалось использовать в табло схему из базы данных в изначальном виде, потому что:


  • Нельзя автоматически менять названия столбцов на графиках;
  • Нельзя использовать составные части названия столбца для фильтрации метрик;
  • Функциональность объектов Measure Values и Measure Keys ограничена и у нас так и не получилось автоматически добавлять в них новые метрики;

Мы решили эти проблемы с помощью EAV, то есть превратили таблицу в вид:


  • День;
  • Идентификатор пользователя;
  • Название метрики;
  • Полное название метрики, больше 63 символов (ограничение постгреса);
  • Значение метрики;
    По нему строим график
  • Информация о метрике;
    Чтобы найти метрику
  • Информация о пользователе и его аккаунте;
    По ним разрезаем графики

Чтобы в табло можно было фильтровать и строить графики, мы посчитали агрегированные метрики для всех комбинаций значений информации о пользователях. Если у нас два измерения: страна (Россия или Сша) и тип аккаунта (платный или бесплатный), мы агрегируем метрики по всем комбинациям этих значений, для каждой метрики получается 4 строки:


  • Для бесплатных из России;
  • Для платных из России;
  • Для бесплатных из США;
  • Для платных из США.

Даже если на этих пересечениях нули, мы не пропускаем эту комбинацию, иначе нельзя будет построить график или на нём будут пропуски.


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


Более того, чтобы передать в Tableau Online данные, нужно превратить Spark Dataframe в Tableau Data Extract используя библиотеку с устаревшей документацией, а отправить данные на сервер Tableau Online можно только используя Windows, но это мелочи жизни, мы их решили один раз и забыли.


Что это дало компании?


Cамое крутое в таком формате хранения данных то, что их можно анализировать автоматически.


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


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


У нас много планов, как ещё использовать эти данные:


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

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


Картинка в начале — кадр из фильма «Она» Спайка Джонса.

Автор: @EliseeAlex
Wrike
рейтинг 181,80
Wrike делает совместную работу над проектами проще

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

  • 0
    user_id|date|metric1|metric2|metric3|metricN|dimension1|dimension2|dimensionN
    Денормализовать не пробовали?
    • 0

      Как именно денормализовать?


      В dimension1|dimension2|dimensionN мы уже добавили информацию по пользователю и его аккаунту за этот день.


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


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

      • 0
        И что в нем плохого?
  • 0
    С такой гипер нормализованной витриной вам нужна columnstore db.
    Из нее можно выжать максимальный профит в вашем случае.
    • 0

      Спасибо, согласен, column-oriented db может дать большой прирост производительности.


      Cейчас мы используем foreign таблицы в Postgres в формате ORC, это колонко-ориентированный формат хранения данных, он уже дал нам большой прирост в производительности и пока что всем нас устраивает. Перейти с него на отдельную базу данных не так-то просто, аналитики привыкли к синтаксису Postgres и удобно, что можно в одной базе данных без DI-решений сделать сджоинить эту денормализованную таблицу с чем-то ещё.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка