Elasticsearch - 寻找一种使用 id 进行must_not的高性能方式



我有以下情况:

我们目前通过商业解决方案实现了产品搜索。 我正在使用Elasticsearch来实现我们当前的Elasticsearch产品搜索,它基本上工作得很好。 但我们有一个专长。我们的产品目录包含大约 100 万种产品,但并非每个客户都可以购买每种产品。 有许多规则定义了客户是否可以购买产品。

这不仅仅是:

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

或:

客户 B 不得购买供应商 B 的 B 类产品。

那很容易。

为了获得这些不允许客户购买的产品,我们几年前实施了微服务/Web服务。此 Web 服务返回一个产品黑名单,只是一个产品编号列表。

问题是,如果我只是在 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在我的情况下表现非常糟糕。因为我想Elasticsearch各自的Lucene匹配过程不仅仅是一个:

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

你们有人对我有提示吗,如何实现这样的黑名单机制更有性能?

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

提前谢谢。

基督教

这不是解决方案,但可能是另一种方法。

首先,这里有一个您可能感兴趣的较旧的SO帖子。据我所知,较新版本的Elasticsearch并没有引入/更改更好或更合适的东西。

如果您按照答案的链接进入术语查询文档页面,您会发现一个非常简单的示例。

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

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

正如我所说,这可能不是一个解决办法。但是,如果对您来说听起来可行,则可能值得尝试与其他解决方案进行比较。

感谢您的提示。实际上,我想避免索引黑名单信息。因此,我决定编写自己的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和ID以及关键字。(注意:服务器和客户端位于同一主机上。因此,我们没有网络作为瓶颈。

"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,775ms

测试 3:39,586ms

测试 4:3,586 毫秒

如您所见,具有must_not和 ids 的测试 2执行速度最慢。使用术语查找的测试3的执行速度比测试 1慢约 11 倍。 测试4的性能略好于测试 1

我将尝试测试 3场景是否足以满足我现实世界的需求,因为实现这一点非常容易。如果没有,我必须使用测试 4场景,这在我的真实现场场景中会更加复杂。

再次感谢。

相关内容

最新更新