Elasticsearch:通过 sampler 聚合来改善繁重的 Elasticsearch 聚合

结合两个 Elasticsearch 功能:sampler 聚合可以帮助创建有效的估计 facet 和见解,同时显着降低繁重和缓慢聚合的成本。Sampler aggregation 是一种筛选聚合,用于将任何子聚合的处理限制为得分最高的文档样本。

典型用例

  • 将分析重点放在高相关性匹配上,而不是可能很长的低质量匹配上
  • 减少仅使用样本即可产生有用结果的聚合(比如 significant terms)运行成本

对于不想分析整个数据集的方案,sampler 聚合是一个很好的解决方案:

  • 它提高了速度和内存使用率
  • 通过滤除低质量的冗长的匹配,还可以提高结果质量
  • 通常和 significant aggregation 一起使用

考虑以下情形:你有一个基于 Elasticsearch 的搜索引擎服务,该服务不仅基于大规模 Elasticsearch 聚合返回常规搜索结果,而且还返回 facet 和见解。但是,你希望对每个查询提供这些见解,而无需事先知道将返回多少结果。


虽然可以通过某种类型的每次抓取和缓存来处理主页或仪表板,但你无法提前告知用户将输入哪个查询。你既无法预测聚合的强度,也无法预测聚合结果的速度。用户仅可以过滤总结果的一小部分,而他的查询最终将几乎与对索引中所有文档进行汇总的匹配所有查询一样重。

这些繁重的查询不仅非常慢,而且非常占用CPU,并且可能会导致诸如由于内存不足问题而导致节点崩溃的风险。

然而,在大多数情况下,高度准确的聚合结果并不那么重要,并且在任何情况下,elastic 聚合都是设计使然的

例如,在估计某个存储桶中的文档数时,报告 58,730,244 个文档而不是估计约 60,000,000 个文档真的很重要吗? 而且,最相关的查询通常是某个字段的 “前 X 个存储桶最高”,以及总百分比。动机:对数据样本进行大量聚合,并获得近似代表真实数据分布的近似结果。

 

准备数据

在今天的教程中,我们将使用 Kibana 自带的索引来进行展示。打开 Kibana 界面:

点击 Add data:

这样我们的样本数据就导入进 Elasticsearch 了。通过上面的操作,我们在 Elasticsearch 中将生成一个叫做 kibana_sample_data_flights 的索引。

 

使用 sampler aggregation

我们先做一个如下的查询:

GET kibana_sample_data_flights/_search
{
  "size": 0,
  "query": {
    "term": {
      "DestCountry": {
        "value": "AU"
      }
    }
  }
}

上面的结果显示:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 416,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  }
}

也就是说到目的地 AU 的共有 416 个航班。

接下来做一个如下的查询:

GET kibana_sample_data_flights/_search
{
  "size": 0,
  "query": {
    "term": {
      "DestCountry": {
        "value": "AU"
      }
    }
  },
  "aggs": {
    "top_orign_countrie": {
      "terms": {
        "field": "OriginCountry",
        "size": 5
      }
    }
  }
}

上面查询在目的地为 AU 的前5个 OriginCountry。显示的结果为:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 416,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "most_origin_country" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 229,
      "buckets" : [
        {
          "key" : "US",
          "doc_count" : 57
        },
        {
          "key" : "IT",
          "doc_count" : 54
        },
        {
          "key" : "AU",
          "doc_count" : 29
        },
        {
          "key" : "CN",
          "doc_count" : 24
        },
        {
          "key" : "GB",
          "doc_count" : 23
        }
      ]
    }
  }
}

上面显示排名第一的是来自 US 国家的航班最多,其次为 IT, AU, CN 及 GB。

我们在如下不使用整个数据集来进行上面的查询。我们采用 sampler 来进行查询:

GET kibana_sample_data_flights/_search
{
  "size": 0,
  "query": {
    "term": {
      "DestCountry": {
        "value": "AU"
      }
    }
  },
  "aggs": {
    "sample": {
      "sampler": {
        "shard_size": 300
      },
      "aggs": {
        "most_origin_country": {
          "significant_terms": {
            "field": "OriginCountry",
            "size": 3
          }
        }
      }
    }
  }
}

在上面,我们定义 shard_size 为 300,也就是使用得分最高的300个数据,而不是之前的416个数据,那么,我们得到的结果是:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 416,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "sample" : {
      "doc_count" : 300,
      "top_orign_countrie" : {
        "doc_count_error_upper_bound" : 0,
        "sum_other_doc_count" : 161,
        "buckets" : [
          {
            "key" : "US",
            "doc_count" : 43
          },
          {
            "key" : "IT",
            "doc_count" : 39
          },
          {
            "key" : "AU",
            "doc_count" : 22
          },
          {
            "key" : "CA",
            "doc_count" : 18
          },
          {
            "key" : "CN",
            "doc_count" : 17
          }
        ]
      }
    }
  }
}

从上面的结果我们可以产出来排名是:US, IT, AU, CA, CN。这个和我们之前的结果 US. IT. AU, CN, GB 是有一些出入,这个是因为我们使用了部分的数据而不全部的数据。

但是我们会发现在前几名的排列中 US, IT 及 AU 的顺序是一样的,虽然我们只使用其中的300个数据。因为目前我们的数据是非常小的,但是在实际的应用中,我们面对的是海量的数据,如果我们有很多的 shard,那么当我们使用 sampler aggregation 时会显得非常有用而高效。它能帮助我们得到我们想要的近似的结果,尽管它只工作于一部分的数据。

在实际的应用中,它可以和 significant terms 聚合一起使用,并搜索出我们需要的感兴趣的 terms,比如:

GET kibana_sample_data_flights/_search
{
  "size": 0,
  "query": {
    "term": {
      "DestCountry": {
        "value": "AU"
      }
    }
  },
  "aggs": {
    "sample": {
      "sampler": {
        "shard_size": 300
      },
      "aggs": {
        "most_origin_country": {
          "significant_terms": {
            "field": "OriginCountry",
            "size": 3
          }
        }
      }
    }
  }
}

在上面,我们采用前面的300个数据,并寻找 significatant terms:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 416,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "sample" : {
      "doc_count" : 300,
      "most_origin_country" : {
        "doc_count" : 300,
        "bg_count" : 13059,
        "buckets" : [
          {
            "key" : "AU",
            "doc_count" : 22,
            "score" : 0.06224272844272845,
            "bg_count" : 518
          },
          {
            "key" : "EC",
            "doc_count" : 14,
            "score" : 0.053121403508771946,
            "bg_count" : 285
          },
          {
            "key" : "ES",
            "doc_count" : 9,
            "score" : 0.01959113924050633,
            "bg_count" : 237
          }
        ]
      }
    }
  }
}

显然,AU 是比较突出的一个 OriginCountry。在这个搜索中,它去掉了噪声 US。有关 significant terms 的更多查询,请阅读我之前的文章 “Elasticsearch:significant terms aggregation”。

 

参考:

【1】https://medium.com/cognigo/improve-heavy-elasticsearch-aggregations-with-random-score-and-sampler-aggregation-9e1857271059