Принятие решений на основе данных является неотъемлемой частью работы аналитика. Данные помогают сделать это быстро. Но что если объём данных достигает десятков петабайт? Подобная задача становится не такой тривиальной, как может показаться на первый взгляд. Как масштабировать работу с данными в продуктовых командах? Как быстро найти инсайты в куче данных? Какие инструменты могут быть полезны для аналитика?
Заинтригованы? Добро пожаловать в мир аналитики больших данных.
Меня зовут Дима, я руковожу направлением аналитики в команде Mail.ru в VK. Попробую ответить на заданные выше вопросы и расскажу о нашем Data Driven-подходе к работе с данными.
Проблемы работы с данными
Продукт в компании непрерывно меняется, знания о его данных могут накапливаться в самых простых инструментах: Confluence, Google-документах, личных заметках разработчиков. Знания могут передаваться «из уст в уста» от разработчика к аналитику, от продакт-менеджера к СЕО и т. д. Эти знания о данных могут устаревать или вовсе теряться, что самое неприятное.
В нашей компании существует немало продуктов, и многим уже по несколько десятков лет. А знаний об этих продуктах и людей, работающих с этими знаниями, ещё больше.
У Почты Mail.ru есть Web-версия, а также мобильные приложения под iOS и Android. Эти части продукта разрабатываются разными командами. Для описания поведения пользователя требуется консолидировать знания о данных в одном удобном месте для их последующего анализа. Если собирать знания из разных команд, то очень сложно следовать каким-то стандартам и шаблонам. Непонятно как найти то, что требуется здесь и сейчас, особенно если изменения касались продукта пару лет тому назад, а людей, которые, хоть что-то помнят об этом, попросту нет рядом. У кого узнать ответ на вопрос: где данные? Какая-то информация постоянно нужна. Например, требуется получить сегмент пользователей Почты Mail.ru для новогодней поздравительной рассылки. Как подготовить этот сегмент? Как быстро передать его в сервис для рассылки писем? Как передать данные безопасно, не копируя их на личный ноутбук? Как понять что в данных нет технических проблем и они регулярно поставляются в хранилище?
Столько вопросов! И это лишь часть. Давайте погрузимся глубже в наши данные и инструменты работы с ними.
Как устроена работа с данными в VK?
Данные тесно связаны с нашими пользователями и их активностью. Мы называем такие данные продуктовыми событиями: регистрации, входы в Почту, переходы из Почты в Календарь, переходы по ссылке в видеозвонки и т. д. Эта информация неоднородна, у каждого события могут быть десятки различных параметров, часть из которых может быть и общей. Например модель устройства, версия браузера, разрешение экрана, ID пользователя.
Нам требуется анализировать события в любых разрезах. Если разрезов для конкретной задачи не хватает, нужно иметь возможность получать их быстро. Нужно считать продуктовые метрики DAU, Retention, Churn по мобильным приложениям, типам устройств, версиям приложений. Всё зависит от конкретных продуктовых задач.
Сердце наших данных — реестр событий. Это self-сервис, мы назвали его Metida. Вы скажете: «Это же просто каталог данных, ничего нового!» C одной стороны, да, но с другой — для нас это нечто большее. Можно провести аналогию с существующими open source-решениями, такими как Amundsen, Datahub от LinkedIn, Apache Atlas и другими. Об опыте крупных компаний можно почитать здесь.
Интересный подход к разметке событий обсуждали на конференции Aha 2023 в докладе «Разметка событий как код». Рекомендую посмотреть на инструменты segment tracking plan и avo.app
И всё же нам чего-то не хватило, и мы сделали свой self-сервис. Давайте разберёмся в чем оригинальность нашего решения?
Metida решает две крупные задачи:
доступ к каталогу данных;
работа с данными в self-сервисе.
Каталог данных, реестр событий
Каталог должен позволять добавлять описания новых событий и быстро искать по ним. Если описание события не актуально, нужно иметь возможность оперативно внести изменения в реестр.
Хорошо, мы описали наши данные или нашли нужные нам события, которые ранее кто-то описал. А что дальше?
Работа с данными в self-сервис
Self-сервис решает задачи получения самих данных и работы с ними. Запросы к ним выполняются в колоночной базе данных. Metida помогает визуализировать в BI и строить графики.
Наши данные разбиты по стандартным слоям RAW, ODS, DDS и CDM:
RAW — слой сырых данных. Сюда загружаем файлы, логи, архивы в исходном виде от сервисов.
ODS — Operational Data Store. Сюда мы загружаем данные в формате, приближенном к табличному. Исходные данные могут быть немного очищены и приведены к нужному формату. С ними можно работать на SQL.
DDS — Detail Data Store. Здесь мы собираем консолидированную модель подробных данных.
CDM — в этом слое собираем прикладные витрины данных для продуктов.
Наш основной слой ODS находится в Hadoop. Данные попадают в хранилище от продуктовых сервисов через общую шину на основе Kafka. Сами данные обрабатываются и хранятся в слабоструктурированном виде: массивах или JSON-ах. Как мы храним данные и выполняем запросы к ним, я подробно рассказывал в этой статье. Технарям она может оказаться полезной, особенно если вы в начале пути по созданию своего хранилища.
Прежде чем я перейду к описанию сервиса, предлагаю рассмотреть работу с данными на примерах. Так будет легче понять источник проблем и то, какую помощь в их решении может оказать каталог? Как говорится, хочешь поймать аналитика — нужно думать как аналитик! Для начала попробуем поработать с сырыми данными. Представим, что у нас пока нет никакого data-каталога. Обычно так и бывает: приходит к аналитику продакт-менеджер и спрашивает: «Почему вырос Churn в Календаре? Где искать ответы?» Конечно же, в сырых, слабоструктурированных данных.
Работа с сырыми данными
Допустим, у нас есть логи серверных событий о переходе пользователя из Почты во вкладку Календарь. Они могут выглядеть так:
2023-11-12 12:23:31 open_calendar_tab {"id": 1121123113}
2023-11-12 12:23:31 open_calendar_tab {"id": 2232112215}
2023-11-12 12:23:31 open_calendar_tab {"id": 4323642234}
Аналитик буквально выкусывает из логов нужную информацию. Поэтому хранилище должно содержать максимум информации. И оно должно быть универсальным, ведь мы не можем сказать, какие поля нам точно понадобятся — они нужны нам все.
Логи обработаны и данные собраны в универсальную Hive-таблицу с названием ods.mail_data_lake и полями data: Array<String>
и data_json: String
. В поле data
хранится массив «строк», в нём можно держать параметры любых событий и обращаться к ним по индексу. В поле data_json
хранится словарь. Он удобнее массивов, потому что обращение по ключу несёт в себе уже какой-то продуктовый смысл. Что понятнее data[3]
или data["id"]
? Спорный вопрос, конечно, и ключи могут быть непонятными. Но в любом случае, словарь — это универсальный контейнер для хранения параметров. Мы используем и массивы, и словари для универсального хранения параметров события.
Итак, данные находятся в универсальном хранилище. К ним можно выполнять SQL-запросы. Для каждого аналитика мы предоставляем персональный Jupyter Notebook, в котором доступен Spark. SQL — любимый инструмент аналитика! Казалось бы, что ещё нужно?
Обращение к сырым данным на SQL всё ещё покрыто тайной, какие поля в этих данных что означают. Попробуем вчитаться в типовой SQL-запрос по сырым данным:
SELECT
data[0]
,count(distinct data_json["id"])
FROM ods.mail_data_lake
WHERE
data[1] = "open_calendar_tab"
GROUP BY data[0]
ORDER BY data[0]
Что он делает? Какие-то data[0]
, data[1]
, data_json["id"]
. Кто знает, что хранится в этих полях?
Вам знакома такая проблема при работе со слабоструктурированными данными? Если аналитик здесь и сейчас разобрался, в каких полях находятся нужные ему данные, — отлично! Но через неделю другой аналитик придёт с вопросом: «В каком поле находится дата и ID пользователя открывшего вкладку Календарь?» И это и есть пример передачи знаний «из уст в уста». Думаю, что есть читатели, которым эта проблема хорошо знакома.
Давайте перепишем SQL-запрос и назовём поля получше, использовав мощь SQL и красоту cte:
with t as ( -- объявляем "временную" таблицу t
SELECT
data[0] as dt
,data[1] as event_name
,data_json["id"] as user_id
FROM ods.mail_data_lake
)
SELECT
dt
,count(distinct user_id)
FROM t
WHERE
event_name = "open_calendar_tab"
GROUP BY dt
ORDER BY dt
Такой запрос выглядит лучше? Кажется, что да, теперь его можно читать! Мы интуитивно можем догадаться, что в массиве data[0]
хранится дата события, а в data[1]
— его тип. Не нужно идти за разъяснениями к автору запроса. Запрос выбирает событие «переход во вкладку Календарь», дату события и ID пользователя. Далее вычисляется уникальное количество пользователей (DAU), открывающих Календарь. Гораздо понятнее, и с этим уже приятно работать.
Запросы к сырым данным в большом хранилище могут выполняться минуты. А если данных петабайты — часы или десятки часов. Это может демотивировать аналитика, ведь нужно получать результат здесь и сейчас, за секунды, ну или хотя бы за пару минут. Как ещё можно улучшить подобные запросы к сырым данным, и сам подход к работе с ними?
Описание и хранение данных в Metida
Мы решили использовать автоматизацию, а также сильно ускорить выполнение запросов к сырым данным. Для этого мы описываем в нашем каталоге не только смысл самого события, но и значения конкретных полей в самом событии. Извлекаем сырые данные в колоночную базу и раскладываем непонятную информацию по колонкам с говорящими именами. А в Metida мы сохраняем подробное описание события. Пример:
Описание события: Переход пользователя из Почты во вкладку Календарь.
Тип события: open_calendar_tab
Поля события:
data[0]
— дата события, храним в колонкеdt
data[1]
— тип события, храним его в колонкеevent_name
data_json["id"]
— идентификатор пользователя, храним его в колонкеuser_id
Данные по всем описанным событиям мы раскладываем по отдельным колонкам в одну широкую таблицу. Колонок может быть 100, 200, 1000. Их количество велико, но всё же конечно. Этот подход называется Activity Schema. Как его реализовать — тема отдельной статьи, и тема очень интересная. Существуют и другие подходы к дата моделированию, классическая звезда или снежинка, anchor modeling, data vault и data vault 2.0.
Но давайте сосредоточимся на работе с данными с точки зрения задач аналитика. Теперь, когда данные описаны в Metida, можно найти интересующее нас событие и воспользоваться простым и понятным SQL-запросом к колоночной базе:
SELECT
dt
,count(distinct user_id)
FROM metida.events_mail
WHERE
event_name = 'open_calendar_tab'
GROUP BY dt
ORDER BY dt
Если сравнить его с запросом к сырым данным, то какой из них понятнее и проще?
Так как данные находятся в колоночной базе данных, то легко их вывести в BI на дашборд. Для этого мы используем Redash и интегрируем его с Metida. Пишем SQL-запрос, запускаем его в Redash, смотрим на графики. И всё это за считанные секунды! Просто мечта продуктового аналитика. Мощно? Это действительно ускоряет принятие решений на основе данных.
Благодаря чему можно повысить скорость выполнения запросов за секунды и минуты? Вы скажете: «Ну да, вместо медленного Hadoop разложили всё по колонкам широкой таблицы в Clickhouse». Да, верно, но мы сохранили только интересующие нас события, а не полный набор сырых событий из Hadoop. Metida быстро загружает историю по новым событиям, добавленным в реестр.
Описать события в новом проекте и поддерживать актуальность — это одна задача, а описать события в проекте, который разрабатывается уже 20 лет — совершенно другая. Мы не можем просто переписать весь код наших проектов, но нам важно понимать что с ними происходит. Важно больше не терять знания, которые мы получили от людей при работе с нашими проектами.
В колоночной базе данных хранятся только описанные события за ограниченный период, сейчас это два года. Мы сравниваем динамику в продукте год к году. Например, заметили рост или другую аномалию в данных — требуется сравнить. Это типовая задача по продуктовому исследованию.
Описание в Metida даёт нам ещё и базу знаний о том, как работали с данными. Эти знания не будут теряться. Нужные события удобно искать, не придётся спрашивать разработчиков, какие события отправляются, не нужно пытать продакт-менеджеров вопросами. Просто находим всё сами.
Как выглядит интерфейс Metida?
Данные в Metida разбиты по проектам, можно добавить новый или найти существующий.
Также можно пометить проект «избранным», тогда он будет всегда под рукой.
Проваливаемся в сам проект. Переходим на страницу с событиями Почты.
У события может быть несколько источников. Например, события из web-интерфейса Почты (Octavius) или из мобильных приложений. Также события могут быть размечены тегами. Доступна фильтрация по источникам и тегам. Вот, например, события от iOS Почты, где есть тег VKID:
У события могут быть добавлены экраны — элементы UI. Экран является местом, где возникают события. Доступен поиск событий по экранам.
Также доступен поиск по событиям, их описанию и описанию параметров:
Нашли нужное нам событие — попадаем в карточку события, изучаем подробности.
Она содержит:
Описание события.
Селектор, который можно считать типом события. По нему выбираем события из широкой таблицы и отличаем одно от другого.
Параметры события, с описанием, типом, именем поля в колоночной
базе данных, а также информацией о быстрой статистике по колонкам события.
Статистика по параметрам уже может быть полезной на этапе быстрого анализа происходящего. Получаем массу полезной информации, пока без каких-либо запросов к базе данных.
Карточка события может содержать фрагмент кода отправки самого события в мобильном приложении:
Код отправки может использоваться разработчиками для поиска проблемных мест в мобильном приложении при анализе данных.
Карточку события завершают интерфейсные элементы со ссылками на BI. Мы используем шаблоны в Redash для быстрого анализа данных.
Доступно имя таблицы, в которой сохранено событие. Также доступна информация по статусу загрузки события по дням в колоночную базу данных.
Из интерфейса Metida можно посмотреть типовые аналитические метрики по событию: хиты, уники, воронки и retention. Переходим по ссылкам для работы с данными, попадаем в Redash.
Как правило, этой информации уже может быть достаточно для того, чтобы принять быстрое решение на основе данных. Но если требуется более сложный кейс, то можно написать любой SQL-запрос в Redash, взяв за основу один из шаблонов и доработав его самостоятельно.
Выполняем запрос и получаем результат мгновенно! Для решения персональной задачи не нужен ни data-инженер, ни администратор, ни аналитик. Если есть хоть немного опыта в SQL, то для решения задачи не нужен никто! Достаточно головы и смекалки.
Как данные реестра попадают в колоночную базу данных?
Загрузку данных и их вставку в Clickhouse выполняет отдельный процесс. Помимо того, что мы загружаем только те сырые события, которые были описаны в реестре, они разбиваются на отдельные колонки и обогащаются дополнительной информацией. Например: добавляется страна, регион, версия мобильного приложения, ОС, браузера и т. п. За обогащение отвечает команда data-инженеров. Они занимаются конфигурированием этого процесса и каждое загружаемое событие обогащается нужными данными. Это готовые разрезы для когортного анализа, не нужно ничего JOIN-ить, просто фильтруем по нужному разрезу и анализируем результат.
Все запросы выглядят примерно так:
SELECT
os_version
,count(distinct user_id) as cnt
FROM …
WHERE event_name='open_calendar_tab'
AND country='RU'
AND os='android'
GROUP BY os_version
ORDER BY cnt DESC
LIMIT 50
Мы фильтруем нужные события по колонке event_name. Она является частью первичного ключа в широкой таблице, поэтому Clickhouse очень быстро находит нужные данные и распаковывает всего лишь небольшую их часть. А далее строим статистику по нужным нам параметрам события. Конкретно этот запрос показывает топ 50 версий приложений для самых активных пользователей Android в России.
Можно назвать данные в широкой таблице DDS-слоем. То, что в итоговом запросе не будет JOIN-ов, не совсем правда. Она скрыта в том, что JOIN-ы сделаны заранее, при вставке и обогащении данных. В этом есть определенный недостаток по сравнению с Anchor Modeling: нельзя начать загрузку, пока не готовы все данные за нужный период.
Но работа с одной широкой таблицей ускоряет работу с данными, избавляя нас от выполнения JOIN-ов. Но как? Можно использовать window-функции или другие специфические функции в Clickhouse.
Поиск воронок по событиям — очень частый сценарий у нас, и мы используем функцию windowFunnel. Она сильно сокращает длительность запроса в случаях, когда данные по разным событиям находятся в одной таблице.
Что делать, если сырых данных недостаточно?
Не все сценарии можно покрыть запросами к широким таблицам. Более того, часть запросов может отрабатывать в Clickhouse не так эффективно, как хотелось бы аналитику. Что делать в таком случае?
Мы предоставляем инструмент для формирования витрины данных по ODS-слою через SQL на Spark. Мы готовы строить витрины и делать JOIN-ы по петабайтам данных. Инструмент представляет собой git-репозиторий. Каждая витрина описывается в отдельном YAML-конфиге; описываются колонки; задача, по которой создавался агрегат; зависимости в данных. Приведу пример агрегата с фрагментами настоящего запроса, в нём можно рассмотреть говорящие названия параметров.
creation_date: '2023-07-31'
author: e.s.user
description: RBI-625 traffic sources to click
interval: 1d
history_load: '2022-07-22'
project: rustore
datasource:
hive_deps: # зависимости от других таблиц
— mr_metrics.customeventsflow
— mr_metrics.mytracker_entities
sql: |- # трёхэтажный SQL по петабайтам данных
SELECT A.dt, …
FROM mr_metrics.customeventsflow A
LEFT JOIN
(SELECT …
ORDER BY A.dt DESC
export:
type: clickhouse # результаты записываем в Clickhouse-таблицу
name: rustore_traffic_sources_mytracker
fields: # описываем поля в таблице
— name: dt
type: Date
description: дата активности пользователя
— name: idCampaign
type: Int64
description: id кампании
…
partition-by: dt
order-by: dt
data_quality_expectations: # добавляем GE-проверки
— expectation_type: expect_table_row_count_to_equal
kwargs:
value: 1
…
Metida отображает все агрегаты в web-интерфейсе.
Также доступна карточка агрегата со статусом его загрузки и описанием колонок с типами.
Механизм агрегатов может напоминать функциональность фреймворка dbt.
C агрегатами плотно работают аналитики. Они должны не просто сгенерировать SQL по слабоструктурированному слою в ODS, а зафиксировать описание этого запроса: какие колонки, как извлекаются, их описание, зависимости в данных. Всё это помогает накапливать базу знаний по работе с нашими данными. Аналитики формируют витрины в CDM-слое, они больше погружены в данные.
Аналитикам доступны и инструменты командной строки: линтер и sql_test
. Они позволяют найти ошибки в YAML-файле. Также запуск sql_test для агрегата выполняется через gitlab-ci. Команда data-инженеров участвует в проверке, помогает ускорить выполнение SQL-запроса и финально вливает изменения в git. После этого агрегат рассчитывается каждый день, и подгружается история на требуемую глубину, указанную в YAML-конфиге.
Дополнительно YAML-агрегат может содержать Data Quality-проверки по данным. Metida позволяет добавлять такие проверки от Great Expectations. Отчёты приходят на почту.
Также Metida контролирует зависимости в данных. Если произойдёт авария с ними (сбой поставки в источнике, несогласованное изменение формата), а после устранения проблем исходные данные будут обновлены, то Metida автоматически пересчитает все зависимые витрины. Это очень важная функция, она избавила нас от многих проблем с данными при авариях.
Как используется реестр событий внутри VK?
Как видно из описанных выше сценариев, ценность такого инструмента для продукта может оказаться огромной.
Metida хранит историческое описание продуктовых событий, позволяет накапливать базу знаний, которые не теряются со временем. Доступна функция поиска событий, а также механизмы фильтрации по тегам, источникам и экранам. Это позволяет сократить Time To Market для продуктовых исследований. Информация находится быстро, работа с данными для типовых аналитических сценариев шаблонизирована, доступны графики по событиям в BI.
Данные Metida могут использоваться для расследования проблем, связанных с выпуском обновлений мобильных приложений. Разработчики могут самостоятельно анализировать, как новый релиз повлиял на пользователей.
Процесс генерации и проверки продуктовых гипотез позволяет получать более полную картину о проблеме. Гипотезы проверяются максимально быстро, и тут дело не только в скорости работы с хранилищем: исключены звенья взаимодействия для доступа и работы с данными между людьми.
Также ценность Metida состоит в её использовании в других продуктах VK. При проведении А/Б-эксперимента мы следим за метриками, которые могут отражать события. Работа с описанными событиями в интерфейсе А/Б-инструмента выглядит просто и понятно не только для аналитика, но и для более широкого круга сотрудников, которые заинтересованы в проведении экспериментов и их результатов.
Как поддерживать актуальность описаний в Metida?
Знания о данных могут устаревать, и описание в Confluence может содержать неактуальную информацию. Как от этого защититься? Мы делаем дашборды по данным, описанным в Metida. Если метрики и формат события меняется, мы замечаем это по сломанным графикам. Оповещения по Data Quality сообщат о проблемах в данных, которые нужно решить внесением изменений в реестр событий.
Продукт и его метрики является основным драйвером актуализации описания данных в реестре. Если тебе нужны графики — нужно описать данные в Metida. Если данные изменились — графики сломаются, и нужно снова актуализировать описание. Получается, что продукт сам достаточно сильно мотивирует поддерживать реестр событий в актуальном состоянии.
Как взаимодействуют команды при работе с данными?
Горизонталь аналитики представлена командами:
продуктовой аналитики;
data-инженерами;
разработчиками сервисов.
Команда data-инженеров готовит данные в хранилище Hadoop, добавляет DQ-проверки, отвечает за SLA по данным. Разрабатывает инструменты для контроля и пересчёта зависимостей. Мы готовим наши данные к 10.00 утра, это наш SLA.
Инструменты для работы с данными — это зона ответственности команды разработки сервисов. Например, сервис Metida, сервис А/Б-экспериментов, сервис маркетинговых выгрузок и обмена данными с другими сервисами в VK. Здесь проходит стык data-инженерных задач, фронтенда и сурового хайлоад.
Команда аналитики привязана к продуктовым вертикалям и функциональным командам. Они проводят А/Б-эксперименты, готовят описание данных в Metida, тесно работают с данными и занимаются той самой продуктовой аналитикой.
Минутка рекламы: мы открыли вакансии продуктового аналитика и data-инженера. Откликайтесь, приходите к нам работать, рекомендуйте знакомых! У нас можно получить отличный опыт, занимаясь интересными задачами с фаном.
А кто же занимается описанием данных? Только аналитики? Мы не ограничиваем зону ответственности по описанию событий, скорее, наоборот, делаем описание в Metida доступным всем! В первую очередь это могут быть разработчики сервисов и мобильных приложений, менеджеры продуктов, проджект-менеджеры и аналитики. Все тесно взаимодействуют с Metida как с центром знаний о наших продуктах. При разработке новых фич в приложениях размечают события в Metida, заполняют описание, экраны, проводят А/Б-эксперименты. Флоу с Metida интегрирован в трекер задач Jira.
Планы по развитию Metida
Мы уходили от описания событий в бесчисленном количестве Google-таблиц и Сonfluence-страниц к красивому интерактивному UI. Реестр доступен всем, он един, его легко найти. Фидбек от аналитиков про таблицы — это удобно. Как и просмотр и редактирование реестра событий в табличном виде. Мы планируем реализовать в UI альтернативное отображение событий в табличном виде с возможностью их редактирования.
Решить проблему ошибок в заполнении параметров событий, а также актуализировать описания части не описанных событий поможет интеграция описаний в коде мобильных приложений под iOS и Android через кодогенерацию для мобильных SDK и Web SDK.
Если над описанием событий в проекте работает несколько человек, то никто не застрахован от дублирования событий и несогласованности описания. Не всегда можно быть уверенным в правильности заполнения описания, поэтому нужно ревью изменений в реестре. Сейчас мы как раз прорабатываем решение этой задачи.
Заключение
Хранить данные о продукте в слабоструктурированном виде — круто. Мы готовы покопаться в них, чтобы найти любые инсайты. Но если нет описания аналитических событий проекта, то работа с его данными несёт риски потери знаний о самих данных. Metida решает проблему накопления знаний о событиях в проекте, помогает формализовать их описание. Это инструмент, главная польза от которого в сокращении Time To Market в наших продуктах с большим легаси (не только в виде кода).
Metida пронизывает достаточно много слоёв, как сами данные и время, так инструменты и команды людей, работающих с данными. Для нас это действительно важный инструмент, который мы используем в повседневной работе.
В конце статьи хочу задать вопрос читателям: Metida — это data-каталог?
Полезные ссылки и статьи:
-
Каталог данных
-
Разметка событий как код
Data Quality — GE
-
Статьи про работу с данными
-
Data Modeling