Статьи

Параметризация запросов в Solr и Elasticsearch

Мы все знаем, как хорошо, когда в программном обеспечении, которое мы создаем, есть уровни абстракции. Мы стремимся абстрагировать реализацию от контрактов методов, используя интерфейсы, мы используем n-уровневые архитектуры, чтобы мы могли абстрагироваться и разделять разные системные уровни друг от друга. Это очень хорошо — когда мы меняем одну часть, нам не нужно касаться других частей, которые знали только о контрактах методов, API и т. Д. Почему бы не сделать то же самое с поисковыми запросами? Можем ли мы сделать это в Elasticsearch и Solr? Мы можем, и я покажу вам, как это сделать.

Эта проблема

Представьте, что у нас есть запрос, сложный, с бустами, сортировками, гранями и так далее. Однако в большинстве случаев запрос довольно статичен, когда дело касается его структуры, и единственное, что изменяется, — это один из фильтров в запросе (фактически значение фильтра) и запрос, введенный пользователем. Я полагаю, что такая ситуация может зазвонить тому, кто разработал поисковое приложение. Конечно, мы можем включить весь запрос в само приложение и использовать его повторно. Но в таком случае, например, для изменений в надстройках необходимо развернуть приложение или файл конфигурации. И если более чем одно приложение использует один и тот же запрос, мы должны изменить их все.

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

Для целей поста в блоге предположим, что мы хотим получить запрос, который:

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

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

Как сделать это с Solr

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

/solr/select?q=blue+jeans&qf=name^100+description^10&defType=edismax&fq=categoryId:12&facet=true&facet.query=price:[*+TO+30]&facet.query=price:[30+TO+100]&facet.query=price:[100+TO+*]

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

<requestHandler name="/search" class="solr.StandardRequestHandler">
 <lst name="defaults">
  <str name="q">_query_:"{!edismax qf=$queryFields v=$userQuery}"</str>
  <str name="queryFields">name^100 description^10</str>
  <str name="df">name</str>
  <str name="q.op">OR</str>
  <bool name="facet">true</bool>
  <str name="facet.query">price:[* TO 30]</str>
  <str name="facet.query">price:[30 TO 100]</str>
  <str name="facet.query">price:[100 TO *]</str>
 </lst>
</requestHandler>

И наш измененный запрос будет выглядеть так:

/solr/search?userQuery=blue+jeans&fq=categoryId:12

Как видите, наш запрос был упрощен. Стоит обратить внимание на то, что мы сейчас используем созданный нами обработчик / search . В дополнение к этому, вместо того, чтобы передавать обычный запрос, мы теперь отправляем только  userQuery, который содержит запрос, введенный пользователем, и фильтр для идентификатора категории. Мы используем $ userQuery в конфигурации обработчика запросов, чтобы получить значение запроса и передать его Solr. Просто как тот.

Как это происходит с Elasticsearch

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

{
 "query": {
  "filtered": {
   "query": {
    "multi_match" : {
     "query": "blue jeans",
     "fields": [ "name^100", "description^10" ]
    }
   },
   "filter": {
    "term" : {
     "categoryId": 12
    }
   }
  }
 },
 "facets": {
  "price": {
   "range": {
    "field": "price",
    "ranges": [
     {
      "to": 30
     },
     {
      "from": 30,
      "to": 100
     },
     {
      "from": 100
     }
    ]
   }
  }
 }
}

Теперь давайте изменим его и используем недавно введенный шаблон запроса. Мы создадим новый файл с именем shopQuery.mustache  в папке $ ES_HOME / config / scripts . имя шаблона должно заканчиваться расширением .mustache . Имя шаблона, который мы будем использовать, будет именем файла без расширения, поэтому в нашем случае это будет shopQuery . Содержимое файла шаблона будет следующим:

{
 "query": {
  "filtered": {
   "query": {
    "multi_match" : {
     "query": "{{userQuery}}",
     "fields": [ "name^100", "description^10" ]
    }
   },
   "filter": {
    "term" : {
     "categoryId": {{userCategory}}
    }
   }
  }
 },
 "facets": {
  "price": {
   "range": {
    "field": "price",
    "ranges": [
     {
      "to": 30
     },
     {
      "from": 30,
      "to": 100
     },
     {
      "from": 100
     }
    ]
   }
  }
 }
}

Как видите, мы изменили запрос на {{userQuery}} и значение идентификатора категории на {{userCategory}} . После перезапуска Elasticsearch мы можем использовать следующий запрос для использования шаблона:

curl -XGET "http://localhost:9200/_search/template" -d '{
 "template": "shopQuery",
 "params": {
  "userQuery": "blue jeans",
  "userCategory": 12
 }
}'

Мы использовали конечную точку / _search / template и указали имя шаблона, используя свойство шаблона в запросе. В дополнение к этому мы указали два параметра в разделе параметров . Кажется, лучше, правда?

Резюме

Как мы видим, мы можем упростить запросы для Solr и Elasticsearch. Однако не все здесь зеленое. В случае Solr нам необходимо перезагрузить конфигурацию для ядра или коллекции, чтобы изменения были видны. Поэтому каждый раз, когда мы хотим изменить запрос или добавить новый, нам нужно изменить конфигурацию и перезагрузить ее. То же самое касается Elasticsearch, по крайней мере, в описанной версии 1.1.0 — нам нужно перезапустить узел, чтобы шаблон стал видимым. Тем не менее, шаблоны поиска в Elasticsearch кажутся более гибкими, потому что мы можем просто установить параметры для любого значения (и не только). Но я полагаю, что это изменится в будущем, и мы сможем использовать API для проталкивания запросов, так же, как мы можем с потеплением запросов. В таком случае шаблоны будут проще в использовании и обслуживании.