Привет, Хабр! Меня зовут Алексей Жиряков, я техлид backend-команды витрины онлайн-кинотеатра KION. В этой статье я расскажу вам, как в нашем сервисе формируется витрина с фильмами и сериалами.
Приподниму завесу тайны над нашей архитектурой и поделюсь, как мы компонуем выдачу персонализированного контента. Подробнее — под катом.
![](https://habrastorage.org/getpro/habr/upload_files/5af/7e6/b8e/5af7e6b8e75434908b3bdbcd6349ed0a.jpg)
Что такое витрины и полки?
![](https://habrastorage.org/getpro/habr/upload_files/bff/d21/f78/bffd21f7855c839333cde87668207e0e.png)
Как только пользователь запускает KION, он попадает на главную витрину. Кроме неё есть ещё две — «Фильмы» и «Сериалы».
Теперь поговорим о полках. Чтобы витрины были разнообразными и интересными, в KION есть несколько видов полок. Первая из них называется «Баннерная».
![](https://habrastorage.org/getpro/habr/upload_files/73d/90b/11d/73d90b11ddc736ee37e6e3f3866a74b9.png)
Она самая просматриваемая, с автоплеем тайтлов на некоторых платформах, а значит и самая важная. Кроме неё есть много обычных VOD-полок (Video on Demand, видео по запросу). Они состоят из тайтлов, по нажатию на которые пользователь попадает в карточку фильма.
![](https://habrastorage.org/getpro/habr/upload_files/89a/922/b3e/89a922b3eeb80e0aa6d716091e456e3c.png)
На скриншоте выше не обычная VOD, а ML-полка — она учитывает предпочтения пользователя. Внешне она не отличается от VOD-полок.
Ещё на главной витрине есть полка с видеобаннерами Originals:
![](https://habrastorage.org/getpro/habr/upload_files/ead/3f4/dcd/ead3f4dcd11e3f31b955eb3be98d66ab.png)
Кроме этого, можно встретить «Суперполку». Она появилась под запрос от маркетинга и редакции.
![](https://habrastorage.org/getpro/habr/upload_files/eac/887/da6/eac887da607efaa123536831ec76cc0a.png)
Маркетинг хотел, чтобы с витрины можно было вести на веб-страницу — например, с подпиской. А редакция хотела показывать на витрине красивые обложки, ведущие на подборки или отдельные тайтлы.
Как работает витрина?
Ещё во время разработки онлайн-кинотеатра мы в KION сделали ставку на ML, экспертность и A/B-эксперименты. Так, мы выбираем определённый таргет, где пользователь будет взаимодействовать с ML. Подготавливаем модель, бэк, запускаем A/B-эксперимент и смотрим на результат. Если он удачный и основные метрики растут, решение идёт в продакшн.
![](https://habrastorage.org/getpro/habr/upload_files/125/a85/729/125a85729b45dfa2f76b9d0546886856.png)
Именно так мы сделали полку «Смотрите также», персональную витрину VOD-полок, баннерную и другие. Количество точек взаимодействия пользователя с ML у нас постоянно растёт, и сейчас проще сказать, где его нет, чем где он есть.
Как это происходит — наша архитектура
Как только пользователь запускает приложение KION или заходит на страницу, происходит запрос в Backend. Он у нас построен на основе микросервисов, имеет три ступени. Первая из них, встречающая запрос пользователя, — Media Gate Way (MGW), классический агрегатор. Его основная роль — обогащать ответ дополнительной информацией: картинками, рейтингами и прочим.
На входе он проводит аутентификацию, после чего опроксирует запрос дальше, в микросервис бэкенда команды витрины — Blender. Называется он так потому, что условно перемешивает контент. В его «зону ответственности» входит:
Сборка и компоновка витрины.
Применение бизнес-правил.
Показ редакторской витрины в случае проблем с ML.
После запроса в ML, получения персональной витрины и применения бизнес-правил микросервис возвращает запрос MGW. В ответе — только ID полок и тайтлов. MGW обогащает его всеми данными для показа клиентам. Среди них — ссылки на картинки, пометки на странице тайтла о его статусе (куплен пользователем, продаётся или входит в подписку), рейтинги.
![](https://habrastorage.org/getpro/habr/upload_files/59a/b4a/9dc/59ab4a9dcfa6e618161bd9802c34c79f.png)
Вот основные преимущества такого подхода:
Возможность использовать дефолтный ответ. Его мы называем «тыква» — по аналогии с транспортным средством Золушки. Все мы помним, что в сказке карета главной героини, которая отлично работала, превратилась в тыкву, когда что-то пошло не так.
Разделение зон ответственности. Каждый микросервис отвечает за свою часть. Это позволяет нашим молодым учёным улучшать витрину, а не заниматься бытовыми вещами.
Наличие отдельного микросервиса бизнес-правил. Он отвечает только за них и формирует редакторскую витрину, на случай если что-то пойдёт не так.
Что такое бизнес-правила?
Бизнес-правила — то, что гарантируется и будет точно выполнено. ML оперирует вероятностями. Поэтому для гарантий у нас есть отдельный микросервис. Он управляет правилами, которых у нас больше 50. Одно из них, например, делает так, чтобы на витрину не попадал контент с бо́льшим возрастным ограничением, чем лимит по возрасту в запросе.
![](https://habrastorage.org/getpro/habr/upload_files/e90/5a2/655/e905a26551fe7991b8260829a7dc9a0c.png)
Как мы собираем витрину?
На схеме выше мы показали, что Blender делает запрос в ML, получает персональную витрину и работает с ней. Но нюансов больше.
![](https://habrastorage.org/getpro/habr/upload_files/764/6a0/397/7646a0397ce86a87bdefebde20685ab2.png)
Например, после получения запроса клиента блендер идёт в разные источники за персональными полками. У нас практикуется подход «полка как сервис». Он удобен и ML-щикам, и для проведения A/B-экспериментов.
Мы можем передавать различные query-параметры для разных полок, тем самым что-то подкручивать, улучшать, тестировать. Ещё есть возможность работать с полками экосистемных продуктов — например, с сервисом для читающих людей от МТС «Строки».
После получения всех нужных полок Blender компонует витрину. Он расставляет полки на свои места, фильтрует, применяет бизнес-правила и возвращает витрину в MGW (Media gateway).
Наши бизнес-правила
Сразу скажу, что привожу пример основных бизнес-правил, для которых легко получить снимок экрана.
Минимальная длина полок и витрины
![](https://habrastorage.org/getpro/habr/upload_files/599/343/176/5993431763735158b7e37dcc9e984825.png)
После получения полок Blender их фильтрует. По разным причинам количество тайтлов в полке может оказаться слишком малым. Например, правообладатель разрешил показывать тайтл только на больших экранах, а на маленьких запретил. Или закончилась лицензия на показ тайтла, и мы её ещё не продлили.
Во всех этих случаях количество тайтлов может опуститься ниже минимального предела, и полка будет удалена с витрины.
Кстати, количество тайтлов в полках для конкретного типа девайса набирает тоже Blender, и у нас оно разное, поскольку зависит от типа устройств. Например, на мобильных экранах в полках на витрине будет одно количество тайтлов, на больших — другое.
Если Blender удалил слишком много полок и на витрине их осталось меньше минимального предела, мы «отстрелим» аларм, вернём специальный код MGW, после получения которого будет показана дефолтная витрина.
Дедупликация тайтлов
Бывает так, что ML-модель слишком настойчива и предлагает один и тот же тайтл в разных полках. Тогда мы применяем фильтр дедупликации контента в зоне видимости.
![](https://habrastorage.org/getpro/habr/upload_files/41c/3f2/db6/41c3f2db67895fe219e7d7c9a248d25a.png)
Оставляем повторяющийся контент наверху. Если он повторяется на нижних полках, система его скроет.
Таргетирование полок по типу устройств и версии
Со стороны бэкенда мы умеем таргетировать виды полок в зависимости от типов устройств и версии. Например, показывать полку для ОС Android начиная с определённой ревизии приложения. Этой фичей мы пользуемся, когда нужно провести A/B-эксперимент или есть опасность поломать старые клиенты.
![](https://habrastorage.org/getpro/habr/upload_files/318/b18/2a5/318b182a5332dbd2dd30227fb106b78a.png)
На скрине выше — пример таргетированного показа суперполок. Здесь мы их показывали только на определённом типе девайса с приложением нужной нам версии клиента для A/B-эксперимента.
Адалт-фильтр для гостя
Если пользователь не залогинен, мы не знаем его точный возраст. Весь контент с жанром adult для него будет скрыт.
![](https://habrastorage.org/getpro/habr/upload_files/5e1/b4c/e92/5e1b4ce922c228fa0efb086ec0945f9a.png)
Поддержка рекомендаций редакции, фильтр суперполок, вся логика на бэке
Редакция может разместить свои рекомендации по определённым тайтлам, которые будут показаны в первую очередь. Уже к ним прикрепится ML-ответ. Это происходит довольно редко, в основном только по отношению к новым Originals, когда модель ещё не накопила достаточных данных.
![](https://habrastorage.org/getpro/habr/upload_files/a1b/eef/096/a1beef096258e2769681aa35ebba8f5e.png)
Суперполки состоят из обложек, которые могут вести на подборки, отдельные тайтлы или любую страницу.
Если обложка ведёт на полку со слишком маленьким количеством элементов, мы её удаляем. Аналогичным образом убираем и суперполку, если на ней не хватает контента.
В KION действует концепция: вся логика — на бэке. Это сделано для сокращения time to market, поскольку у клиентского ПО этот показатель слишком большой.
Например, возьмём стандартный двухнедельный спринт. Предположим, что идея фичи в среднем появляется в его середине. Таким образом, это неделя + 2 недели на спринт + тестирование внутри + код-ревью в сторах, так что получается минимум месяц. Со стороны бэка такого нет — достаточно написать код, тесты и добавить фиче флаг. После этого можно выкатывать в прод.
На примере — кнопка «Смотреть всё на витрине». Полки активируются на клиентах специальным флагом, который ставит бэк.
Расстановка полок на витрине
Когда пользователь запускает KION, он видит перед собой красивую витрину с расставленными в привычных местах полками. Зритель знает, что сначала будет баннерная карусель, а потом «Продолжить просмотр» и остальное.
![](https://habrastorage.org/getpro/habr/upload_files/4b2/0ab/d65/4b20abd655b58c60e88dfe78cd2e7ad2.png)
Это происходит неслучайно, тут нет волшебства и магии. Некоторые спец. полки расставляет блендер в нужные места по шаблону. Для некоторых типов устройств их может закреплять редакция. Blender видит эти правила и применяет их. Витрины для разных девайсов у нас отличаются в соответствии с шаблоном редакции.
Фильтрация контента по тегам
Ещё нельзя обойти вниманием такую возможность, как тегирование контента. Редакция может создать абсолютно любой вид тегов и присвоить ему какое-то значение.
Например, редакция смотрит за поисковыми запросами и видит, что у нас часто ищут по названию какой-то контент, которого никогда не было, но есть очень похожий другой. Для него в теге задаётся та самая поисковая фраза. ML-модель поиска видит этот тег, и после переобучения протегированный тайтл будет уже показан одним из первых. В примере на картинке ниже видно, как мы переносим дублирующий контент с тегом «сурдоперевод» в конец полки:
![](https://habrastorage.org/getpro/habr/upload_files/cd6/fc0/670/cd6fc0670db3de4b528a956b3a216ae2.png)
Детская витрина
В этом году мы полностью пересмотрели концепцию детской витрины. Раньше мы фильтровали взрослую витрину по возрастному ограничению тайтлов и показывали её. Но возрастное ограничение не равно «детскости», даже если и стоит 0+.
Например, у нас на витрине показывали документальные фильмы или кино о программистах. Это явно видео не для детей. Плюс в баннерной полке приходилось всё время держать несколько детских тайтлов, чтобы она не пропадала при возрастной фильтрации. Мы жертвовали очень важными местами, чтобы на детской витрине была хоть пара баннеров.
![](https://habrastorage.org/getpro/habr/upload_files/64d/7ab/80e/64d7ab80e69fe5b57530fe9437d2a2e8.png)
Теперь для зрителей младше 16 лет мы полностью пересобираем витрину. Заменяем баннерную карусель на детскую, вырезаем взрослые суперполки и вставляем детские, а для возраста старше 0+ убираем из витрины материалы с тегом «развивающие». Редакция размечает детский контент, подсвечивает ML-модели, но мы всё равно проверяем и оставляем только тайтлы с флагом «детскости».
Бустинг
В KION постоянно выходит новый уникальный и интересный контент. Это как свои Originals, так и эксклюзивные тайтлы. Поскольку всё это — новинка, статистики по ней нет. Новые элементы нужно хорошо промоутить — показывать первыми возле подобного контента. Но как это гарантировать, если у нас персональная витрина? Для решения задачи мы сделали бустинг.
Работает он следующим образом: редакция ставит флаг бустинга у контента, который нужно поставить на первые места в полках. Если контент на них попадает, он гарантированно будет перемещается на одно из первых мест.
![](https://habrastorage.org/getpro/habr/upload_files/287/f50/ad9/287f50ad9c5f32c105cf54f0ff61478f.png)
Бустинг можно не только включить, но и выключить. Получая персональную витрину, мы считаем средний CTR в полке у тайтлов в зоне видимости, сравниваем его с CTR бустингового тайтла. Если последний ниже определённой дельты, его продвижение отключается. Это защита по нижней границе, чтобы не демонстрировался нерелевантный контент — например, буст мультфильма может закончиться, если ML вернул элементы с высоким CTR в начале.
Скажем, мужчине 40 лет показываются боевики или тайтлы с глубоким декольте на обложке. В этом случае бустинг в персональном запросе будет отключён. Отмечу, что это работает не всегда и не у всех, а именно в конкретном персональном запросе.
Автополки
В KION ML работает с контентом, который есть на полках. В целом у нас около 1 000 полок, 600 из них собираются автоматически, остальные — редакторские.
![](https://habrastorage.org/getpro/habr/upload_files/fbe/3d3/6be/fbe3d36be4fef5830ac74b48bf1ada3c.png)
Изначально у нас были только последние, но за ними нужно следить и обновлять. С контентом постоянно что-то происходит. Например, на него может закончиться лицензия. Тогда полка может стать слишком короткой, и её придётся скрывать.
Мы нашли выход. Автополки — это элементы, которые обновляются самостоятельно по правилам редакции. Она пишет правила наполнения для конкретной полки, а разработчики реализуют фильтр.
Но как контент попадает в полки? После транскодирования и занесения в базу всех креативов он уходит в микросервис бэкенда витрины. По ночам, когда запускается процесс наполнения, берётся вся база фильмов и сериалов, проходит через фильтры, и все подходящие под запрос элементы записываются в полку.
Для ML актуальнее узкотематические полки, а не широкие подборки. Это потому, что непонятно, кому может быть интересна полка с большим количеством разноплановых тайтлов.
Важный момент: в KION обновлять витрину для пользователей нельзя чаще раза в сутки. Так мы избегаем ситуации, когда юзер зашёл в карточку фильма, вернулся на витрину, а там всё изменилось. Человек может занервничать, а мы этого не хотим.
Персонализация
Отмечу, что в KION она даёт +5% к смотрению. Для механизма персонализации мы выбираем точку соприкосновения ML и пользователя, подготавливаем модель, бэк, проводим A/B-эксперименты, и если видим положительный прогресс, тогда нововведение отправляется в прод.
Персонализация VOD-полок
Одной из первых точек взаимодействия пользователя с ML была персонализация обычных VOD-полок. Как я уже писал выше, в KION есть шаблон, по которому строятся «прибитые полки»:
первая полка — всегда баннерная карусель
вторая — продолжить просмотр
третья — любимые телеканалы
Это всё касается первых двух экранов. Между закреплёнными полками есть свободные слоты, вот в них мы и встраиваем ML, дальше элементы идут друг за другом, поскольку там уже нет шаблона с припиненными полками.
![](https://habrastorage.org/getpro/habr/upload_files/b43/156/9a2/b431569a260dd9a69143bbe2b3b2fc79.png)
Сейчас в проде модель, которую мы уже долгое время не можем победить. Она возвращает первые три полки на основе персональных интересов, статистики просмотренных тайтлов. Остальные полки строятся по сегментам, учитывая популярность, новизну и рейтинги.
Персонализация полок «Специально для вас» и «Похожие фильмы»
Прежде чем расскажу о персонализации полки «Специально для вас», объясню, как мы это делаем с полкой «Смотрите также (похожие фильмы)», которая располагается в карточке контента.
Для решения этой задачи применяется нейронная сеть DSSM (Deep Semantic Similarity Model). Сначала для каждого тайтла считаются векторы у всех фич, потом получается агрегированный вектор. Когда пользователь открывает карточку, видим вектор тайтла, для которого нужно подобрать похожие фильмы. На евклидовом пространстве ищем наиболее близкие векторы, получая тематические фильмы.
![](https://habrastorage.org/getpro/habr/upload_files/7e7/cfd/a8f/7e7cfda8fb8214d2cb9892bef5275f3c.png)
В полке «Специально для вас» используется двухуровневая модель. Сначала отбираются кандидаты путём генерации похожих фильмов к просмотренным фильмам. Затем они подбираются с учётом новизны, разнообразия, рейтинга.
Персонализация баннерной полки
Баннерная полка — самая важная, об этом мы говорили в начале статьи. У баннеров больше всего показов, кликов и просмотров. Поэтому полка с ними была лакомым кусочком для улучшения. Проблема в том, что эту полку постоянно используют для продвижения новых тайтлов. Чтобы запустить A/B-эксперимент, мы договорились оставить за редакцией 5 тайтлов на главной витрине и по одному — на фильмах и сериалах. Остальные баннеры определяются и сортируются ML, а фильтруются посредством Blender.
![](https://habrastorage.org/getpro/habr/upload_files/6f0/f69/a89/6f0f69a89abc3101e18b2fe73d1ec705.png)
Наши молодые учёные подготовили ML-модель на основе градиентного бустинга CatBoost, учли статистику смотрения контента и статистику взаимодействия. Результаты эксперимента положительные, основные метрики значительно улучшились: например, смотрение фильмов и сериалов выросло больше чем на 2%.
A/B-эксперименты
Теперь — о самих экспериментах. О том, как мы их делаем и кто реализует логику. У нас есть своя «разбивалка», которая может прокидывать специальные параметры в бэк — это либо квери-параметры, либо хедеры.
Логику реализует бэкенд витрины. Конечно, если это A/B-эксперимент, связанный с клиентским UI, логика реализуется клиентским ПО. Но если дело касается экспериментов с витриной, то тут участвует именно бэк витрины.
Как правило, эксперименты достаточно уникальны — например, ML-хвост в баннерной карусели или показ определённой полки вместо другой.
![](https://habrastorage.org/getpro/habr/upload_files/5b1/94e/32d/5b194e32db97e334670acb6694b42179.png)
Очередь на эксперименты с витриной расписана на месяцы вперёд. Недавно у нас был тест, в котором участвовало 6 моделей-кандидатов. Мы так сделали специально, чтобы все модели не стояли в долгой очереди. Этот кейс мы назвали playoff — модели, которые прошли первый раунд, соревновались между собой в финале.
Баланс и контроль
Всё это, конечно, хорошо: ML + экспертность = профит. Но всё обязательно нужно контролировать и соблюдать баланс. Например, у нас есть процессы, которые следят за ежедневной персонализированной новизной витрин — как полок, так и тайтлов в зоне видимости. Каждый день персональная витрина должна отличаться по содержанию от вчерашней.
![](https://habrastorage.org/getpro/habr/upload_files/0e8/9bd/744/0e89bd7447fff632f3284c213405c1d2.png)
Для этого проверяем наличие персонализации, количество платного контента, чтобы его не было слишком много, а также количество бустинговых тайтлов как на витрине, так и в полке. Если есть превышение предела, мы «отстреливаем» аларм и подсвечиваем редакции, а они уже оперативно исправляют.
Нагрузка и тайминги
Отдельно хотел выделить нашу гордость. Расчётная нагрузка — 300 RPS, но при пушах бывает и 450 RPS. В среднем мы отвечаем за 160 мс. Максимальный ответ для 95% пользователей — 220 мс, причём мы применяем фильтрацию, компоновку, задействуем 50+ бизнес-правил.
![](https://habrastorage.org/getpro/habr/upload_files/1e8/2f8/341/1e82f83419bb5ff717ccf1385c412ba0.png)
Наш стек
Что позволяет нам быстро реализовывать фичи, иметь минимальный time to market и выдерживать нагрузки?
![](https://habrastorage.org/getpro/habr/upload_files/669/3d1/33b/6693d133ba67a3dffeb0bcab9a63c0a9.png)
Одна из последних версий Python плюс асинхронный фреймворк FastAPI. В Python с энтерпрайзностью в свежих версиях всё хорошо — больше типизации, повышают производительность. Основные бизнес-правила для KION реализовала команда из трёх человек. А недавно мы поставили рекорд time to market фичи — всего 2,5 часа до прода. Бизнесу было важно, чтобы мы по одному поисковому запросу показывали определённый тайтл, но при этом его не нужно было показывать на витрине.
Последнее, о чём расскажу, — это бизнес-правила, не упомянутые выше. Из наиболее интересных выделю фильтр удаления просмотренных тайтлов с витрины. Он работает следующим образом: если пользователь начал смотреть, тайтл будет в полке «Продолжить просмотр».
Смысла размещать его на витрине, выжигая показы в зоне видимости, нет. Также нет смысла показывать тайтл на витрине, если пользователь его уже просмотрел. Это всё хорошо для фильмов, но с сериалами это работает иначе. У них часто выходят новые сезоны, серии, причём они могут это делать по-разному: раз в неделю, все сразу, группой. Мы очень хотим узнать, когда лучше возвращать сериал на витрину: когда вышла новая серия, новый сезон или и то, и другое одновременно. Скоро запустим A/B-эксперимент, о результатах напишу.
В качестве заключения скажу, что сейчас витрина KION стала одной из самых технологичных в отрасли. Останавливаться в развитии мы не собираемся: продолжаем придумывать новые бизнес-правила, ещё лучше контролировать качество работы и улучшать пользовательский опыт.
Собственно, на этом всё. Если у вас есть вопросы, задавайте в комментариях, постараюсь ответить на все.
Комментарии (2)
ermouth
28.12.2023 18:08Наши молодые учёные подготовили ML-модель на основе градиентного бустинга CatBoost
Открыл заглавную Киона. 307 запросов, 36 Мб. Плохо верится в молодых учёных, тут криворукие джуны скорее.
Заглавная kion.ru
iwram
Поставил kion на телевизор huawei, показывает в вертикальном разрешении (как в телефоне как будто бы) - удалил, ловить нечего. С kinopoisk таких проблем не было.