Elasticsearch - 寻找 must_not with ids 的高效方式

Elasticsearch - Looking for a performant way of must_not with ids

我有以下情况:

我们目前使用商业解决方案实现了产品搜索。 我正在尝试使用 Elasticsearch 来实现我们当前使用 Elasticsearch 进行的产品搜索,基本上效果很好。 但我们有一个专长。我们有大约 100 万种产品的产品目录,但并不是每个客户都可以购买每一种产品。 定义客户是否可以购买产品的规则很多。

不只是:

不允许客户 A 购买供应商 A 的产品

或者:

不允许客户 B 购买供应商 B 的类别 B 的产品。

那很容易。

为了获得不允许客户购买的这些产品,我们在 microservice/webservice 年前实施了。此网络服务 returns 一个产品黑名单,只是一个产品编号列表。

问题是,如果我只是 运行 在 Elasticsearch 中忽略这些列入黑名单的产品的查询,我会取回不允许客户购买的产品。如果我只查询前 10 个搜索结果,则可能会发生不允许我展示这些产品的情况,因为不允许客户购买它们。 此外,如果我对供应商和类别使用聚合,我会返回供应商 and/or 类别,客户可能不允许从中购买。

我在原型中做了什么?

在查询 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 在我的情况下表现非常糟糕。因为我猜Elasticsearch各自的Lucene匹配过程可不止一个:

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

有没有大佬指点一下,如何实现这样的黑名单机制性能更好?

有没有办法拦截Elasticsearch/Lucene查询过程? 也许我可以编写自己真正的 Lucene 查询而不是使用 TermInSetQuery.

提前致谢。

基督教徒

这不是解决方案,但也许是另一种方法。

首先, 是一个较旧的 SO post,您可能会感兴趣。据我所知,最新版本的 Elasticsearch 没有 introduce/change 更好或更合适的东西。

如果您按照 Terms Query Documentation 页面答案的 link 进行操作,您会发现一个非常简单的示例。

现在,您可以创建一个索引并为每个客户存储黑名单,而不是缓存您的黑名单。然后您可以使用术语查询,基本上从其他索引(=您的黑名单缓存)引用黑名单。

我不知道这些黑名单的更新频率,所以这可能是个问题。另外,您必须小心不要不同步。特别值得一提的是,索引 inserts/updates 默认情况下不会立即可见。所以你可能需要在 indexing/updating 黑名单时强制刷新。

正如我所说,这可能不是解决方案。但如果您觉得可行,不妨尝试与您的其他解决方案进行比较。

感谢您的提示。其实我想避免索引黑名单信息。因此我决定自己写一个 Elasticsearch 黑名单插件。但我越想越不喜欢我的想法。如果我能摆脱我的插件,我就不必维护我的插件,并且可以更容易地移动到云。所以,我尝试了一些东西。

测试场景:

我创建了一个包含 100,000 个文档的测试索引,其中包括哪些客户不允许购买哪些产品的信息。 例如

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

此外,我还创建了一个黑名单索引,其中包含一个包含 10,000 项黑名单的文档,以测试术语查找。 (应该代表一个客户的黑名单。)

我使用了 5.1.2 版的现有 Elasticsearch 安装。

测试 1:

已忽略黑名单。只是查询关键字。

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

测试 2:

黑名单考虑到must_not和ids加关键字。 (注意:服务器和客户端在同一台主机上。因此我们没有网络瓶颈。)

  "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"
        }
      }
    }
      ]
    }
  }

我对每个测试场景进行了 1,000 次搜索。这是结果:

测试 1:3,708 毫秒

测试 2:104,775 毫秒

测试 3:39,586 毫秒

测试 4:3,586 毫秒

如您所见,test 2 with must_not and ids 执行最慢。 带有术语查找的测试 3 执行速度比 测试 1 慢 11 倍。 测试 4测试 1.

表现稍好

如果 test 3 场景足以满足我的真实需求,我会尝试,因为实现这一点很容易。如果不是,我必须使用 test 4 场景,这在我的真实场景中会更复杂。

再次感谢。