
Карлос Карвальейра, старший инженер-программист
Рекомендовать товары, бренды и одежду в Farfetch — не новинка. Просто взгляните на Как построить рекомендательную систему: это все о ракетостроении — Часть 1 и Часть 2. Как только мы обнаружили, что на самом деле можем сделать довольно крутой механизм рекомендаций (что было очень рано, если бы мы могли так сказать), мы мечтали предоставить самые свежие рекомендации нашим пользователям. Они уже есть на сайте и в мобильных приложениях. Но не в электронной почте. Это вот-вот изменится, но как мы можем это сделать? Это история ОФР. Это может стать немного техническим, но не волнуйтесь. Название не очень важно, оно было определено древним искусством выбора чего-то, что звучит круто, а затем создания для него аббревиатуры: Рекомендации на лету. Изо дня в день он проходит через OFR.
Задача OFR заключается не в том, чтобы воссоздать всю инфраструктуру рекомендаций, а в том, чтобы построить на ней дополнительные функции, специфичные для электронной почты. Имея имя и общее направление, какие бизнес-кейсы мы должны рассмотреть? Самое главное, чего мы хотим, — генерировать рекомендации как можно позже (но, конечно, вовремя!). Что ж, самый последний возможный момент для создания рекомендации для электронного письма — это генерировать его во время его открытия! Любой раньше, чем это, и рекомендация начинает устаревать. Любое более позднее, чем это, и электронная почта отсутствует содержание. Вот и все, одно единственное требование.
Рабочий процесс здесь заключается в том, что мы отправляем документ электронной почты, он долго находится в папке «Входящие» пользователя, а затем пользователь открывает электронное письмо. Нормальный. Но ждать! Если мы генерируем рекомендации, когда пользователь открывает электронное письмо, это означает, что у нас нет возможности узнать, какие продукты будут отображаться в то время, когда мы создаем контент электронной почты и отправляем его в первую очередь. Как и везде, наши электронные письма представляют собой HTML-документы. И у нас уже есть API, который говорит по HTTP и JSON. Так что, возможно, мы могли бы написать какой-нибудь JavaScript, который вызывает наш API, отображает правильный HTML после открытия электронной почты и бац! проект завершен! За исключением почтовых пользовательских агентов, таких как Outlook (и для простоты мы также включаем сюда Gmail), все игнорируют JavaScript в электронных письмах HTML. Следовательно, разрешены только запросы GET, что представляет собой еще одно препятствие. Что нам нужно, так это какой-то способ иметь динамическое содержимое в документе, которое нельзя изменить после отправки. Мы подумаем об этом позже.
Итак, теперь мы говорим с командой маркетинга, чтобы понять, какую информацию о продукте они хотели бы показать. Название бренда, описание, изображение, ничего страшного. Подождите минуту; название бренда? Описание? Мы не можем отображать динамический текст без JavaScript! Должен быть лучший способ. Может быть, мы могли бы использовать теги ‹img›, чтобы обойти рендеринг динамического контента? Возможно, мы могли бы взять атрибуты продукта, которые нам нужны, и отобразить изображение со сплошным фоном и текстом на нем. Это не совсем текст, но мы можем настроить изображение, чтобы оно выглядело органично с остальной частью письма. Это звучит достаточно безумно, чтобы работать! Да, именно это мы и сделали. Итак, эта часть динамического контента решена. Мы также определили, что проверенные нами MUA следуют перенаправлениям, поэтому OFR перенаправляет запросы на изображения продуктов в CDN Farfetch. Ура, интерфейс работает! Пример кода, к сожалению, на этот раз отсутствует в меню, но URL-адреса содержат уникальный идентификатор (мы доберемся до *этого* позже), и, кроме того, из-за того, как должен быть построен документ, будет отправлено несколько запросов. на серверы для каждого продукта.
Но сейчас у нас другая проблема. Каждое электронное письмо делает несколько запросов на продукт. И каждая рекомендация, которую мы генерируем (чтобы уточнить, мы называем рекомендацию списком продуктов, брендов или нарядов), сама по себе содержит несколько продуктов. Теперь нам нужно справиться с этим взрывным ростом запросов! Мы экспериментировали с объединением продукта, бренда и описания в одно изображение, но это было слишком медленно. Кроме того, с этим разделением мы делегируем изображения продуктов в CDN и улучшаем использование кеша для брендов и описаний. Так получилось, что все эти запросы относятся к одной рекомендации, поэтому на самом деле OFR нужно просто запросить одну рекомендацию, выбрать информацию, относящуюся к этому конкретному запросу, и вернуть ее. Но мы действительно не хотим запрашивать много раз одну и ту же рекомендацию одновременно. Во-первых, это ненужно, потому что результат, вероятно, будет одинаковым для всех из них, а во-вторых, это в несколько раз больше рекомендаций, которые нам нужны! Кроме того, у нас нет гарантии порядка поступления запросов: любой из них может прийти раньше любого другого (или не прийти вообще!). Дело усложнялось тем, что появилось новое требование. Это происходит следующим образом: когда пользователь повторно открывает электронное письмо, которое уже было открыто, должны быть видны те же рекомендации, которые были сгенерированы во время первого открытия электронного письма. Это причина существования уникального идентификатора в URL-адресе. Идея этого нового требования состоит в том, чтобы позволить пользователю открыть электронное письмо, просмотреть несколько рекомендаций и сделать мысленную пометку о том, чтобы вернуться позже для одного или нескольких продуктов, которые привлекли его внимание, но по какой-то причине пользователь не может их проверить. на сайте или в приложении в этот момент. Это было бы невозможно, если бы рекомендации обновлялись каждый раз, когда пользователь открывает письмо.
Итак, сначала мы займемся постоянством рекомендаций и притворимся, что не обрабатываем в десять раз больше трафика, чем должны обрабатывать (пожалуйста, не говорите ребятам из SRE! Или сделайте это :) Это было исправлено задолго до запуска, очевидно). Мы не можем воссоздать рекомендации только из исходных параметров, поэтому нам понадобится уникальный идентификатор для него и хранить его несколько месяцев где-то в базе данных. Этого времени достаточно для защиты варианта использования. Но что происходит, когда злоумышленник создает кучу ссылок с разными уникальными идентификаторами? Мы бы создали и хранили кучу ненужных рекомендаций и не хватило бы места на диске. Большое спасибо, злонамеренный пользователь… Итак, сейчас нам нужен способ гарантировать, что ссылки, которые идут в электронных письмах, имеют законный источник, такой как маркетинговые команды, которые отправляют электронные письма. Способ создания этого уникального идентификатора является частью секретной смеси ракетного топлива, благодаря которой Farfetch взлетает высоко. Таким образом, когда OFR получает запрос, он проверяет идентификатор; если это подтверждается, запрос является законным.
Вернуться к количеству запросов; теперь у нас есть дополнительный фактор, который на самом деле делает жизнь проще: уникальный идентификатор. Помните, что мы все еще ожидаем несколько запросов, выданных одновременно MUA. Мы не можем контролировать порядок прибытия, поэтому каждый запрос должен содержать всю информацию, необходимую для формирования рекомендаций. Таким образом, порядок прибытия для нас не имеет значения, что является плюсом. Любой запрос, поступивший на сервер первым, считается таким же хорошим, как и любой другой.
Идеальный рабочий процесс будет таким:
- для каждого электронного письма мы берем первый поступивший запрос и позволяем ему «пройти». Все остальные ждут, когда они прибудут
2. запрос, который проходит, делает это:
- проверить базу данных на наличие уже существующих рекомендаций (если да, это означает, что это письмо уже открывалось ранее)
- если нет, сгенерируйте рекомендацию и сохраните ее в базе данных
- вернуть только данные, относящиеся к этому запросу
- сигнализировать другим запросам, что данные готовы
3. все остальные запросы на это письмо считываются из базы данных
Мы могли бы использовать механизм распределенной блокировки, чтобы несколько экземпляров OFR могли координировать, какой запрос «пройти первым», но мы хотели попробовать другой маршрут. Во-первых, мы будем выполнять блокировку памяти для каждого экземпляра. Не глобальная блокировка, а блокировка по уникальному идентификатору; таким образом, мы могли бы пройти все уникальные рекомендации. По сути, это словарь мьютексов. Кроме того, пункт 3 не совсем верен. Мы сохраняем кеш с очень маленьким TTL в памяти с последними рекомендациями, чтобы запросы не приходилось отправлять в базу данных, что также противоречит цели механизма блокировки. Обратите внимание, что если бы мы остановились на этом, решение все равно не было бы полным. Балансировщики нагрузки будут распределять запросы между серверами приложений, но запросы, относящиеся к одной рекомендации, могут попадать в несколько экземпляров. В этом случае у нас будет в худшем случае одна генерация рекомендаций на экземпляр OFR. Не плохо, но не идеально. Решение здесь очевидно: липкие сессии! Верно, так что это совсем не очевидно, и некоторые из вас скорчили рожи, просто прочитав это, но потерпите меня. Балансировщик нагрузки будет направлять запросы на основе уникального идентификатора в URL-адресе, чтобы все запросы с этим идентификатором попадали в один и тот же экземпляр OFR. Таким образом, мы полностью решаем проблему слишком большого количества запросов, не вводя еще одну зависимость (цена за удаление этой зависимости — усложнение кода приложения и нестандартная конфигурация балансировщика нагрузки).
Так оказывается это оно! Все требования соблюдены, вопросов больше не надо решать. Отправим его! Мы, кстати, уже сделали. В большинстве рекламных и транзакционных электронных писем Farfetch, содержащих рекомендации, за кулисами используется OFR. Этот проект был похож на убийство Гидры. В таком уникальном проблемном пространстве каждая проблема требовала безумного решения, и каждый раз, когда мы решали одну, появлялись еще две, которые требовали еще более безумных решений! Ну, если он достаточно сумасшедший, чтобы работать, он может!
Первоначально опубликовано на https://www.farfetchtechblog.com 12 августа 2020 г.