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

Ric*_*Mao 1 performance filter query-cache elasticsearch

我需要找出一个标签与另一组固定标签作为整体之间的共现时间。我有 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}

              }}}
        }) 
Run Code Online (Sandbox Code Playgroud)

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

Nik*_*iev 6

您所看到的是 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"
            }
          }
        }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

结果看起来像这样:

  "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
          }
        ]
      }
    }
Run Code Online (Sandbox Code Playgroud)

现在,如果您问这如何有助于提高性能,这里有一个技巧:为每个标签添加更多这样的filter聚合:"by tag D""by tag E"等。

改进将来自于执行“批量”请求,将许多初始请求合并为一个。将所有 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"
  }
}
Run Code Online (Sandbox Code Playgroud)

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

此时,无需这两个子句即可完成原始查询。

在这种情况下,加速将来自于限制文档数量,文档数量越小,增益就越大。所以,如果fixed_set_tags_list你只剩下10亿的​​一小部分,这是你绝对可以尝试的选择。

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

老实说,这个用例看起来更像是pandas的工作。如果您的情况是数据分析,我建议使用滚动 API将数据提取到磁盘上,然后使用任意脚本对其进行处理。

在 python 中,它可以像使用库.scan()的辅助方法一样简单elasticsearch

为什么不尝试暴力方法呢?

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

但它不会,因为查询的内容总是不同的(查询的整个 JSON 用作缓存 key,并且我们在每个查询中都有一个新标签)。不同级别的缓存将开始发挥作用。

Elasticsearch 严重依赖于文件系统缓存,这意味着在幕后,文件系统中更常访问的块将被缓存(实际上加载到 RAM 中)。对于最终用户来说,这意味着“预热”将会缓慢进行,并且会出现大量类似的请求。

在您的情况下,聚合和过滤将发生在 2 个字段上:create_timetags。这意味着,在使用不同标签执行 10 或 100 个请求后,响应时间将从 1.5 秒下降到更容易忍受的时间。

为了证明我的观点,下面是我对 Elasticsearch 性能的研究中的Vegeta图,该图是在使用固定 RPS 发送的大量聚合的相同查询下进行的:

文件系统缓存启动

正如您所看到的,最初请求花费了大约 10 秒,在 100 个请求之后,时间减少到了 200 毫秒。

我肯定会建议尝试这种“蛮力”方法,因为如果它有效,那就很好,如果不行——它不需要任何成本。

希望有帮助!