[问题排查篇]一次业务问题对 ES 的 cardinality 原理探究

科技资讯 投稿 6700 0 评论

[问题排查篇]一次业务问题对 ES 的 cardinality 原理探究

作者:京东科技 王长春

业务问题

用户后台统计的订单笔数和导出的订单笔数不一致!

    用户操作后台里的订单总笔数:商户页面的"订单总笔数","订单总笔数"使用的是小编 ES 存储服务中 ES 的统计聚合功能,其中订单总笔数是使用了 cardinality 操作,并且使用的是 orderId(订单编号进行统计去重。
  1. 导出功能里的订单总笔数:导出功能使用的是 ES 存储服务中的 ES 条件查询功能,导出功能是进行分页查询的。

问题定位

这两个查询数量不一致,首先看查询条件是否一致呢?

为了具体排查哪个操作可能存在问题,于是通过相同条件下查询数据库的总数和 ES 里面的数据进行对比。发现相同条件下,数据库里面的数据和 ES 条件查询的总数是一致的, 同时业务的 orerId 字段是没有重复,所以可以确定的是:通过 orderId 进行统计聚合去重的操作是有问题的。

运营后台查询:运营后台查询是直接查询 ES 存储服务。

问题定位:
ES 存储服务对外给业务提供的: 通过 orderId 进行统计聚合去重(cardinality)的功能应该是有问题的。

ES 的 cardinality 原理探究

执行业务的查询条件操作,从 ES 的管理端后台里面查询竟然复现了和线上生产一样的结果,聚合统计的是 21514,条件查询的是 21427!

GET datastore_big_es_1_index/datastore_big_es_1_type/_search
{
  "size": 3,
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "v021.raw": "selfhelp"
          }
        },
        {
          "match": {
            "v012.raw": "1001"
          }
        },
        {
          "match": {
            "typeId": "00029"
          }
        },
        {
          "range": {
            "createdDate": {
              "gte": "2021-02-01",
              "lt": "2021-03-01"
            }
          }
        },
        {
          "bool": {
            "should": [
              {
                "match": {
                  "v031.raw": "113692300"
                }
              }
            ]
          }
        }
      ]
    }
  },
  "aggs": {
    "distinct_orderId": {
      "cardinality": {
        "field": "v033.raw"
      }
    }
  }
}


为什么 cardinality 操作会出现这样的结果呢?

可以参考Elasticsearch 2.x 版本官方文档对 cardinality的解释:cardinality

可以总结如下:

    cardinality 并不是像关系型数据库 MySQL 一样精确去重的,cardinality做的是一个近似值,是 ES 帮你"估算"出的,这个估算使用的HyperLogLog++(HLL算法,在速度上非常快,遍历一次即可统计去重,具体可看文档中推荐的论文。
  1. ES 做cardinality估算,是可以设置估算精确度,即设置参数  precision_threshold 参数,但是这个参数在 0-40000, 这个值越大意味着精度越高,同时意味着损失更多的内存,是以内存空间换精度。
  2. 在小数据量下,ES 的这个"估算"精度是非常高的,几乎可以说是等于实际数量。

ES 中 cardinality 参数验证

precision_threshold参数进行验证:

1、大数据量下,设置最高精度及其以上,仍然会存在误差:

2、小数据量下,设置最高精度,可以和实际数量保持一致:

线上代码运行和ES集群设置都没有主动设置过 precision_threshold 参数,那么可以知道,这个应该是 ES 集群设置的默认值。线上 ES 集群版本为 5.4x  因此找到 5.4 版本的官方文档,发现 5.4 版本中设置的是默认值 precision_threshold=3000,在此条件下查询的统计聚合出来的值是 21514。

precision_threshold参数也做了研究,研究了官方文档中precision_threshold设置cardinality查询失败率查询数据量级的关系,可作为我们在业务开发中进行参考,如下图所示:

precision_threshold参数的研究文档:precision_threshold

总结与方案

    对于精确统计的业务场景,是不建议使用的。例如:订单数的统计(统计结果会引起歧义的场景下,不建议使用。
  1. 对于非精确统计的业务场景,那么可以说是很有用了,尤其是在大数据量的场景下,在保持一定的准确性下,同时能提供高性能。例如:监控指标数据,大盘比例计算等场景,在非精确统计下,是有很大用处。

基于小编的这个业务场景,对商户订单进行统计,是属于精确统计场景,那 cardinality 操作就不适合了。又因为业务的 orderId 是不会重复的,理论上在我们 ES 集群中每个记录的 orderId 都是唯一的,因此可以不用进行去重,而可以直接使用 ES 的 count 操作,将订单数统计汇总出,对应 Elasticsearch 开发包中 COUNT API 如下:

org.springframework.data.elasticsearch.core.ElasticsearchTemplate
#count(org.springframework.data.elasticsearch.core.query.SearchQuery, java.lang.Class<T>


public <T> long count(SearchQuery searchQuery, Class<T> clazz {
    QueryBuilder elasticsearchQuery = searchQuery.getQuery(;
    QueryBuilder elasticsearchFilter = searchQuery.getFilter(;
    return elasticsearchFilter == null ? this.doCount(this.prepareCount(searchQuery, clazz, elasticsearchQuery : this.doCount(this.prepareSearch(searchQuery, clazz, elasticsearchQuery, elasticsearchFilter;
}


最后欢迎大家点赞、收藏、评论,转发!❤️❤️❤️

编程笔记 » [问题排查篇]一次业务问题对 ES 的 cardinality 原理探究

赞同 (35) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽