Статьи

Эталонная архитектура розничной торговли, часть 1: создание гибкого каталога продуктов с низкой задержкой и возможностью поиска

[Эта статья была написана Брайаном Рейнеро.]

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

В современных системах, предоставляемых поставщиками, данные о продуктах часто необходимо перемещать назад и вперед с использованием процессов ETL, чтобы гарантировать, что все системы работают с одним и тем же набором данных. Этот подход медленный, подвержен ошибкам и дорог с точки зрения разработки и управления. В ответ ритейлеры теперь предоставляют услуги передачи данных по отдельности в рамках централизованной сервис-ориентированной архитектуры (SOA).

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

Почему MongoDB?

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

  • Гибкость документа: каждый документ MongoDB может хранить данные, представленные в виде расширенных структур JSON. Это делает MongoDB идеальным для хранения практически всего, включая очень большие каталоги с тысячами вариантов на элемент.
  • Динамическая схема: структуры JSON в каждом документе могут быть изменены в любое время, что позволяет повысить гибкость и простую реструктуризацию данных при необходимости. В MongoDB эти несколько схем могут храниться в одной коллекции и могут использовать общие индексы, обеспечивая эффективный поиск как старого, так и нового форматов одновременно.
  • Язык экспрессивных запросов: возможность выполнять запросы по многим атрибутам документа упрощает многие задачи. Это также может повысить производительность приложений за счет снижения необходимого количества запросов к базе данных.
  • Индексирование: Мощные вторичные, составные и геоиндексирующие опции доступны в MongoDB прямо из коробки, быстро обеспечивая такие функции, как сортировка и запросы на основе местоположения.
  • Согласованность данных: по умолчанию все операции чтения и записи отправляются первичному члену набора реплик MongoDB. Это обеспечивает строгую согласованность, важную функцию для розничных продавцов, у которых может быть много клиентов, делающих запросы на один и тот же товар.
  • Геораспределенные реплики. Задержка в сети из-за географического расстояния между источником данных и клиентом может быть проблематичной, особенно для службы каталогов, которая, как ожидается, будет поддерживать большое количество операций чтения с низкой задержкой. Наборы реплик MongoDB могут быть геораспределены, так что они близки к пользователям для быстрого доступа, что во многих случаях снижает потребность в CDN.

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

  • Поиск предметов и вариантов предметов
  • Получение цен на товары в магазине
  • Включение просмотра каталога с помощью фасетного поиска

Модель данных элемента

Первое, что нам нужно рассмотреть, — это модель данных для наших товаров. В следующих примерах мы показываем только самую важную информацию о каждом товаре, такую ​​как категория, марка и описание:

{   “_id”: “30671”, //main item ID   “department”: “Shoes”,   “category”: “Shoes/Women/Pumps”,   “brand”: “Calvin Klein”,   “thumbnail”: “http://cdn.../pump.jpg”,   “title”: “Evening Platform Pumps”,   “description”: “Perfect for a casual night out or a formal event.”,   “style”: “Designer”,   …}

Этот тип простой модели данных позволяет нам легко запрашивать элементы на основе наиболее востребованных критериев. Например, используя db.collection.findOne, который вернет один документ, который удовлетворяет запросу:

  • Получить товар по ID
    db.definition.findOne({_id:”301671”})
  • Получить элементы для набора идентификаторов продуктов
    db.definition.findOne({_id:{$in:[”301671”,”452318”]}})
  • Получить товары по префиксу категории
    db.definition.findOne({category:/^Shoes\/Women/})

Обратите внимание, как во втором и третьем запросах используются $inоператор и регулярное выражение соответственно. При выполнении с правильно проиндексированными документами MongoDB может обеспечить высокую пропускную способность и низкую задержку для этих типов запросов.

Вариантная модель данных

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

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

Другой вариант — создать отдельную вариантную модель данных, на которую можно ссылаться относительно основного элемента:

{
“_id”: ”93284847362823”, //variant sku
“itemId”: “30671”, //references the main item
“size”: 6.0,
“color”: “red”
}

Эта модель данных позволяет нам быстро искать конкретные варианты элементов по их номеру SKU:

db.variation.find({_id:”93284847362823”})

А также все варианты для конкретного элемента путем запроса itemIdатрибута:

db.variation.find({itemId:”30671”}).sort({_id:1})

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

Цены в магазине

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

Конечно, мы можем хранить цену для каждого варианта в виде вложенного документа в документе позиции, но лучшим решением будет снова воспользоваться тем, насколько быстро MongoDB может выполнять запросы _id. Например, если на каждый элемент в нашем каталоге ссылается itemId, а на каждый вариант ссылается номер SKU, мы можем установить _idдля каждого документа конкатенацию itemId или SKU и storeId, связанных с этим вариантом цены. Используя эту модель, _idпара насосов и ее красный вариант, описанный выше, будут выглядеть примерно так:

  • Предмет: 30671_store23
  • Вариант: 93284847362823_store23

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

  • Все цены: db.prices.find({_id:/^30671/})
  • Цена магазина: db.prices.find({_id:/^30671_store23/})

Мы могли бы даже добавить другие комбинации, такие как цены для группы магазинов, и получить все возможные цены для товара с помощью одного запроса, используя $inоператор:

db.prices.find({_id:{$in:[“30671_store23”,
“30671_sgroup12”,
“93284847362823_store23”,
“93284847362823_sgroup12” ]}})

Обзор и поиск продуктов

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

Образец каталога

У нас много проблем:

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

Многие розничные продавцы могут использовать специальную поисковую систему в качестве основы для этих функций. MongoDB предоставляет проект коннектора с открытым исходным кодом , который позволяет использовать Apache Solr и Elasticsearch с MongoDB. Однако для нашей эталонной архитектуры мы хотели полностью реализовать фасетный поиск в MongoDB.

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

{ 
“_id”: “30671”,
“title”: “Evening Platform Pumps”,
“department”: “Shoes”,
“Category”: “Women/Shoes/Pumps”,
   “price”: 149.95,
“attrs”: [“brand”: “Calvin Klein”, …],
“sattrs”: [“style”: ”Designer”, …],
“vars”: [
{
“sku”: “93284847362823”,
“attrs”: [{“size”: 6.0}, {“color”: “red”}, …],
“sattrs”: [{“width”: 8.0}, {“heelHeight”: 5.0}, …],
}, … //Many more SKUs
]

}

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

Используя эту модель данных, мы создали бы составные индексы для следующих комбинаций:

  • отдел + атрибут + категория + _id
  • отдел + vars.attr + категория + _id
  • отдел + категория + _id
  • отдел + цена + _ид
  • отдел + рейтинг + _id

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

  • Получить сводку из itemId
    db.variation.find({_id:”30671”})
  • Получить сводку по конкретному варианту товара
    db.variation.find({vars.sku:”93284847362823”},{“vars.$”:1})
  • Получить сводку по всем пунктам по отделам
    db.variation.find({department:”Shoes”})
  • Получите резюме с различными параметрами
    db.variation.find({ “department”:”Shoes”,
     “vars.attr”: {“color”:”red”},
     “category”: “^/Shoes/Women”})

резюмировать

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

Выучить больше

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

Чтобы узнать, как консалтинговая команда MongoDB может ускорить запуск вашего приложения, изучите наше участие в Rapid Start.