使用重复使用的术语查询过滤器可加快Elasticsearch查询的速度

时间:2019-04-20 05:32:51

标签: performance elasticsearch filter query-cache

我将需要找出一个标签和另一组固定标签之间的共现时间。我有10000个不同的单一标签,并且在固定的标签集中有10k个标签。我在固定时间范围内的一组固定标记上下文下遍历所有单个标记。我在索引中总共有10亿个文档,其中包含20个分片。

这是elasticsearch查询,elasticsearch 6.6.0:

es.search(index=index, size=0, body={ 
        "query": {
          "bool": {
              "filter": [
                  {"range": {
                      "created_time": {
                       "gte": fixed_start_time,  
                       "lte": fixed_end_time, 
                       "format": "yyyy-MM-dd-HH"
                       }}},
                        {"term": {"tags": dynamic_single_tag}},
                        {"terms": {"tags": {
                            "index" : "fixed_set_tags_list",
                            "id" : 2,
                            "type" : "twitter",
                            "path" : "tag_list"
                        }}}
                       ]

                }
          }, "aggs": {
             "by_month": {
              "date_histogram": {
                  "field": "created_time",
                  "interval": "month",
                              "min_doc_count": 0,
                              "extended_bounds": {
                                  "min": two_month_start_time,
                                  "max": start_month_start_time}

              }}}
        }) 

我的问题:是否有任何解决方案可以在elasticsearch内提供一个用于固定的10k标签项查询和时间范围过滤器的缓存,以加快查询时间?我在上面的查询中为一个标签花费了1.5秒的时间。

1 个答案:

答案 0 :(得分:1)

您所看到的是Elasticsearch聚合的正常行为(实际上,鉴于您有10亿个文档,性能相当不错。

您可以考虑以下几种选择:使用一批filter聚合,使用一部分文档重新编制索引,从Elasticsearch中下载数据并离线计算共现。

但是可能值得尝试发送这些10K查询,并查看Elasticsearch内置缓存是否启动。

让我更详细地说明每个选项。

使用filter聚合

首先,让我们概述一下原始ES查询中的操作:

  • 在特定时间范围内用create_time过滤文档;
  • 过滤包含所需标签dynamic_single_tag的文档;
  • 还过滤列表fixed_set_tags_list中具有至少一个标签的文档;
  • 计算在特定时间段内每个月有多少此类文件。

性能是一个问题,因为我们有10K的标签可以进行此类查询。

我们在这里可以做的是将filter上的dynamic_single_tag从查询移到汇总:

POST myindex/_doc/_search
{
  "size": 0,
  "query": {
    "bool": {
      "filter": [
        { "terms": { ... } }
      ]
    }
  },
  "aggs": {
    "by tag C": {
      "filter": {
        "term": {
          "tags": "C" <== here's the filter
        }
      },
      "aggs": {
        "by month": {
          "date_histogram": {
            "field": "created_time",
            "interval": "month",
            "min_doc_count": 0,
            "extended_bounds": {
              "min": "2019-01-01",
              "max": "2019-02-01"
            }
          }
        }
      }
    }
  }
}

结果将如下所示:

  "aggregations" : {
    "by tag C" : {
      "doc_count" : 2,
      "by month" : {
        "buckets" : [
          {
            "key_as_string" : "2019-01-01T00:00:00.000Z",
            "key" : 1546300800000,
            "doc_count" : 2
          },
          {
            "key_as_string" : "2019-02-01T00:00:00.000Z",
            "key" : 1548979200000,
            "doc_count" : 0
          }
        ]
      }
    }

现在,如果您要问这对性能有何帮助,请按以下步骤:为每个标记filter"by tag D"等添加更多这样的"by tag E"聚合。

改进将来自“批量”请求combining many initial requests into one。将所有10K标记放在一个查询中可能不切实际,但每个查询甚至100个标签的批处理都可以改变游戏规则。

(请注意:通过使用terms过滤器参数进行include聚合,可以实现大致相同的行为。)

这种方法当然需要动手并编写一些更复杂的查询,但是如果需要在零准备的情况下随机运行此类查询,它将很方便。

为文档重新编制索引

第二种方法背后的想法是通过reindex API预先减少文档的数量。 reindex查询可能看起来像这样:

POST _reindex
{
  "source": {
    "index": "myindex",
    "type": "_doc",
    "query": {
      "bool": {
        "filter": [
          {
            "range": {
              "created_time": {
                "gte": "fixed_start_time",
                "lte": "fixed_end_time",
                "format": "yyyy-MM-dd-HH"
              }
            }
          },
          {
            "terms": {
              "tags": {
                "index": "fixed_set_tags_list",
                "id": 2,
                "type": "twitter",
                "path": "tag_list"
              }
            }
          }
        ]
      }
    }
  },
  "dest": {
    "index": "myindex_reduced"
  }
}

此查询将创建一个新索引myindex_reduced,其中仅包含满足前2个过滤子句的元素。

这时,原始查询可以不用这两个子句来完成。

在这种情况下,提高速度将来自限制文档的数量,数量越小,收益就越大。因此,如果fixed_set_tags_list仅剩10亿美元,那么您绝对可以尝试一下。

在Elasticsearch外部下载数据并进行处理

说实话,这个用例更像是pandas的工作。如果您需要数据分析,我建议使用scroll API提取磁盘上的数据,然后使用任意脚本进行处理。

在python中,就像使用elasticsearch库的.scan()帮助方法一样简单。

为什么不尝试暴力手段?

Elasticsearch已经尝试通过request cache帮助您进行查询。它仅适用于纯聚合查询(size: 0),因此应适用于您的情况。

但是不会,因为查询的内容总是不同的(整个JSON of the query is used as caching key,并且每个查询中都有一个新标记)。不同级别的缓存将开始播放。

Elasticsearch heavily relies on the filesystem cache,这意味着在后台,文件系统中访问频率更高的块将被缓存(实际上加载到RAM中)。对于最终用户而言,这意味着“热身”将缓慢进行,并且会有大量类似的请求。

对于您来说,聚合和过滤将在两个字段上进行:create_timetags。这意味着在执行了10或100个带有不同标签的请求后,响应时间将从1.5秒减少到更可忍受的时间。

为证明我的观点,这是我对Elasticsearch性能的研究中的Vegeta图,该查询在具有固定RPS发送的大量聚合的同一查询下:

Filesystem cache kicks in

如您所见,最初的请求耗时约10秒钟,而在100个请求后,耗时减少为200ms。

我绝对建议尝试这种“强力”方法,因为如果有效,那么就很好,如果不行,那就没什么用。

希望有帮助!