Главная » Хабрахабр » Ни GA, ни ЯМ. Как мы сделали собственный кликстрим

Ни GA, ни ЯМ. Как мы сделали собственный кликстрим

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

Расскажу о технической стороне кликстрима в Авито: устройство событий, их отправка и доставка, аналитика, отчёты. Систему сбора и анализа событий можно обобщённо назвать кликстримом. Почему хочется своё, если есть Google Analytics и Яндекс.Метрика, кому портят жизнь разработчики кликстримов и почему go-кодеры не могут забыть php.

Работаю в платформенной команде, разрабатываю общие инфраструктурные инструменты. Дмитрий Хасанов, десять лет в веб-разработке, три года в Авито. Люблю хакатоны.

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

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

Собрать, структурировать, дать быстрый доступ к данным. Все указанные признаки: операционная система, идентификатор пользователя, время запроса, устройство, браузер, значения в полях, — нужно сделать доступными для анализа.

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

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

Отчёты, построенные на основе потока событий.
Пример 1.

Пример 2.

С их помощью хорошо и быстро можно собирать аналитические данные с фронтендов. Знаем о Яндекс Метрике и Google Analytics, используем для некоторых задач. Но для экспорта данных из бэкендов во внешние аналитические системы придётся делать хитрые интеграции.

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

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

Законодательство обязывает хранить данные на территории России.

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

На основе данных в хранилище строятся аналитические отчёты. События отправляются через высокопроизводительный транспорт (Event Streaming Processing, ESP) в хранилище (Data Warehouse, DWH).

Событие

Само по себе оно означает факт. Центральная сущность. Случилось что-то конкретное в обозначенную единицу времени.

Этому служит уникальный идентификатор события. Нужно отличать одно событие от другого.

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

Поле

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

Признаки поля: тип (строка, число, массив), обязательность.

Окружение

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

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

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

Примеры окружений: “бэкенд сервиса А”, “фронтенд сервиса А”, “ios-приложение сервиса А”.

Справочник событий

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

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

Лангпак

Вместо этого на основе справочника генерируем набор файлов для каждого из поддерживаемых в компании серверных языков: php, go или python’а. Мы отказались от пыток, и больше не заставляем разработчиков вручную писать код отправки событий. Такой сгенерированный код называем “лангпаком”.

Это набор геттеров и сеттеров полей для каждого из событий и код для отправки события. Файлы в лангпаке максимально простые, они не знают о бизнес-логике проектов.

Он раскладывается в репозиторий пакетов (satis для php, pypi для python’а). Для каждого окружения создаётся один лангпак. Обновляется автоматически при внесении изменений в справочник.

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

Версионирование

Код на бою ломать нельзя. Справочник править можно. Опасненько. Мы генерируем боевой код на основе справочника.

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

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

Версия каждого лангпака состоит из трёх цифр. Используем semver. Третья цифра меняется чаще всего, после каждого изменения событий. Первая всегда ноль, вторая — версия кода лангпака, третья — инкремент.

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

Транспорт

И считаем, что NSQ — не только сервер очередей, но и транспорт для событий. В отличие от ребят из Badoo на LSD, мы так и не научились красиво писать файлы.

Скрыли NSQ за небольшим слоем кода на go, разложили коллекторы на каждую ноду в кластере Кубернетеса с помощью daemon set’ов, написали консьюмеры, которые умеют складывать события в разные источники.

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

Роутинг событий

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

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

Среди возможных получателей — общее аналитическое хранилище (DWH), рэббиты или монги проектов, заинтересованных в определённых событиях. Общая схема роутинга: у каждого события может быть указан набор получателей. Модели слушают определённые события, получая необходимую обратную связь. Последний случай, например, используется для дообучения моделей автомодерации объявлений.

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

Хранилище

Колоночная база с характеристиками, подходящими нашим аналитикам. Основное хранилище событий — HP Vertica на несколько десятков терабайт. Интерфейс — Tableau для построения отчётов.

Перед хранилищем стоит буфер в виде Монго. Записывать события в наше хранилище эффективнее большими пачками. Коллекции хранятся несколько дней, чтобы иметь возможность перезапустить вычитку в Вертику, если что-то пойдёт не так. Автосоздаваемые автоудаляемые коллекции на каждый час.

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

Ручные танцы в темноте

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

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

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

Буферные коллекции в монго добавлялись по мере необходимости. Событий было не очень много. Решение о размещении события в той или иной буферной коллекции принималось в момент отправки, на стороне проекта. По мере роста количества событий требовалось вручную перенаправлять события в другие коллекции, досоздавать необходимые коллекции. Транспортом выступал Fluent, клиентом для него — td-agent.

Осведомлённый рассинхрон

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

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

Это приводило к рассинхронизации справочника и кода, но общую картину справочник показал. Разработчики умеют забывать.

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

Новый транспорт

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

Просвещённый кликстримизм

Они не позволяют создавать невалидные события. На основе справочника сгенерировали лангпаки.

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

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

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

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

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

Признаки появления бизнес-логики: события меняются по пути от проекта в хранилище; изменять транспорт без изменения проектов становится невозможно. Инфраструктура не должна знать о бизнес-логике. На стороне продукта — логический смысл этих полей. На стороне инфраструктуры должны быть знания о составе событий, типах полей, их обязательности.

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

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

S. P. Винегрет. Доклад на эту тему я рассказывал на митапе Backend United #1. Можно посмотреть
презентацию или видео со встречи.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

[Перевод] Китайская панель биомаркеров старения

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

Как работает stack trace на ARM

Добрый день! Несколько дней назад столкнулся с небольшой проблемой в нашем проекте — в обработчике прерывания gdb неправильно выводил stack trace для Cortex-M. Поэтому в очередной раз полез выяснять, а какими способами можно получать stack trace для ARM? Какие флаги ...