非常慢的 elasticsearch 术语聚合。怎么提高?

Very slow elasticsearch term aggregation. How to improve?

我们在 elastic(1.6.2) 中存储了约 20M(酒店优惠)文档,重点是按多个字段(duration, start_date, adults, kids)和 select 中的一个最便宜的报价对文档进行分组每组。我们必须按成本字段对这些结果进行排序。

为了避免子聚合,我们将目标字段值合并为一个名为 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": [

    ]
  },...

处理中等数量的过滤文档和每个组中最便宜的 select 似乎不应该花这么长时间。它可以在应用程序内部完成,这对我来说似乎是一个丑陋的 hack。

日志中满是这样的行:

[DEBUG][index.fielddata.plain ] [Karen Page] [offers] Global-ordinals[default_group_field][2564761] took 2453 ms

这就是我们更新映射以在索引更新时执行急切 global_ordinals 重建的原因,但这并未对查询时间产生显着影响。

有什么方法可以加速这种聚合,或者可以告诉 elastic 只对过滤后的文档进行聚合。

或者如此长的查询执行还有其他原因吗?非常感谢任何想法!

好的。我会尽力回答这个问题, 问题中有几部分我无法理解 -

To avoid sub-aggregations we have united target fields values into one called default_group_field by joining them with dot(.)

我不确定你的意思,因为你这么说,

您添加此字段是为了避免聚合(但是如何?如果您是 joining them with dot(.),又如何避免聚合?)

好的。即使我也是弹性搜索的新手。所以如果有什么我遗漏的,你可以评论这个答案。谢谢,

我会继续回答这个问题。

But before that I am assuming that you have that(default_group_field) field to differentiate between records 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
}

文档 4:

{
"default_group_field": "adults",
"cost": 150,
"documentId":4
}

所以现在你有了这些文件,你想得到最小值。 adultskids 的成本文档:

因此您的查询应如下所示:

    {
      "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": {} } }   
       }
    }

To explain the above query, what I am doing is grouping the document by "default_group_field" and then I am sorting each group by cost and size:1 helps me to get the just one document.

因此此查询的结果将为最小值。每个类别的成本文件(adultskids

通常当我尝试为弹性搜索或数据库编写查询时。我尽量减少文档或行数。

我想我对你的问题的理解是正确的。 如果我对你的问题的理解有误或者我做错了什么,请回复并告诉我哪里错了。

谢谢,

再次感谢您的努力。

终于解决了主要问题,性能恢复正常。

简而言之,我们做了以下工作: - 将 default_group_field 的映射更新为 Long 类型 - 压缩 default_group_field 值,使其匹配类型 Long

一些解释:

字符串字段的聚合需要对其进行一些工作。正如我们从日志中看到的那样,为具有很大差异的字段构建 Global Ordinals 非常昂贵。事实上,我们只对提到的字段进行聚合。话虽如此,使用 String 类型并不是很有效。

所以我们将映射更改为:

default_group_field: {
  type: 'long',
  index: 'not_analyzed'
}

这样我们就不会触及那些昂贵的操作。

在此之后,相同的查询时间减少到约 100 毫秒。它还降低了 CPU 使用率。

PS 1

我从 global ordinals

的文档中获得了很多信息

PS 2

我仍然不知道如何绕过 String 类型字段的这个问题。有想法欢迎评论。

这可能是由于术语聚合的默认行为,它需要构建全局序数。对于 high-cardinality 个字段,此计算可能很昂贵。

以下博客解决了这种性能不佳的可能原因以及解决该问题的几种方法。

https://www.elastic.co/blog/improving-the-performance-of-high-cardinality-terms-aggregations-in-elasticsearch