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 场景,这在我的真实场景中会更复杂。
再次感谢。
我有以下情况:
我们目前使用商业解决方案实现了产品搜索。 我正在尝试使用 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.
提前致谢。
基督教徒
这不是解决方案,但也许是另一种方法。
首先,
如果您按照 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 场景,这在我的真实场景中会更复杂。
再次感谢。