Elasticsearch - Ищете эффективный способ must_not с идентификаторами

У меня следующая ситуация:

В настоящее время мы осуществляем поиск продуктов с помощью коммерческого решения. Я играю с Elasticsearch, чтобы реализовать наш текущий поиск продуктов с Elasticsearch, который в принципе работает очень хорошо. Но у нас есть одна специальность. У нас есть каталог товаров, насчитывающий около 1 миллиона товаров, но не каждому покупателю разрешено покупать каждый товар. Есть много правил, определяющих, может ли продукт быть куплен покупателем.

Это не просто

Клиент А не имеет права покупать товары продавца А

Или же:

Клиент B не имеет права покупать товары категории B поставщика B.

Это было бы легко.

Чтобы получить эти продукты, которые клиент не имеет права покупать, мы внедрили микросервис / веб-сервис несколько лет назад. Этот веб-сервис возвращает черный список продуктов, просто список номеров продуктов.

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

Что я делал в своем прототипе?

Перед запросом Elasticsearch я запрашиваю черный список продуктов для определенного клиента (и, конечно, кеширую его). После того, как я получил черный список, я запускаю запрос, подобный этому:

{
  "query" : {
    "bool" : {
      "must_not" : [
        {
          "ids" : {
            "values" : [

              // Numbers of blacklisted products. Can be thousands!

              1234567,
              1234568,
              1234569,
              1234570,
              ...
            ]
          }
        }
      ],
      "should" : [
        {
        "query" : {
            ...
          }
        ]
      }
    }
  }
  "aggregations" : {
    ...
  }
}

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

Мой следующий подход состоял в том, чтобы разработать свой собственный построитель запросов Elasticsearch в качестве плагина, который обрабатывает черные списки внутри Elasticsearch. Этот запрос в черный список расширяет AbstractQueryBuilder и использует TermInSetQuery. Таким образом, этот построитель запросов один раз запрашивает черный список данного клиента, кэширует его и создает TermInSetQuery со всеми этими номерами продуктов, занесенными в черный список.

Теперь мой запрос выглядит так:

{
  "query" : {
    "bool" : {
      "must_not" : [
        {
          "blacklist" : {         <-- This is my own query builder
            "customer" : 1234567
          }
        }
      ],
      "should" : [
        {
        "query" : {
            ...
          }
        ]
      }
    }
  }
  "aggregations" : {
    ...
  }
}

Это решение быстрее и не должно каждый раз отправлять полный список номеров продуктов в черном списке в запросе. Так что у меня нет сетевых накладных расходов. Но запрос все еще заметно медленнее, чем без этого чёрного списка. Я профилировал этот запрос, и я не удивлен, увидев, что мой черный список запросов занимает около 80-90% времени выполнения.

Я думаю, что этот TermInSetQuery работает очень плохо в моем случае. Потому что я предполагаю, что соответствующий процесс сопоставления Lucene с Elasticsearch - это нечто большее, чем просто:

if (blacklistSet.contains(id)) {
  continue; // ignore the current search hit.
}

У кого-нибудь из вас есть подсказка для меня, как сделать такой механизм черного списка более производительным?

Есть ли способ перехватить процесс запроса Elasticsearch/Lucene? Может быть, я могу написать свой собственный реальный запрос Lucene вместо использования TermInSetQuery.

Заранее спасибо.

Кристиан

2 ответа

Решение

Это не решение, но, возможно, другой подход.

Прежде всего, вот старый пост SO, который может вас заинтересовать. Насколько я знаю, более поздние версии Elasticsearch не вводили / не изменяли что-то более или более подходящее.

Если вы перейдете по ссылке ответа на страницу " Условия запроса", вы найдете очень простой пример.

Теперь вместо кэширования ваших черных списков вы можете создать индекс и сохранить черный список для каждого клиента. Затем вы можете использовать запрос терминов и, в основном, ссылаться на черный список из другого индекса (= кэш черного списка).

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

Как я уже сказал, это не может быть решением. Но если это кажется вам возможным, возможно, стоит попробовать сравнить с другими вашими решениями.

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

Тестовый сценарий:

Я создал тестовый индекс со 100000 документов, включая информацию о том, какой клиент не имеет права покупать продукт. Например

{
  "id" : "123456"
  "description" : "My example products",
  ...
  "blacklist" : [ <lots_of_customer_numbers> ]
}

Кроме того, я создал индекс черного списка с одним документом с черным списком из 10 000 элементов для проверки поиска терминов. (Должен представлять черный список для одного клиента.)

Я использовал существующую установку Elasticsearch версии 5.1.2.

Тест 1:

Черный список игнорируется. Просто запрос по ключевому слову.

  "query" : {
    "bool" : {
      "must" : [
    {
      "multi_match" : {
        "query" : <keyword>,
        "fields" : [
          "_all"
        ]
      }
    }
      ]
    }
  }

Тест 2:

Черный список взят в соответствие с must_not и ключевыми словами ids plus. (Примечание. Сервер и клиент находятся на одном хосте. Поэтому у нас нет узкого места в сети.)

  "query" : {
    "bool" : {
      "must" : [
    {
      "multi_match" : {
        "query" : <keyword>,
        "fields" : [
          "_all"
        ]
      }
    }
      ],
      "must_not" : [
    {
      "ids" : {
        "values" : [ <10000_ids> ]
      }
    }
      ]
    }
  }

Тест 3:

Черный список учитывается при поиске терминов плюс ключевое слово.

  "query" : {
    "bool" : {
      "must" : [
    {
      "multi_match" : {
        "query" : <keyword>,
        "fields" : [
          "_all"
        ]
      }
    }
      ],
      "must_not" : [
    {
      "terms" : {
        "blacklist" : {
          "index" : "blacklists",
          "type" : "blacklist",
          "id" : "1234567",
          "path" : "items"
        }
      }
    }
      ]
    }
  }

Тест 4:

Черный список учитывается с помощью must_not и термина запроса в пределах одного индекса и документов плюс ключевое слово.

  "query" : {
    "bool" : {
      "must" : [
    {
      "multi_match" : {
        "query" : <keyword>,
        "fields" : [
          "_all"
        ]
      }
    }
      ],
      "must_not" : [
    {
      "term" : {
        "blackList" : {
          "value" : "1234567"
        }
      }
    }
      ]
    }
  }

Я сделал 1000 поисков для каждого тестового сценария. И вот результат:

Тест 1: 3,708мс

Тест 2: 104,775мс

Тест 3: 39 586 мс

Тест 4: 3586 мс

Как вы можете видеть, тест 2 с must_not и ids выполняется медленнее. Тест 3 с поиском терминов выполняется примерно в 11 раз медленнее, чем тест 1.Тест 4 работает немного лучше, чем тест 1.

Я постараюсь, будет ли сценарий теста 3 достаточным для моих реальных потребностей, потому что понять это довольно просто. Если нет, то я должен пойти по сценарию теста 4, который был бы более сложным в моем реальном живом сценарии.

Большое спасибо, еще раз.

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