我们有~20M(酒店提供)文件存储在弹性(1.6.2)中,重点是按多个字段(duration, start_date, adults, kids
)对文档进行分组,并从每个组中选择一个最便宜的报价。我们必须按成本字段对这些结果进行排序。
为了避免子聚合,我们将目标字段值合并为一个名为default_group_field
的值,方法是将它们与点(.
)连接起来。
该字段的映射如下所示:
"default_group_field": {
"index": "not_analyzed",
"fielddata": {
"loading": "eager_global_ordinals"
},
"type": "string"
}
我们执行的查询看起来像这样:
{
"size": 0,
"aggs": {
"offers": {
"terms": {
"field": "default_group_field",
"size": 5,
"order": {
"min_sort_value": "asc"
}
},
"aggs": {
"min_sort_value": {
"min": {
"field": "cost"
}
},
"cheapest": {
"top_hits": {
"_source": {}
},
"sort": {
"cost": "asc"
},
"size": 1
}
}
}
}
},
"query": {
"filtered": {
"filter": {
"and": [
...
]
}
}
}
}
问题是此类查询需要几秒钟(2-5秒)才能加载。
但是,一旦我们执行没有汇总的查询,我们会在100毫秒内获得适量的结果(比如"total": 490
)。
{
"took": 53,
"timed_out": false,
"_shards": {
"total": 6,
"successful": 6,
"failed": 0
},
"hits": {
"total": 490,
"max_score": 1,
"hits": [...
但是使用聚合需要2秒:
{
"took": 2158,
"timed_out": false,
"_shards": {
"total": 6,
"successful": 6,
"failed": 0
},
"hits": {
"total": 490,
"max_score": 0,
"hits": [
]
},...
似乎不应该花这么长时间来处理适量的过滤文档并选择每组中最便宜的文档。它可以在应用程序内完成,这对我来说似乎是一个丑陋的黑客。
日志中有很多行说明:
[DEBUG][index.fielddata.plain ] [Karen Page] [offers] Global-ordinals[default_group_field][2564761] took 2453 ms
这就是我们更新映射以在索引更新时执行急切的global_ordinals重建的原因,但这并没有对查询时间产生显着影响。
有没有办法加速这种聚合,或者可能是一种告诉弹性仅对过滤文档进行聚合的方法。
或许还有另一个这么长的查询执行来源?任何想法都高度赞赏!
答案 0 :(得分:6)
再次感谢您的努力。
最后我们解决了主要问题,我们的表现恢复正常。
简而言之,我们做了以下事情:
- 将default_group_field
的映射更新为Long
类型
- 压缩default_group_field
值,使其匹配类型Long
一些解释:
字符串字段的聚合需要对它们进行一些工作。正如我们从日志中看到的那样构建Global Ordinals
的那个具有非常广泛差异的字段非常昂贵。实际上,我们只对所提到的字段进行聚合。据说使用String
类型效率不高。
所以我们已将映射更改为:
default_group_field: {
type: 'long',
index: 'not_analyzed'
}
这样我们就不会触及那些昂贵的操作。
此后和相同的查询时间减少到~100ms。它还降低了CPU使用率。
PS 1
的文档中获得了大量信息PS 2
我仍然不知道如何使用String
类型的字段绕过此问题。如果你有一些想法,请评论。
答案 1 :(得分:0)
确定。我会尽力回答这个, 问题中很少有我无法理解的部分 -
为避免子聚合,我们将目标字段值合并为一个名为default_group_field的字段,方法是将它们与点(。)连接起来。
我不确定你的意思是因为你这么说,
您添加了此字段以避免聚合(但是如何?如果您是joining them with dot(.)
,还如何避免聚合?)
确定。即使我也是弹性搜索的新手。所以如果有什么我错过了,你可以评论这个答案。谢谢,
我将继续回答这个问题。
但在此之前我假设你有 那个(
default_group_field
)字段来区分记录duration
,start_date
,adults
,kids
。
在我的解决方案之后,我将尝试提供以下一个示例。
我的解决方案:
{
"size": 0,
"aggs": {
"offers": {
"terms": {
"field": "default_group_field"
},
"aggs": {
"sort_cost_asc": {
"top_hits": {
"sort": [
{
"cost": {
"order": "asc"
}
}
],
"_source": {
"include": [ ... fields you want from the document ... ]
},
"size": 1
}
}
}
}
},
"query": {
"... your query part ..."
}
}
我会尝试解释我在这里要做的事情:
我假设您的文档看起来像这样(可能还有一些嵌套,但是例如我试图保持文档尽可能简单):
<强>文档1:强>
{
"default_group_field": "kids",
"cost": 100,
"documentId":1
}
<强>文件2:强>
{
"default_group_field": "kids",
"cost": 120,
"documentId":2
}
<强>文件3:强>
{
"default_group_field": "adults",
"cost": 50,
"documentId":3
}
<强> document4:强>
{
"default_group_field": "adults",
"cost": 150,
"documentId":4
}
所以现在你有这些文件,你想得到分钟。 adults
和kids
的费用文件:
因此您的查询应如下所示:
{
"size": 0,
"aggs": {
"offers": {
"terms": {
"field": "default_group_field"
},
"aggs": {
"sort_cost_asc": {
"top_hits": {
"sort": [
{
"cost": {
"order": "asc"
}
}
],
"_source": {
"include": ["documentId", "cost", "default_group_field"]
},
"size": 1
}
}
}
}
},
"query": {
"filtered":{ "query": { "match_all": {} } }
}
}
为了解释上述问题,我正在做的是按
"default_group_field"
对文档进行分组,然后我sorting each group by cost
和size:1
帮助我获取一个文档。
因此,此查询的结果将为min。每个类别中的费用文档(adults
和kids
)
通常当我尝试编写弹性搜索或db的查询时。我尽量减少文档或行的数量。
我认为我理解你的问题是正确的。 如果我在理解你的问题时错了,或者我犯了一些错误,请回复并告诉我出错的地方。
谢谢,
答案 2 :(得分:0)
这可能是由于术语聚合的默认行为所致,这需要构建全局序数。对于高基数字段,此计算可能会很昂贵。
以下博客解决了这种性能不佳的可能原因以及几种解决方法。