Бегущий по витринам KION. Как контент попадает на витрины приложений
Привет, Хабр! Меня зовут Алексей Жиряков, я техлид backend-команды витрины онлайн-кинотеатра KION. В этой статье я расскажу вам, как в нашем сервисе формируется витрина с фильмами и сериалами.
Приподниму завесу тайны над нашей архитектурой и поделюсь, как мы компонуем выдачу персонализированного контента. Подробнее — под катом.
Что такое витрины и полки?
Как только пользователь запускает KION, он попадает на главную витрину. Кроме неё есть ещё две — «Фильмы» и «Сериалы».
Теперь поговорим о полках. Чтобы витрины были разнообразными и интересными, в KION есть несколько видов полок. Первая из них называется «Баннерная».
Она самая просматриваемая, с автоплеем тайтлов на некоторых платформах, а значит и самая важная. Кроме неё есть много обычных VOD-полок (Video on Demand, видео по запросу). Они состоят из тайтлов, по нажатию на которые пользователь попадает в карточку фильма.
На скриншоте выше не обычная VOD, а ML-полка — она учитывает предпочтения пользователя. Внешне она не отличается от VOD-полок.
Ещё на главной витрине есть полка с видеобаннерами Originals:
Кроме этого, можно встретить «Суперполку». Она появилась под запрос от маркетинга и редакции.
Маркетинг хотел, чтобы с витрины можно было вести на веб-страницу — например, с подпиской. А редакция хотела показывать на витрине красивые обложки, ведущие на подборки или отдельные тайтлы.
Как работает витрина?
Ещё во время разработки онлайн-кинотеатра мы в KION сделали ставку на ML, экспертность и A/B-эксперименты. Так, мы выбираем определённый таргет, где пользователь будет взаимодействовать с ML. Подготавливаем модель, бэк, запускаем A/B-эксперимент и смотрим на результат. Если он удачный и основные метрики растут, решение идёт в продакшн.
Именно так мы сделали полку «Смотрите также», персональную витрину VOD-полок, баннерную и другие. Количество точек взаимодействия пользователя с ML у нас постоянно растёт, и сейчас проще сказать, где его нет, чем где он есть.
Как это происходит — наша архитектура
Как только пользователь запускает приложение KION или заходит на страницу, происходит запрос в Backend. Он у нас построен на основе микросервисов, имеет три ступени. Первая из них, встречающая запрос пользователя, — Media Gate Way (MGW), классический агрегатор. Его основная роль — обогащать ответ дополнительной информацией: картинками, рейтингами и прочим.
На входе он проводит аутентификацию, после чего опроксирует запрос дальше, в микросервис бэкенда команды витрины — Blender. Называется он так потому, что условно перемешивает контент. В его «зону ответственности» входит:
Сборка и компоновка витрины.
Применение бизнес-правил.
Показ редакторской витрины в случае проблем с ML.
После запроса в ML, получения персональной витрины и применения бизнес-правил микросервис возвращает запрос MGW. В ответе — только ID полок и тайтлов. MGW обогащает его всеми данными для показа клиентам. Среди них — ссылки на картинки, пометки на странице тайтла о его статусе (куплен пользователем, продаётся или входит в подписку), рейтинги.
Вот основные преимущества такого подхода:
Возможность использовать дефолтный ответ. Его мы называем «тыква» — по аналогии с транспортным средством Золушки. Все мы помним, что в сказке карета главной героини, которая отлично работала, превратилась в тыкву, когда что-то пошло не так.
Разделение зон ответственности. Каждый микросервис отвечает за свою часть. Это позволяет нашим молодым учёным улучшать витрину, а не заниматься бытовыми вещами.
Наличие отдельного микросервиса бизнес-правил. Он отвечает только за них и формирует редакторскую витрину, на случай если что-то пойдёт не так.
Что такое бизнес-правила?
Бизнес-правила — то, что гарантируется и будет точно выполнено. ML оперирует вероятностями. Поэтому для гарантий у нас есть отдельный микросервис. Он управляет правилами, которых у нас больше 50. Одно из них, например, делает так, чтобы на витрину не попадал контент с бо́льшим возрастным ограничением, чем лимит по возрасту в запросе.
Как мы собираем витрину?
На схеме выше мы показали, что Blender делает запрос в ML, получает персональную витрину и работает с ней. Но нюансов больше.
Например, после получения запроса клиента блендер идёт в разные источники за персональными полками. У нас практикуется подход «полка как сервис». Он удобен и ML-щикам, и для проведения A/B-экспериментов.
Мы можем передавать различные query-параметры для разных полок, тем самым что-то подкручивать, улучшать, тестировать. Ещё есть возможность работать с полками экосистемных продуктов — например, с сервисом для читающих людей от МТС «Строки».
После получения всех нужных полок Blender компонует витрину. Он расставляет полки на свои места, фильтрует, применяет бизнес-правила и возвращает витрину в MGW (Media gateway).
Наши бизнес-правила
Сразу скажу, что привожу пример основных бизнес-правил, для которых легко получить снимок экрана.
Минимальная длина полок и витрины
После получения полок Blender их фильтрует. По разным причинам количество тайтлов в полке может оказаться слишком малым. Например, правообладатель разрешил показывать тайтл только на больших экранах, а на маленьких запретил. Или закончилась лицензия на показ тайтла, и мы её ещё не продлили.
Во всех этих случаях количество тайтлов может опуститься ниже минимального предела, и полка будет удалена с витрины.
Кстати, количество тайтлов в полках для конкретного типа девайса набирает тоже Blender, и у нас оно разное, поскольку зависит от типа устройств. Например, на мобильных экранах в полках на витрине будет одно количество тайтлов, на больших — другое.
Если Blender удалил слишком много полок и на витрине их осталось меньше минимального предела, мы «отстрелим» аларм, вернём специальный код MGW, после получения которого будет показана дефолтная витрина.
Дедупликация тайтлов
Бывает так, что ML-модель слишком настойчива и предлагает один и тот же тайтл в разных полках. Тогда мы применяем фильтр дедупликации контента в зоне видимости.
Оставляем повторяющийся контент наверху. Если он повторяется на нижних полках, система его скроет.
Таргетирование полок по типу устройств и версии
Со стороны бэкенда мы умеем таргетировать виды полок в зависимости от типов устройств и версии. Например, показывать полку для ОС Android начиная с определённой ревизии приложения. Этой фичей мы пользуемся, когда нужно провести A/B-эксперимент или есть опасность поломать старые клиенты.
На скрине выше — пример таргетированного показа суперполок. Здесь мы их показывали только на определённом типе девайса с приложением нужной нам версии клиента для A/B-эксперимента.
Адалт-фильтр для гостя
Если пользователь не залогинен, мы не знаем его точный возраст. Весь контент с жанром adult для него будет скрыт.
Поддержка рекомендаций редакции, фильтр суперполок, вся логика на бэке
Редакция может разместить свои рекомендации по определённым тайтлам, которые будут показаны в первую очередь. Уже к ним прикрепится ML-ответ. Это происходит довольно редко, в основном только по отношению к новым Originals, когда модель ещё не накопила достаточных данных.
Суперполки состоят из обложек, которые могут вести на подборки, отдельные тайтлы или любую страницу.
Если обложка ведёт на полку со слишком маленьким количеством элементов, мы её удаляем. Аналогичным образом убираем и суперполку, если на ней не хватает контента.
В KION действует концепция: вся логика — на бэке. Это сделано для сокращения time to market, поскольку у клиентского ПО этот показатель слишком большой.
Например, возьмём стандартный двухнедельный спринт. Предположим, что идея фичи в среднем появляется в его середине. Таким образом, это неделя + 2 недели на спринт + тестирование внутри + код-ревью в сторах, так что получается минимум месяц. Со стороны бэка такого нет — достаточно написать код, тесты и добавить фиче флаг. После этого можно выкатывать в прод.
На примере — кнопка «Смотреть всё на витрине». Полки активируются на клиентах специальным флагом, который ставит бэк.
Расстановка полок на витрине
Когда пользователь запускает KION, он видит перед собой красивую витрину с расставленными в привычных местах полками. Зритель знает, что сначала будет баннерная карусель, а потом «Продолжить просмотр» и остальное.
Это происходит неслучайно, тут нет волшебства и магии. Некоторые спец. полки расставляет блендер в нужные места по шаблону. Для некоторых типов устройств их может закреплять редакция. Blender видит эти правила и применяет их. Витрины для разных девайсов у нас отличаются в соответствии с шаблоном редакции.
Фильтрация контента по тегам
Ещё нельзя обойти вниманием такую возможность, как тегирование контента. Редакция может создать абсолютно любой вид тегов и присвоить ему какое-то значение.
Например, редакция смотрит за поисковыми запросами и видит, что у нас часто ищут по названию какой-то контент, которого никогда не было, но есть очень похожий другой. Для него в теге задаётся та самая поисковая фраза. ML-модель поиска видит этот тег, и после переобучения протегированный тайтл будет уже показан одним из первых. В примере на картинке ниже видно, как мы переносим дублирующий контент с тегом «сурдоперевод» в конец полки:
Детская витрина
В этом году мы полностью пересмотрели концепцию детской витрины. Раньше мы фильтровали взрослую витрину по возрастному ограничению тайтлов и показывали её. Но возрастное ограничение не равно «детскости», даже если и стоит 0+.
Например, у нас на витрине показывали документальные фильмы или кино о программистах. Это явно видео не для детей. Плюс в баннерной полке приходилось всё время держать несколько детских тайтлов, чтобы она не пропадала при возрастной фильтрации. Мы жертвовали очень важными местами, чтобы на детской витрине была хоть пара баннеров.
Теперь для зрителей младше 16 лет мы полностью пересобираем витрину. Заменяем баннерную карусель на детскую, вырезаем взрослые суперполки и вставляем детские, а для возраста старше 0+ убираем из витрины материалы с тегом «развивающие». Редакция размечает детский контент, подсвечивает ML-модели, но мы всё равно проверяем и оставляем только тайтлы с флагом «детскости».
Бустинг
В KION постоянно выходит новый уникальный и интересный контент. Это как свои Originals, так и эксклюзивные тайтлы. Поскольку всё это — новинка, статистики по ней нет. Новые элементы нужно хорошо промоутить — показывать первыми возле подобного контента. Но как это гарантировать, если у нас персональная витрина? Для решения задачи мы сделали бустинг.
Работает он следующим образом: редакция ставит флаг бустинга у контента, который нужно поставить на первые места в полках. Если контент на них попадает, он гарантированно будет перемещается на одно из первых мест.
Бустинг можно не только включить, но и выключить. Получая персональную витрину, мы считаем средний CTR в полке у тайтлов в зоне видимости, сравниваем его с CTR бустингового тайтла. Если последний ниже определённой дельты, его продвижение отключается. Это защита по нижней границе, чтобы не демонстрировался нерелевантный контент — например, буст мультфильма может закончиться, если ML вернул элементы с высоким CTR в начале.
Скажем, мужчине 40 лет показываются боевики или тайтлы с глубоким декольте на обложке. В этом случае бустинг в персональном запросе будет отключён. Отмечу, что это работает не всегда и не у всех, а именно в конкретном персональном запросе.
Автополки
В KION ML работает с контентом, который есть на полках. В целом у нас около 1 000 полок, 600 из них собираются автоматически, остальные — редакторские.
Изначально у нас были только последние, но за ними нужно следить и обновлять. С контентом постоянно что-то происходит. Например, на него может закончиться лицензия. Тогда полка может стать слишком короткой, и её придётся скрывать.
Мы нашли выход. Автополки — это элементы, которые обновляются самостоятельно по правилам редакции. Она пишет правила наполнения для конкретной полки, а разработчики реализуют фильтр.
Но как контент попадает в полки? После транскодирования и занесения в базу всех креативов он уходит в микросервис бэкенда витрины. По ночам, когда запускается процесс наполнения, берётся вся база фильмов и сериалов, проходит через фильтры, и все подходящие под запрос элементы записываются в полку.
Для ML актуальнее узкотематические полки, а не широкие подборки. Это потому, что непонятно, кому может быть интересна полка с большим количеством разноплановых тайтлов.
Важный момент: в KION обновлять витрину для пользователей нельзя чаще раза в сутки. Так мы избегаем ситуации, когда юзер зашёл в карточку фильма, вернулся на витрину, а там всё изменилось. Человек может занервничать, а мы этого не хотим.
Персонализация
Отмечу, что в KION она даёт +5% к смотрению. Для механизма персонализации мы выбираем точку соприкосновения ML и пользователя, подготавливаем модель, бэк, проводим A/B-эксперименты, и если видим положительный прогресс, тогда нововведение отправляется в прод.
Персонализация VOD-полок
Одной из первых точек взаимодействия пользователя с ML была персонализация обычных VOD-полок. Как я уже писал выше, в KION есть шаблон, по которому строятся «прибитые полки»:
первая полка — всегда баннерная карусель
вторая — продолжить просмотр
третья — любимые телеканалы
Это всё касается первых двух экранов. Между закреплёнными полками есть свободные слоты, вот в них мы и встраиваем ML, дальше элементы идут друг за другом, поскольку там уже нет шаблона с припиненными полками.
Сейчас в проде модель, которую мы уже долгое время не можем победить. Она возвращает первые три полки на основе персональных интересов, статистики просмотренных тайтлов. Остальные полки строятся по сегментам, учитывая популярность, новизну и рейтинги.
Персонализация полок «Специально для вас» и «Похожие фильмы»
Прежде чем расскажу о персонализации полки «Специально для вас», объясню, как мы это делаем с полкой «Смотрите также (похожие фильмы)», которая располагается в карточке контента.
Для решения этой задачи применяется нейронная сеть DSSM (Deep Semantic Similarity Model). Сначала для каждого тайтла считаются векторы у всех фич, потом получается агрегированный вектор. Когда пользователь открывает карточку, видим вектор тайтла, для которого нужно подобрать похожие фильмы. На евклидовом пространстве ищем наиболее близкие векторы, получая тематические фильмы.
В полке «Специально для вас» используется двухуровневая модель. Сначала отбираются кандидаты путём генерации похожих фильмов к просмотренным фильмам. Затем они подбираются с учётом новизны, разнообразия, рейтинга.
Персонализация баннерной полки
Баннерная полка — самая важная, об этом мы говорили в начале статьи. У баннеров больше всего показов, кликов и просмотров. Поэтому полка с ними была лакомым кусочком для улучшения. Проблема в том, что эту полку постоянно используют для продвижения новых тайтлов. Чтобы запустить A/B-эксперимент, мы договорились оставить за редакцией 5 тайтлов на главной витрине и по одному — на фильмах и сериалах. Остальные баннеры определяются и сортируются ML, а фильтруются посредством Blender.
Наши молодые учёные подготовили ML-модель на основе градиентного бустинга CatBoost, учли статистику смотрения контента и статистику взаимодействия. Результаты эксперимента положительные, основные метрики значительно улучшились: например, смотрение фильмов и сериалов выросло больше чем на 2%.
A/B-эксперименты
Теперь — о самих экспериментах. О том, как мы их делаем и кто реализует логику. У нас есть своя «разбивалка», которая может прокидывать специальные параметры в бэк — это либо квери-параметры, либо хедеры.
Логику реализует бэкенд витрины. Конечно, если это A/B-эксперимент, связанный с клиентским UI, логика реализуется клиентским ПО. Но если дело касается экспериментов с витриной, то тут участвует именно бэк витрины.
Как правило, эксперименты достаточно уникальны — например, ML-хвост в баннерной карусели или показ определённой полки вместо другой.
Очередь на эксперименты с витриной расписана на месяцы вперёд. Недавно у нас был тест, в котором участвовало 6 моделей-кандидатов. Мы так сделали специально, чтобы все модели не стояли в долгой очереди. Этот кейс мы назвали playoff — модели, которые прошли первый раунд, соревновались между собой в финале.
Баланс и контроль
Всё это, конечно, хорошо: ML + экспертность = профит. Но всё обязательно нужно контролировать и соблюдать баланс. Например, у нас есть процессы, которые следят за ежедневной персонализированной новизной витрин — как полок, так и тайтлов в зоне видимости. Каждый день персональная витрина должна отличаться по содержанию от вчерашней.
Для этого проверяем наличие персонализации, количество платного контента, чтобы его не было слишком много, а также количество бустинговых тайтлов как на витрине, так и в полке. Если есть превышение предела, мы «отстреливаем» аларм и подсвечиваем редакции, а они уже оперативно исправляют.
Нагрузка и тайминги
Отдельно хотел выделить нашу гордость. Расчётная нагрузка — 300 RPS, но при пушах бывает и 450 RPS. В среднем мы отвечаем за 160 мс. Максимальный ответ для 95% пользователей — 220 мс, причём мы применяем фильтрацию, компоновку, задействуем 50+ бизнес-правил.
Наш стек
Что позволяет нам быстро реализовывать фичи, иметь минимальный time to market и выдерживать нагрузки?
Одна из последних версий Python плюс асинхронный фреймворк FastAPI. В Python с энтерпрайзностью в свежих версиях всё хорошо — больше типизации, повышают производительность. Основные бизнес-правила для KION реализовала команда из трёх человек. А недавно мы поставили рекорд time to market фичи — всего 2,5 часа до прода. Бизнесу было важно, чтобы мы по одному поисковому запросу показывали определённый тайтл, но при этом его не нужно было показывать на витрине.
Последнее, о чём расскажу, — это бизнес-правила, не упомянутые выше. Из наиболее интересных выделю фильтр удаления просмотренных тайтлов с витрины. Он работает следующим образом: если пользователь начал смотреть, тайтл будет в полке «Продолжить просмотр».
Смысла размещать его на витрине, выжигая показы в зоне видимости, нет. Также нет смысла показывать тайтл на витрине, если пользователь его уже просмотрел. Это всё хорошо для фильмов, но с сериалами это работает иначе. У них часто выходят новые сезоны, серии, причём они могут это делать по-разному: раз в неделю, все сразу, группой. Мы очень хотим узнать, когда лучше возвращать сериал на витрину: когда вышла новая серия, новый сезон или и то, и другое одновременно. Скоро запустим A/B-эксперимент, о результатах напишу.
В качестве заключения скажу, что сейчас витрина KION стала одной из самых технологичных в отрасли. Останавливаться в развитии мы не собираемся: продолжаем придумывать новые бизнес-правила, ещё лучше контролировать качество работы и улучшать пользовательский опыт.
Собственно, на этом всё. Если у вас есть вопросы, задавайте в комментариях, постараюсь ответить на все.