如何使用include和regex在elasticsearch中正确查询术语内部的聚合值



如何有效地筛选/搜索聚合结果

假设您有100万个文档处于弹性搜索中。在这些文档中,您有一个多字段(关键字、文本(tags:

{
...
tags: ['Race', 'Racing', 'Mountain Bike', 'Horizontal'],
...
},
{
...
tags: ['Tracey Chapman', 'Silverfish', 'Blue'],
...
},
{
...
tags: ['Surfing', 'Race', 'Disgrace'],
...
},

您可以使用这些值作为过滤器(facets(,针对查询只提取包含以下标记的文档:

...
"filter": [
{
"terms": {
"tags": [
"Race"
]
}
},
...
]

但是您希望用户能够查询可能的标记过滤器。因此,如果用户键入race,则返回应该显示(来自前面的示例(['Race', 'Tracey Chapman', 'Disgrace']。这样,用户就可以查询要使用的过滤器。为了实现这一点,我不得不使用聚合:

{
"aggs": {
"topics": {
"terms": {
"field": "tags",
"include": ".*[Rr][Aa][Cc][Ee].*", // I have to dynamically form this
"size": 6
}
}
},
"size": 0
}

这正是我所需要的!但它很慢,非常慢。我试着添加execution\hint,它对我没有帮助。

你可能会想;只需在聚合之前使用查询"但问题是,它将提取该查询中所有文档的所有值。也就是说,您可以显示完全不相关的标签。如果我在聚合之前查询race,并且没有使用include regex,那么我最终会得到所有其他值,比如'Horizontal', etc...

如何重写此聚合以更快地工作?有更好的写法吗?我真的必须为值单独创建索引吗?(悲伤的脸(这似乎是一个常见的问题,但通过文档和谷歌搜索没有找到答案。

您当然不需要仅为值创建单独的索引。。。

以下是我的看法:

  1. 您使用正则表达式所做的基本上是标记化器应该做的事情,即构建子字符串(或N-gram(,以便以后可以针对它们
    这意味着需要将关键字Race标记为n-gram["rac", "race", "ace"](少于3个字符是没有意义的——大多数自动完成库选择忽略少于3个的字符,因为可能的匹配膨胀得太快了。(

Elasticsearch提供了N-gram标记器,但我们需要将名为max_ngram_diff的默认索引级别设置从1增加到(任意(10,因为我们希望捕获尽可能多的ngram:

PUT tagindex
{
"settings": {
"index": {
"max_ngram_diff": 10
},
"analysis": {
"analyzer": {
"my_ngrams_analyzer": {
"tokenizer": "my_ngrams",
"filter": [ "lowercase" ]
}
},
"tokenizer": {
"my_ngrams": {
"type": "ngram",
"min_gram": 3,
"max_gram": 10,
"token_chars": [ "letter", "digit" ]
}
}
}
},
{ "mappings": ... }                                 --> see below
}
  1. 当您的tags字段是一个关键字列表时,如果不使用include选项(可以是精确匹配项,也可以是正则表达式(您已经在使用(,就不可能在该字段上进行聚合。现在,我们不能保证完全匹配,但我们也不想regex!因此,我们需要使用嵌套列表,该列表将分别处理每个标记

现在,嵌套列表应该包含对象,因此

{
"tags": ["Race", "Racing", "Mountain Bike", "Horizontal"]
}

将需要转换为

{
"tags": [
{ "tag": "Race" },
{ "tag": "Racing" },
{ "tag": "Mountain Bike" },
{ "tag": "Horizontal" }
]
}

之后,我们将继续进行多字段映射,保持原始标签不变,但也添加了一个要搜索的.tokenized字段和一个要聚合的.keyword字段:

"index": { ... },
"analysis": { ... },
"mappings": {
"properties": {
"tags": {
"type": "nested",
"properties": {
"tag": {
"type": "text",
"fields": {
"tokenized": {
"type": "text",
"analyzer": "my_ngrams_analyzer"
},
"keyword": {
"type": "keyword"
}
}
}
}
}
}
}

然后我们将添加我们调整后的标签文档:

POST tagindex/_doc
{"tags":[{"tag":"Race"},{"tag":"Racing"},{"tag":"Mountain Bike"},{"tag":"Horizontal"}]}
POST tagindex/_doc
{"tags":[{"tag":"Tracey Chapman"},{"tag":"Silverfish"},{"tag":"Blue"}]}
POST tagindex/_doc
{"tags":[{"tag":"Surfing"},{"tag":"Race"},{"tag":"Disgrace"}]}

并应用嵌套过滤项聚合:

GET tagindex/_search
{
"aggs": {
"topics_parent": {
"nested": {
"path": "tags"
},
"aggs": {
"topics": {
"filter": {
"term": {
"tags.tag.tokenized": "race"
}
},
"aggs": {
"topics": {
"terms": {
"field": "tags.tag.keyword",
"size": 100
}
}
}
}
}
}
},
"size": 0
}

产生

{
...
"topics_parent" : {
...
"topics" : {
...
"topics" : {
...
"buckets" : [
{
"key" : "Race",
"doc_count" : 2
},
{
"key" : "Disgrace",
"doc_count" : 1
},
{
"key" : "Tracey Chapman",
"doc_count" : 1
}
]
}
}
}
}

注意事项

  • 要使其工作,您必须重新索引
  • ngrams将增加存储占用空间——这可能会成为一个问题,具体取决于每个文档的标签数量
  • 嵌套字段在内部被视为"嵌套"字段;单独的文件";所以这也会影响磁盘空间

p.S.:这是一个有趣的用例。让我知道实施情况如何!

最新更新