【问题排查篇】一次业务问题对 ES 的 cardinality 原理探究
作者:京东科技 王长春
业务问题
小编工作中负责业务的一个服务端系统,使用了 Elasticsearch 服务做数据存储,业务运营人员反馈,用户在使用该产品时发现,用户后台统计的订单笔数和导出的订单笔数不一致!
交易订单笔数不对,出现差错订单了?这一听极为震撼!出现这样的问题,在金融科技公司里面是绝对不允许发生的,得马上定位问题并解决!
小编马上联系业务和相关人员,通过梳理上游系统的调用关系,发现业务系统使用到的是我这边的 ES 的存储服务,然后对线上情况进行复现,基本了解问题的现象:
- 用户操作后台里的订单总笔数:商户页面的"订单总笔数","订单总笔数"使用的是小编 ES 存储服务中 ES 的统计聚合功能,其中订单总笔数是使用了 cardinality 操作,并且使用的是 orderId(订单编号)进行统计去重。
- 导出功能里的订单总笔数:导出功能使用的是 ES 存储服务中的 ES 条件查询功能,导出功能是进行分页查询的。
问题定位
这两个查询数量不一致,首先看查询条件是否一致呢?
经过一番排查,业务系统在调用查询订单总数和导出订单总数的这两个查询条件是一致的,也就是请求到我这边 ES 服务时,统计聚合的查询和分页导出的查询条件是一致的,但是为什么会在 ES 里面查询的结果是不一致的呢?难道 ES 里面的数据不全?统计聚合或分页导出的其中有一个不准了?
为了具体排查哪个操作可能存在问题,于是通过相同条件下查询数据库的总数和 ES 里面的数据进行对比。发现相同条件下,数据库里面的数据和 ES 条件查询的总数是一致的, 同时业务的 orerId 字段是没有重复,所以可以确定的是:通过 orderId 进行统计聚合去重的操作是有问题的。
数据库查询:数据库是做分库分表,此处数据库查询使用的是公司内的数据部银河大表——公司数据部会 T+1日从业务从库数据库中抽取 T 日的增量数据放在建立的"大表"中, 方便各业务进行数据使用。
运营后台查询:运营后台查询是直接查询 ES 存储服务。
数据部大表数量 = MySQL 数据库分库分表表里数量 = 运营控制台查询数量 = ES 存储文档数量
问题定位:
ES 存储服务对外给业务提供的: 通过 orderId 进行统计聚合去重(cardinality)的功能应该是有问题的。
ES 的 cardinality 原理探究
上面说过,小编负责的 ES 存储服务对外给业务提供了通过指定业务字段进行统计聚合去重的功能,统计聚合去重使用的是 ES 的 cardinality 功能。通过业务的查询的条件,使用 ES 的聚合功能 cardinality 操作,映射到 ES 层的操作命令如下代码所示,
执行业务的查询条件操作,从 ES 的管理端后台里面查询竟然复现了和线上生产一样的结果,聚合统计的是 21514,条件查询的是 21427!!!
可以确定的就是这个 cardinality 操作,导致了两个查询的数据不一致,如下图所示:
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 操作会出现这样的结果呢?
小编开始陷入了想当然的陷阱—— 以为这就是一个简简单单的统计去重的功能,ES 做的多好,帮你去重并统计数量了。然后事实并不是,通过 Elasticsearch 对 cardinality 官方文档解释,终于找到了原因。
可以参考Elasticsearch 2.x 版本官方文档对 cardinality的解释:cardinality
其中对 cardinality 算法核心解释是:
可以总结如下:
- cardinality 并不是像关系型数据库 MySQL 一样精确去重的,cardinality做的是一个近似值,是 ES 帮你"估算"出的,这个估算使用的HyperLogLog++(HLL)算法,在速度上非常快,遍历一次即可统计去重,具体可看文档中推荐的论文。
- ES 做cardinality估算,是可以设置估算精确度,即设置参数 precision_threshold 参数,但是这个参数在 0-40000, 这个值越大意味着精度越高,同时意味着损失更多的内存,是以内存空间换精度。
- 在小数据量下,ES 的这个"估算"精度是非常高的,几乎可以说是等于实际数量。
ES 中 cardinality 参数验证
下面对 ES 的 cardinality 的precision_threshold参数进行验证:
1、大数据量下,设置最高精度及其以上,仍然会存在误差:
2、小数据量下,设置最高精度,可以和实际数量保持一致:
那么线上的为什么聚合统计的是 21514,条件查询的是 21427?
线上代码运行和ES集群设置都没有主动设置过 precision_threshold 参数,那么可以知道,这个应该是 ES 集群设置的默认值。线上 ES 集群版本为 5.4x 因此找到 5.4 版本的官方文档,发现 5.4 版本中设置的是默认值 precision_threshold=3000, 在此条件下查询的统计聚合出来的值是 21514。
另外 ES 官方对 cardinality 操作中的precision_threshold参数也做了研究,研究了官方文档中precision_threshold设置和cardinality查询失败率、查询数据量级的关系,可作为我们在业务开发中进行参考,如下图所示:
Elasticsearch 5.4版本官方文档对cardinality中precision_threshold参数的研究文档:precision_threshold
总结与方案
通过对 cardinality 的原理探究, 需要明白的是 : 我们使用 cardinality 是需要区分使用场景的。
- 对于精确统计的业务场景,是不建议使用的。例如:订单数的统计(统计结果会引起歧义)的场景下,不建议使用。
- 对于非精确统计的业务场景,那么可以说是很有用了,尤其是在大数据量的场景下,在保持一定的准确性下,同时能提供高性能。例如:监控指标数据,大盘比例计算等场景,在非精确统计下,是有很大用处。
基于小编的这个业务场景,对商户订单进行统计,是属于精确统计场景,那 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 原理探究的更多相关文章
- 走向DBA[MSSQL篇] 针对大表 设计高效的存储过程【原理篇】 附最差性能sql语句进化过程客串
原文:走向DBA[MSSQL篇] 针对大表 设计高效的存储过程[原理篇] 附最差性能sql语句进化过程客串 测试的结果在此处 本篇详解一下原理 设计背景 由于历史原因,线上库环境数据量及其庞大,很多千 ...
- JS基础篇--sort()方法的用法,参数以及排序原理
JS基础篇--sort()方法的用法,参数以及排序原理 sort() 方法用于对数组的元素进行排序,并返回数组.默认排序顺序是根据字符串Unicode码点.语法:arrayObject.sort( ...
- 日志查看技巧之筛选&去重[排查篇]
引语:相信大家都会偶尔遇到要排查问题发生的原因的情况,那这种时候,我们最有力后盾就是日志文件了,所以谨记日志记录真的很重要.但是日志文件往往是很大的文件,而且里面有太多的东西可能不是我们需要的,如无数 ...
- 【整合篇】Activiti业务与流程的整合
对于不管是Activtit还是jbpm来说,业务与流程的整合均类似.启动流程是绑定业务.流程与业务的整合放到动态代理中 [java] view plain copy print" style ...
- CRM【第三篇】: crm业务
1. 项目背景 crm系统是某某教育公司正在使用的项目,系统主要为 销售部.运营部.教质部门提供管理平台,随着公司规模的扩展,对公司员工的业务信息量化以及信息化建设越来越重要. crm系统为不同角色的 ...
- 运维排查篇 | Linux 连接跟踪表满了怎么处理
nf_conntrack (在老版本的 Linux 内核中叫 ip_conntrack )是一个内核模块,用于跟踪一个网络连接的状态 一旦内核 netfilter 模块 conntrack 相关参数配 ...
- 报警系统:php输出头信息以方便脚本抓取信息[排查篇]
做监控系统时,需要对某个页面进行监控,可以通过很多方式进行报警,如:正常则输出一个规定的变量,错误时则不输出.但是还有一个更为方便的做法,就是当前错误时,直接使用header抛出信息,如: heade ...
- Tars | 第8篇 TarsJava Subset最终代码的执行流程与原理分析
目录 前言 1. SubsetConf配置项的结构 1.1 SubsetConf 1.2 RatioConfig 1.3 KeyConfig 1.4 KeyRoute 1.5 SubsetConf的结 ...
- 整理分布式锁:业务场景&分布式锁家族&实现原理
1.引入业务场景 业务场景一出现: 因为小T刚接手项目,正在吭哧吭哧对熟悉着代码.部署架构.在看代码过程中发现,下单这块代码可能会出现问题,这可是分布式部署的,如果多个用户同时购买同一个商品,就可能导 ...
- Python面向对象篇之元类,附Django Model核心原理
关于元类,我写过一篇,如果你只是了解元类,看下面这一篇就足够了. Python面向对象之类的方法和属性 本篇是深度解剖,如果你觉得元类用不到,呵呵,那是因为你不了解Django. 在Python中有一 ...
随机推荐
- HTTP 协议相关
一. HTTP常见请求头 1. Host (主机和端口号) 2. Connection (连接类型) 3.Upgrade-Insecure-Requests (升级为HTTPS请求) 4. User- ...
- sql server 索引检测
-- 声明表变量 DECLARE @userTable TABLE (table_name NVARCHAR(20)); -- 将源表中的数据插入到表变量中 INSERT INTO @userTabl ...
- “你帮我助”软件开发(Final)
本项目是上海交通大学 CS-3331 软件工程课程大作业. 作业描述 "你帮我助"软件开发(Final) 新的功能需求: 物品有公共的信息(物品名称,物品说明,物品所在地址,联系人 ...
- Oracle-账户被锁:The account is locked
Oracle-账户被锁:The account is locked cmd-->sqlplus--> 管理员登录:system@ORCL 密码:1 然后输入:alter user kjb( ...
- Spring MVC的请求处理逻辑
当大家了解了如何编写一个简单的Spring MVC程序后,大家心中应该会有一些好奇:这背后到底发生了什么? Spring MVC是怎么把这些功能串联起来的?我们只是写了一个控制器而已,HTTP请求是怎 ...
- graphviz 简单入门
上一位负责画图的选手 CS Academy 由于不明原因暂时不能继续工作,所以本俱乐部招收了新选手 graphviz. 在 Linux 上,你可以这么下载: sudo apt install grap ...
- 设计师必备:免费素材管理工具Billfish v3.0更新了!
Billfish是专门为设计师打造的图片收藏管理工具,可以轻松管理您的各种素材文件.Billfish是一个免费的软件,支持对大量的图片素材进行管理,提供多种快速的检索筛选功能,如颜色,格式,方向, ...
- TCC 分布式事务解决方案
更多内容,前往 IT-BLOG 一.什么是 TCC事务 TCC 是Try.Confirm.Cancel三个词语的缩写,TCC要求每个分支事务实现三个操作:预处理Try.确认Confirm.撤销Canc ...
- 安装原版Windows自动安装已经备份的驱动
安装完Windows10后联网会自动更新驱动,除了自动更新.如果不让新安装的Windows系统自动安装驱动以外,还有自己手动安装驱动的方式.如何在安装系统过程中就让系统自己安装好驱动? 重装系统首先要 ...
- Linux报错:audit: backlog limit exceeded(审计:超出积压限制)
Linux报错:audit: backlog limit exceeded(审计:超出积压限制) 系统版本:CentOS Linux release 7.6.1810 (Core) 问题现象:一次巡检 ...