user8003353

На Пикабу
поставил 1 плюс и 0 минусов
100 рейтинг 0 подписчиков 0 подписок 1 пост 0 в горячем

Интернет-магазин светильников: схема полей elasticsearch для фасетного поиска, фильтра товаров

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

В этой статье я опущу такие подробности работы с Elasticsearch (далее по тексту просто ES), как:

1) Как установить

2) Как подключаться

3) Раскрывать полную схему mapping для товара интернет-магазина

4) Подробное описание всей структуры и всех запросов для получения страницы товаров с результатами поиска и фильтром.

И что-либо еще.

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

Предисловие

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

Интернет-магазин светильников: схема полей elasticsearch для фасетного поиска, фильтра товаров Разработка, Интернет-магазин, PHP, IT, Длиннопост

Скрин из интернета.

Сайт разрабатывал не я. Разбираться с ним в дальнейшем не было ни знаний, ни желания. Решил, что впоследствии буду самостоятельно разрабатывать интернет-магазин, но используя другое и нереляционное хранилище данных, а не Mysql. Выбор пал на ES и при изучении понимание структурирования характеристик товаров и получение для них значений, которые впоследствии безболезненно и не затрагивая код можно было бы менять, отняло много времени. Лично мне очень не хватало в русскоязычном интернете абсолютно простых примеров, какие есть, например, для PHP+Mysql.

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

По сути дела

“Elasticsearch – это распределенный поисковый и аналитический движок на базе Apache Lucene. Полностью с описанием можно ознакомиться на официальном сайте.

Фасетный поиск (фасетная навигация) – поиск товара в разделе, категории или же на странице полнотекстового поиска по характеристикам: цвет, материал, цена, производитель и т.д. Для конечного пользователя – набор фильтров. Каждый фильтр – характеристика. Значения этого фильтра – все возможные значения характеристики. Для интернет-магазина это основная функция поиска, и пользователи ожидают, что она будет работать достаточно быстро.”

В приведенном ниже примере пользователь находится в категории “Люстры” и отфильтровал дополнительно товары в диапазоне цен от 1394 до 42207 руб. и с цветом черный. Было найдено 198 товаров, а на панели фильтров слева перечислены те характеристики, которые содержатся в результатах поиска, а также количество доступных значений, имеющих этот атрибут (количество фасетов):

Интернет-магазин светильников: схема полей elasticsearch для фасетного поиска, фильтра товаров Разработка, Интернет-магазин, PHP, IT, Длиннопост

Перейдя в интернет-магазине светильников в раздел люстры можно лично опробовать фильтр и повторить действия, описанные выше (на сайте используется ES).

Для создания фасетного поиска в ES есть достаточно мощный инструмент агрегирования. Одной из приятных особенностей агрегации является то, что они могут быть вложенными — другими словами, можно определить агрегации верхнего уровня, которые создают «корзины» (buckets) документов и другие агрегации, которые выполняются внутри этих корзин.

Для упрощения понимания, это в целом похоже на команду SQL GROUP_BY. На основе фильтров обобщаются, группируются документы по какому-то определенному признаку.

Индексирование значений фасета

Перед созданием агрегатов атрибуты документа, которые могут служить фасетами, необходимо проиндексировать в ES. Один из способов индексировать их — перечислить все атрибуты и их значения в одном поле, как в следующем примере:

"facets": {

"color": "Черный",

"style": "Лофт",

"room": "Гостиная",

}

Mapping ES при этом должен выглядеть так:

"facets": {

"type": "nested",

"properties": {

"color": {

"type": "keyword",

},

"style": {

"type": "keyword", }

"room": {

"type": "keyword", }

}

}

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

"aggs": {

"facets": {

"nested": {

"path": "facets"

},

"aggs": {

"color": {

"terms": {

"field": "facets.color"

}

},

"style": {

"terms": {

"field": "facets.style"

}

},

"room": {

"terms": {

field": "facets.room"

}

},

}

}

}

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

Вместо этого я пришел к следующему.

Разделил имена и значения фасетов, отправляемых в индекс эластика, следующим образом:

"string_facets": {

{

"name": "color",

"value": "Черный"

},

{

"name": "color",

"value": "Белый"

},

{

"name": "style",

"value": "Лофт"

},

{

"name": "style",

"value": "Техно"

},

{

"name": "room",

"value": "Гостиная"

},

{

"name": "room",

"value": "Спальня"

}

}

Mapping:

"string_facets": {

"type": "nested",

"properties": {

"name": {

"type": "keyword",

},

"value": {

"type": "keyword",

}

}

}

Для фильтрации и агрегирования такой структуры требуются вложенные фильтры и вложенные агрегации в запросах.

Агрегация:

"aggs": {

"aggs_text_facets": {

"nested": {

"path": "string_facets"

},

"aggs": {

"name": {

"terms": {

"field": "string_facets.name"

},

"aggs": {

"value": {

"terms": {

"field": "string_facets.value"

}

}

}

}

}

}

}

Фильтрация:

"filter": {

"nested": {

"path": "string_facets",

"filter": {

"bool": {

"must": {

{

"terms": {

"string_facets.name": "color"

}

},

{

"terms": {

"string_facets.value": "Черный"

}

}

}

}

}

}

}

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

В mapping это будет выглядеть следующим образом:

"number_facets": {

"type": "nested",

"properties": {

"name": {

"type": "keyword",

},

"value": {

"type": "double",

}

}

}

Агрегация.

"aggs_number_facet": {

"nested": {

"path": "number_facets"

},

"aggs": {

"name": {

"terms": {

"field": "number_facets.name"

},

"aggs": {

"value": {

"stats": {

"field": "number_facets.value"

}

}

}

}

}

}

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

Показать полностью 2
Отличная работа, все прочитано!