Как создать маркетплейс, который не будет уступать Wildberries

Пока маркетплейсы захватывают мир, а Wildberries отчитывается о росте оборота на 96% в 2020 году — до 437,2 млрд рублей, мы, разработчики, ищем пути как делать так, чтобы эти маркетплейсы отрабатывали запросы за считанные миллисекунды и могли масштабироваться по щелчку пальцев.

В статье расскажу о своем опыте работы с высоконагруженным проектом, с которым Веб Секрет сотрудничает уже почти 2 года. Я решил рассмотреть пока только несколько пунктов, которые считаю реально крутыми.

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

У первых все нереально круто продумано, но скорость отклика не такая быстрая. У Lamoda классный дизайн, но с точки зрения скорости они не идеальны, а Wildberries показывают самые крутые цифры по сравнению с остальными. Поэтому поставили перед собой цель достичь в скорости работы именно их уровня. Жаль, что разработчики Wildberries мало что рассказывают, поэтому, если кто-то из них вдруг прочитает эту статью, поделитесь своим опытом. Думаю, всем будет очень интересно.

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

Разработка серверной архитектуры, выбор баз данных

То, насколько крутым выйдет ваш проект, зависит от многих составляющих. Например, насколько будет продвинутым и дальновидным техлид в команде. Как точно он продумает серверную архитектуру, чтобы она легко масштабировалась с ростом проекта. Какие базы данных подберет, чтобы сайт “летал” и не напрягал пользователей. Чаще всего используют Mysql и Postgresql. Мы работаем с последней уже 2 года. Однако в данном случае ее было недостаточно. Потому что проект подразумевал высокие нагрузки и использование фасетного поиска. Поэтому нам пришлось искать другое решение.

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

Итак, задача номер 1 – возможность удобного использования фасетного поиска. Это такой поиск, где фильтры зависят друг от друга. То есть, если вы выбрали бренд boss, то вам покажут цвета, доступные только для него. Потом выбрали размер L и увидели все вещи данного бренда, данного цвета, данного размера. Так все фильтры зависят от всех.

Фасетный поиск

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

Как создать маркетплейс, который не будет уступать Wildberries

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

Как создать маркетплейс, который не будет уступать Wildberries

В общем мы стали искать БД, которая бы быстро читала данные и делала фасетный поиск удобным. Я не буду перечислять кучу вариантов, которые мы перепробовали. В итоге в финалистах у нас оставалась Mongodb и Elasticsearch.

По сути, между собой это одно и тоже, только Elasticsearch написан на java и писец как жрет ресурсы сервера. Если у вас каталог хотя бы от 100к товаров, то гигов 8 памяти смело придется выделить только на Elasticsearch. Да и в целом мы считаем, что java достаточно тормознутый. Поэтому Mongo стала победителем.

Как создать маркетплейс, который не будет уступать Wildberries

Нагрузка на сайт

Решение второй задачи – снижение нагрузки на сайт и уменьшение количества запросов – привело нас к денормализации данных. Это способ, при котором мы заранее готовим данные для вывода. Например, собрали информацию о каком-то товаре сразу с наличием в магазине, связанными товарами, отзывами, ценой и сложили это в одно место. Таким образом, вывод страницы товара превращается в один запрос, а не несколько (в каких магазинах есть + по какой цене + какие похожие товары + какие размеры и т.п.).

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

Конечно, мы могли бы хранить эти данные и в Postgresql, но это не логично, учитывая, что вся информация для фасетного поиска хранится в Mongo. Т.к. это сначала запрос в одну БД на фильтрацию данных, а потом в другую на их вывод.

Итоговая архитектура БД

В качестве основной БД использована Postgresql – здесь вся логика и данные, сюда все пишем и посылаем запросы. Дополнительно – Mongodb, куда денормализуются данные, она используется только для чтения. Mongo очень быстрая и легко реплицируется в случае роста объема данных или нагрузок.

Как создать маркетплейс, который не будет уступать Wildberries

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

В результате мы получили очень быстрый рабочий каталог и высокую скорость генерации отдачи данных. Например, выбрать и подготовить страницу товара – порядка 7мс, каталог – около 30-40мс. И возможно это на сервере с 2 процессорами и 4Гб памяти. Так что перспективы для масштабирования и потенциал здесь огромные. Единственное что возможно – это упереться в скорость диска.

Как создать маркетплейс, который не будет уступать Wildberries

Отмечу еще, что вся наша серверная архитектура управляется с помощью Kubernetes. Для бизнеса это полезно по следующим причинам:

  • автоматически разворачиваются разные ветки на тестовой площадке. То есть мы можем параллельно разрабатывать несколько новых фич и каждую тестируем на отдельном домене. Ключевой момент, что происходит это автоматически.
  • Kubernetes – инструмент для серверной оркестрации. Понимайте это в прямом смысле слова. Штука, которая управляет оркестром контейнеров: БД1, БД2, front-end и php. И в случае падения он перезагрузит, в случае увеличения нагрузки – реплицируется на еще одну машину. Это наш дирижер. Однако не стоит думать, что он все сам умеет. Разработчики должны прописывать в Kubernetes всю логику. Вроде как составлять ему кучу инструкций, что делать в каждой ситуации. Кто-то может сказать, что он снижает немного время отклика из-за того, что строит свои сети, с помощью которых управляет контейнерами. Но послушайте, если этим будет заниматься человек, то времени потребуется еще больше, но помимо этого еще появится фактор ошибки. И, кстати, считая, за сколько выполнится код, мы всегда учитываем время на сеть. Вот ты, допустим, знаешь, что на это надо 30мс, а в консоли браузера показывается время обработки запроса — 100 мс. Как? 70мс — это скорость, с которой ходят данные. Хочешь, чтобы это время было меньше — храните сервера в непосредственной близости от клиента. Обычно в консоли видно, что тот же Wildberries передает данные о ближайшем сервере — откуда грузить данные, чтобы меньше тратить времени на их доставку до клиента.
  • Деплой на продакшн происходит без простоя и нажатием одной кнопки.
Деплой на продакшн одной кнопкой Веб Секрет
Деплой на продакшн одной кнопкой Веб Секрет

Общее с мобильным приложением – в чем ценность переиспользования кода?

Как создать маркетплейс, который не будет уступать Wildberries

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

Тем не менее, у нас, наконец, получилось частично переиспользовать код и логику между вебом и мобайлом, так как веб у нас на React, а мобайл – на React Native.

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

Конечно, можно было вспомнить еще про React Native Web, который предназначен для того, чтобы писать одно приложение, работающее одновременно и в браузере с использованием стандартных веб-технологий и на iOS или Android как настоящее нативное мобильное приложение. Год назад мы пытались там покопаться, но был еще совсем сырой. Поэтому мы сделали следующим образом.

Front-end разработчик, делая сайт, пишет store, а затем мобильный разработчик с небольшим отставанием приходит к разработке этого же раздела, берет store и делает собственную обработку, но в целом можно и наоборот. Так мы еще избегаем проблем с разной логикой. И я сейчас не только про то, что случается между Android и iOs, а еще про разницу между вебом и мобайлом. Плюс нам это дает колоссальный прирост в скорости разработки. По-моему офигенно.

Как создать маркетплейс, который не будет уступать Wildberries

Кэширование на стороне браузера

Наверняка всех бесит, когда в браузере нажимаешь “назад”, и тебя выбрасывает не на ту строку магазина, где ты был, а в рандомное место страницы, и приходится снова скроллить до товара, на котором ты остановился. Обидно правда. А порой это еще и долго происходит. Поэтому в рамках сессии пользователя мы делаем браузерное кеширование. Как результат, если пользователь нажимает “назад”, мы просто отдаем страницу из браузерного кэша. Это происходит мгновенно, и пользователь возвращается в то место, где он закончил серфинг. Сообщество предлагает множество вариантов с костылями, но, как показала практика, кэширование ajax-запросов — самый действенный и простой способ, который позволит пользователям кайфовать от использования вашего сайта-каталога.

Получение и формат хранения сессионных данных, генерация токена, мердж данных

Теперь поговорим о том, как на проекте устроены сессии и мерж данных. Зачем мы разделяем get- и post- запросы и для чего кэшируем их? Откуда появилась идея генерировать токен на фронте? И что нам это дает?

Сначала про кэш

Т.к. предполагается, что проект будет масштабироваться, мы решили учесть допнагрузки заранее и просчитали вариант с кэшированием. Чтобы прибавить скорости сайту, страница товара должна загружаться мгновенно, а профиль пользователя – потом подтягиваться асинхронно. Таким образом всю страницу товара можно класть в кэш, а профиль пользователя загружать отдельно. Быстрее? Очевидно же!

get- и post- запросы

Мы пришли к тому, что нам достаточно получать данные только на клиенте (они не важны для ssr, но у нас есть возможность их кэширования на стороне Nginx). Это позволяет видеть мгновенную отдачу. Т.к. на ssr может кэшироваться страница целиком – все Get-запросы, и в случае высоких нагрузок мы себе оставляем такую опцию, а на стороне клиента будет подтягиваться информация по каждому конкретному пользователю (все его избранное, кол-во вещей в корзине и т.п.) – Post-запросы. Get-запрос кешируется отдельно даже для незарегистрированных посетителей и не ломается при апдейте. Пользовательские данные не должны изменять get-запросы. Таким образом, при необходимости весь сайт можно положить в кэш, не затрагивая сессионные данные (авторизационные).

Проиллюстрирую примером, почему это важно.

Допустим, я авторизовался и зашел на главную страницу, на мне она закешировалась. Если мы не будем разделять запросы, то, когда кто-то другой зайдет на сайт, он увидит главную с моими данными, полученными по post-запросам – с моим избранным, потому что Nginx посчитает кэшированную страницу актуальной и отдаст ее в том виде, в котором она попала туда. При разделении запросов же, получится, что главная достается из кэша, а пользовательские данные подтягиваются для каждого отдельно.

Сессионный токен (не путать с авторизационным)

Он нужен, чтобы бэк понимал, кто конкретно к нему обращается и какие данные необходимо отдать. Это уникальный ключ, который раньше генерировался на стороне backend, но мы сделали иначе — на стороне клиента. Что это нам дало? Мы, во-первых, знаем источник, откуда пришел человек (web, ios, android), а во-вторых, теперь на фронте не надо ждать окончания одного запроса, как раньше, чтоб отправить другой (запрос на получение сессии + запрос на обновление истории просмотров). Соответственно мы снова сократили скорость.

Как создать маркетплейс, который не будет уступать Wildberries

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

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

Например:

user — Илья А

сессия 1 — web

сессия 2 — android

Илья в своем аккаунте с компьютера положил в корзину 2 товара. Потом с телефона зашел просто на сайт, не логинясь там и стал тоже наполнять корзину. Это пока “сессия 3”, т.к. мы не знаем кто это, и чья корзина. Но стоит Илье залогиниться с телефона в своем аккаунте, как все товары суммируются в личной корзине Ильи.

Как итог мы таким образом соединяем все устройства пользователя в один аккаунт и делаем мерж корзины с помощью сессионных cookie. Ну и мы объяснили почему мы так делаем с точки зрения перфоманса. Тут мелочь на перфомансе, а в итоге получается огонь. Ну и мы всегда знаем с каких устройств сидят наши пользователи. Конкретно в нашем случае это повлияло на ценообразование – пользователи с мобильного приложения получали скидку в 1%.

Изображения, форматы

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

Как создать маркетплейс, который не будет уступать Wildberries

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

Поэтому мы грузим картинки через тэг и целым массивом: для телефонов, десктопа, разного разрешения, с разной плотностью точек. Кстати, сейчас хорошим тоном является формат webp (на момент написания текста Safari его не поддерживал, поэтому использовали оба формата), который весит меньше привычного jpg. В итоге каждое устройство получает свое изображение. Естественно, что это занимает очень много места (на 1 товар 14 картинок и это только для одной страницы, а есть же еще корзина, каталог и т.п в итоге под 50 штук набирается, чтобы каждый пользователь получил именно ту картинку, которую нужно), но место — самый дешевый ресурс сейчас. НО цель не экономить средства, а показать товар настолько круто, чтобы он его купил не ходя в магазин.

Как создать маркетплейс, который не будет уступать Wildberries

Все картинки на стороне сервера мы кэшируем, чтобы не генерировать каждый раз массив целиком. Клиент грузит одну, а мы уже нарезаем, сколько нам нужно.

Front-end в админке

Почему хорошая админка – это крутой инструмент, который требует сложной разработки, времени и денег?

Для нас мифы об админке – это боль. Например, что, если сайт сделан на CMS, админку можно будет редактировать самостоятельно без участия разработчиков. Но конкретно эту историю мы уже разбирали. Поэтому опустим.

Мы всегда все делаем с нуля, соответственно, админку тоже. У нас есть классный пакет, который мы в будущем заопенсорсим. Он позволяет быстро создавать управление простыми сущностями, вроде новостей, категорий имен и т.п. Но до сих пор приходится много внимания уделять сложным сущностям, вроде меню, конструкторов, поведения шахматной сетки в недвижимости или логики интерфейса в логистике, например. Это действительно большой кусок работы, и не стоит ее недооценивать. Потому мы закладываем часы, привлекаем front-end разработчика, придумываем и создаем удобные механизмы управления. Отсюда и возникают соответствующие суммы, но на выходе вы получаете кастомную удобную разработку индивидуально под ваш проект. Ниже 2 охеренных примера.

Конструктор меню

Его мы “придумали” для того, чтобы клиент как раз-таки мог самостоятельно без разработчиков что-то менять и добавлять. Например, разные новые категории меню (привет статье на vc.ru, где мы рассказываем, что это две разные вещи). Какими они могут быть благодаря текущем конструктору на примере нашего проекта?

Как создать маркетплейс, который не будет уступать Wildberries

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

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

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

Конструктор подборок

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

Например, к майкам boss хотим рекомендовать джинсы boss. Или к майкам рекомендовать майки такого же цвета. Все бы ничего, но мы то строим универсальную архитектуру. А клиент сегодня продает майки, у которых разный цвет, завтра – ноутбуки с разной памятью и так далее. Поэтому опять сделали конструктор, зацените на видео.

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

Вывод подборки Веб Секрет
Вывод подборки Веб Секрет

Обмен с 1С

Есть несколько сценариев обмена со сторонними системами:

  • сторонний поставщик вкидывает сайту какой-то стандартизированный файл, сайт его обрабатывает – так работают почти все CMS.
  • сайт периодически сам запрашивает по URL стандартизированный файл и делает обновления, когда сайту удобно – так, например, работает Яндекс. Маркет.
  • на стороне сайта есть полноценное API, и сторонний поставщик дергает методы, изменяет только то, что ему нужно – так, например, работает Ozon. Этот метод называется еще push based.

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

Как создать маркетплейс, который не будет уступать Wildberries

Вот по последнему принципу реализован обмен на нашем проекте. Выбор на него пал, потому что требовалось обновлять очень много данных и запрашивать их со стороны. Например, полный обмен товаров – это рядовая операция, поэтому все используют 1 или 2 вариант. А нам нужно было, чтобы, как только товар забрали из магазина в оффлайне, 1С уведомлял сайт, что такого товара больше нет, и мы сразу же убрали бы его со страницы. Или сменился импортер поставщика всех товаров Boss. Не переписывать же нам все товары — ты поменяешь название импортера, а эта информация подтянется во все товарах. Помимо этого есть очень много фоновых процессов, требующих полноценного API, например, обмен информацией о клиенте, push-уведомления и т.п., все это прописано в документации.

Как создать маркетплейс, который не будет уступать Wildberries

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

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

Заключение

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

1717
19 комментариев

Как создать маркетплейс, который не будет уступать Wildberries

И как? Теперь он не уступает диким ягодам? Вы уже вышли на топ-3 маркетплейсов?

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

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

6
Ответить

"И как? Теперь он не уступает диким ягодам? Вы уже вышли на топ-3 маркетплейсов?"
 - статья как бы находится в разделе разработка, а не маркетинг или сео или торговля и вопрос больше был про создать, а не обогнать...


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

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

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

-  и снова здравствуй битрикс=)) Мне иногда кажется, что некоторым нравится читать статьи из серии, а как мы использовали "коробочные решения" и заклепали сайт, или а как допили CMS, или как из "говна и палок" сделать сайт за 100$, нежели, чем когда студии делятся реально чем-то годным и достаточно подробно...

4
Ответить

В итоге в финалистах у нас оставалась Mongodb и Elasticsearch. По сути, между собой это одно и тоже.Дальше можно не читать... ¯\_(ツ)_/¯

Но если очень хочется продолжить, то:
 Да и в целом мы считаем, что java достаточно тормознутый.Задача: Как создать маркетплейс, который не будет уступать Wildberries
Решение: Не использовать продукты, написанные на java, т.к. java "тормознутая".

2
Ответить

Комментарий недоступен

1
Ответить

Давайте в личку по таким вопросам)

2
Ответить

Ёбко!

Ответить

Ещё можно Intersection Observer прикрутить

1
Ответить