Индексирование/поиск "сложного" JSON в elasticsearch

У меня есть JSON, который выглядит следующим образом: Позвольте назвать эти метаданные полей

{ 
  "somekey1": "val1",
  "someotherkey2": "val2",
  "more_data": { 
    "contains_more": [
      { 
        "foo": "val5",
        "bar": "val6"
      },
      { 
        "foo": "val66",
        "baz": "val44"
      },
    ],
    "even_more": {
      "foz" : 1234,
    }
  }
}

Это простой пример. Реальный может стать еще более сложным. Ключи могут появляться несколько раз. Значения также могут быть int или str.

Теперь первая проблема заключается в том, что я не совсем уверен, как мне правильно индексировать это в elasticsearch, чтобы я мог найти что-то с конкретными запросами.

Я использую Django/Haystack, где индекс выглядит следующим образом:

class FooIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.CharField(document=True, use_template=True)
    metadata = indexes.CharField(model_attr='get_metadata')
    # and some more specific fields

И шаблон:

{
    "foo": {{ object.foo }},
    "metadata": {{ object.metadata}},
    # and some more
}

Затем метаданные будут заполнены образцом выше, и результат будет выглядеть следующим образом:

  {
    "foo": "someValue",
    "metadata": { 
      "somekey1": "val1",
      "someotherkey2": "val2",
      "more_data": { 
        "contains_more": [
          { 
            "foo": "val5",
            "bar": "val6"
          },
          { 
            "foo": "val66",
            "baz": "val44"
          },
        ],
        "even_more": {
          "foz" : 1234,
        }
      }
    },
  }

который войдет в столбец "текст" в elasticsearch.

Итак, теперь цель состоит в том, чтобы искать такие вещи, как:

  • foo: val5
  • foz: 12 *
  • bar: val *
  • somekey1: val1
  • и т.д.

Вторая проблема: Когда я ищу, например. для foo: val5 он соответствует всем объектам, которые имеют только ключ "foo" и все объекты, имеющие val5 где-то еще в нем.

Вот как я ищу в Django:

self.searchqueryset.auto_query(self.cleaned_data['q'])

Иногда результаты "okayish" иногда просто бесполезны.

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

Изменить: я добавил свое окончательное решение в качестве ответа ниже!

Ответ 1

Потребовалось некоторое время, чтобы выяснить правильное решение, которое работает для меня

Это было сочетание как предоставленных ответов @juliendangers, так и @Val и еще одна настройка.

  • Я заменил Haystack более конкретным django-simple-elasticsearch
  • Добавлен пользовательский метод get_type_mapping к модели

    @classmethod
    def get_type_mapping(cls):
      return {
        "properties": {
          "somekey": {
            "type": "<specific_type>",
            "format": "<specific_format>",
          },
          "more_data": {
            "type": "nested",
            "include_in_parent": True,
            "properties": {
              "even_more": {
                "type": "nested",
                "include_in_parent": True,
              }
              /* and so on for each level you care about */
           }
         }
      }
    
  • Добавлен пользовательский метод get_document к модели

    @classmethod
    def get_document(cls, obj):
      return {
        'somekey': obj.somekey,
        'more_data': obj.more_data,
        /* and so on */
      }
    
  • Добавить пользовательскую форму поиска

    class Searchform(ElasticsearchForm):
      q = forms.Charfield(required=False)
    
      def get_index(self):
        return 'your_index'
    
      def get_type(self):
        return 'your_model'
    
      def prepare_query(self):
        if not self.cleaned_data['q']:
          q = "*"
        else:
          q = str(self.cleaned_data['q'])
    
        return {
          "query": {
            "query_string": {
              "query": q
            }
          }
        }
    
      def search(self):
        esp = ElasticsearchProcessor(self.es)
        esp.add_search(self.prepare_query, page=1, page_size=25, index=self.get_index(), doc_type=self.get_type())
        responses = esp.search()
        return responses[0]
    

Итак, это то, что сработало для меня, и охватывает мои сервисы. Может быть, это может помочь кому-то.

Ответ 2

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

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

{
  "your_type_name": {
    "properties": {
      "foo": {
        "type": "string"
      },
      "metadata": {
        "type": "object",
        "properties": {
          "some_key": {
            "type": "string"
          },
          "someotherkey2": {
            "type": "string"
          },
          "more_data": {
            "type": "object",
            "properties": {
              "contains_more": {
                "type": "nested",
                "properties": {
                  "foo": {
                    "type": "string"
                  },
                  "bar": {
                    "type": "string"
                  },
                  "baz": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Затем, как уже упоминалось в комментарии в своем комментарии, auto_query не будет вырезать его, главным образом из-за множественных уровней вложенности. Насколько я знаю, Django/Haystack не поддерживает вложенные запросы из коробки, но вы можете расширить Haystack, чтобы поддержать его. Вот сообщение в блоге, в котором объясняется, как решить эту проблему: http://www.stamkracht.com/extending-haystacks-elasticsearch-backend. Не уверен, что это поможет, но вы должны дать ему попробовать и сообщить нам, если вам нужна дополнительная помощь.

Ответ 3

Индексирование:

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

Но 30 клавиш не так высоки, и вы должны предпочесть определять собственное сопоставление, не позволяя Elasticsearch угадывать его для вас (в случае если раньше были добавлены неверные данные, отображение было бы определено в соответствии с этими данными)

Поиск:

Вы не можете выполнить поиск

foz: val5

поскольку ключ "foz" не существует.

Но ключ "metadata.more_data.even_more.foz" does = > все ваши ключи сглажены из корня вашего документа

таким образом вам придется искать

foo: val5
metadata.more_data.even_more.foz: 12*
metadata.more_data.contains_more.bar: val*
metadata.somekey1: val1

Использование query_string, например

"query_string": {
    "default_field": "metadata.more_data.even_more.foz",
    "query": "12*"
}

Или если вы хотите искать в нескольких полях

"query_string": {
    "fields" : ["metadata.more_data.contains_more.bar", "metadata.somekey1"],
    "query": "val*"
}