Word-ориентированный подсказчик завершения (ElasticSearch 5.x)

В ElasticSearch 5.x внесены некоторые (серьезные) изменения в API подсказок ( Документация). Наиболее заметное изменение заключается в следующем:

Завершение предложения ориентирован на документ

Предложения знают о документе, к которому они принадлежат. Теперь сопутствующие документы (_source) возвращаются как часть предложений по завершению.

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

Допустим, у нас есть это простое отображение:

{
   "my-index": {
      "mappings": {
         "users": {
            "properties": {
               "firstName": {
                  "type": "text"
               },
               "lastName": {
                  "type": "text"
               },
               "suggest": {
                  "type": "completion",
                  "analyzer": "simple"
               }
            }
         }
      }
   }
}

С несколькими тестовыми документами:

{
   "_index": "my-index",
   "_type": "users",
   "_id": "1",
   "_source": {
      "firstName": "John",
      "lastName": "Doe",
      "suggest": [
         {
            "input": [
               "John",
               "Doe"
            ]
         }
      ]
   }
},
{
   "_index": "my-index",
   "_type": "users",
   "_id": "2",
   "_source": {
      "firstName": "John",
      "lastName": "Smith",
      "suggest": [
         {
            "input": [
               "John",
               "Smith"
            ]
         }
      ]
   }
}

И индивидуальный запрос:

POST /my-index/_suggest?pretty
{
    "my-suggest" : {
        "text" : "joh",
        "completion" : {
            "field" : "suggest"
        }
    }
}

Что дает следующие результаты:

{
   "_shards": {
      "total": 5,
      "successful": 5,
      "failed": 0
   },
   "my-suggest": [
      {
         "text": "joh",
         "offset": 0,
         "length": 3,
         "options": [
            {
               "text": "John",
               "_index": "my-index",
               "_type": "users",
               "_id": "1",
               "_score": 1,
               "_source": {
                 "firstName": "John",
                 "lastName": "Doe",
                 "suggest": [
                    {
                       "input": [
                          "John",
                          "Doe"
                       ]
                    }
                 ]
               }
            },
            {
               "text": "John",
               "_index": "my-index",
               "_type": "users",
               "_id": "2",
               "_score": 1,
               "_source": {
                 "firstName": "John",
                 "lastName": "Smith",
                 "suggest": [
                    {
                       "input": [
                          "John",
                          "Smith"
                       ]
                    }
                 ]
               }
            }
         ]
      }
   ]
}

Короче говоря, для завершения предложения по тексту "joh" были возвращены два (2) документа - и у Джона, и у обоих было одинаковое значение text имущество.

Тем не менее, я хотел бы получить одно (1) слово. Что-то простое, как это:

{
   "_shards": {
      "total": 5,
      "successful": 5,
      "failed": 0
   },
   "my-suggest": [
      {
         "text": "joh",
         "offset": 0,
         "length": 3,
         "options": [
          "John"
         ]
      }
   ]
}

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

Подходит ли "Предложение о завершении" для моего сценария? Или я должен использовать совершенно другой подход?


РЕДАКТИРОВАТЬ: Как многие из вас указали, дополнительный индекс только для завершения будет жизнеспособным решением. Тем не менее, я вижу несколько проблем с этим подходом:

  1. Синхронизация нового индекса.
  2. Автозаполнение последующих слов, вероятно, будет глобальным, а не суженным. Например, скажем, у вас есть следующие слова в дополнительном индексе: "John", "Doe", "David", "Smith", Когда запрашиваете "John D"результат для неполного слова должен быть "Doe" и не "Doe", "David",

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

3 ответа

Решение

Как указывалось в комментарии, еще один способ достичь этого без получения дубликатов документов - это создать подполе для firstname поле, содержащее нграммы поля. Сначала вы определяете свое отображение следующим образом:

PUT my-index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "completion_analyzer": {
          "type": "custom",
          "filter": [
            "lowercase",
            "completion_filter"
          ],
          "tokenizer": "keyword"
        }
      },
      "filter": {
        "completion_filter": {
          "type": "edge_ngram",
          "min_gram": 1,
          "max_gram": 24
        }
      }
    }
  },
  "mappings": {
    "users": {
      "properties": {
        "autocomplete": {
          "type": "text",
          "fields": {
            "raw": {
              "type": "keyword"
            },
            "completion": {
              "type": "text",
              "analyzer": "completion_analyzer",
              "search_analyzer": "standard"
            }
          }
        },
        "firstName": {
          "type": "text"
        },
        "lastName": {
          "type": "text"
        }
      }
    }
  }
}

Затем вы индексируете несколько документов:

POST my-index/users/_bulk
{"index":{}}
{ "firstName": "John", "lastName": "Doe", "autocomplete": "John Doe"}
{"index":{}}
{ "firstName": "John", "lastName": "Deere", "autocomplete": "John Deere" }
{"index":{}}
{ "firstName": "Johnny", "lastName": "Cash", "autocomplete": "Johnny Cash" }

Затем вы можете запросить joh и получить один результат для John и еще один для Johnny

{
  "size": 0,
  "query": {
    "term": {
      "autocomplete.completion": "john d"
    }
  },
  "aggs": {
    "suggestions": {
      "terms": {
        "field": "autocomplete.raw"
      }
    }
  }
}

Результаты:

{
  "aggregations": {
    "suggestions": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "John Doe",
          "doc_count": 1
        },
        {
          "key": "John Deere",
          "doc_count": 1
        }
      ]
    }
  }
}

Дополнительное поле skip_duplicates будет добавлено в следующем выпуске 6.x.

Из документов по адресу https://www.elastic.co/guide/en/elasticsearch/reference/master/search-suggesters-completion.html:

POST music/_search?pretty
{
    "suggest": {
        "song-suggest" : {
            "prefix" : "nor",
            "completion" : {
                "field" : "suggest",
                "skip_duplicates": true
            }
        }
    }
}

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

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

Другие вопросы по тегам