热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

转聚合查询变慢详解Elasticsearch的GlobalOrdinals与HighCardinality

转自:https:blog.csdn.netzwgdftarticledetails83215977【README】Elasticsearch中的概念很多࿰

转自: https://blog.csdn.net/zwgdft/article/details/83215977

 

【README】

 Elasticsearch中的概念很多,本文将从笔者在实践过程中遇到的问题出发,逐步详细介绍 Global OrdinalsHigh Cardinality ,这也是笔者的认知过程。文中的Elasticsearch 版本为5.5。


背景

  故事是这样的,因为业务需要,我们在项目中设计了一种针对Elasticsearch数据的异步去重方法(注:关于Elasticsearch数据去重,笔者会在另一篇博文中更加详细介绍),基本思路是:


  • 为每一条数据计算hash值,作为document的一个字段(keyword类型)插入到Elasticsearch中,数据格式简化如下:

{"timestamp": 1540099182,"msgType": 1210,......"hash": "31a2c683dccb83ef8b8d1ee43290df62"
}

  •  

  • 每隔一段时间,运行一次检测脚本,检查Elasticsearch中的数据是否有重复,相关查询语句如下(这里,terms聚合用于发现给定时间范围内是否有超过2条hash值一样的数据,top_hits聚合用于找出重复数据组中的具体数据信息,然后删除掉重复的数据即可):

{"size": 0,"query": {"bool": {"filter": [{ "range":{ "timestamp":{ "gte": 1540087200,"lt": 1540087500}}}]}},"aggs": {"duplications": {"terms": {"field": "hash","min_doc_count": 2,"size": 500},"aggs": {"top_duplications": {"top_hits": {"size": 3}}}}}
}

  •  

  这样一个方案,因为只是在数据集中增加了一个hash字段,并且去重是异步的,不会影响到原有的设计,所以在通过相关的功能性测试后就上线了。然而,运行一段时间后,出现了严重问题:


  • 随着新数据的写入,上述的查询语句变得越来越慢,从秒级逐步变成要20多秒,并且在数据量超过10亿条后,每次查询都会使内存超过80%;
  • index的存储空间比原先增加了近一倍

  对于类似上述的查询语句,Elasticsearch会先根据Filter条件找出匹配的document,然后再进行聚合运算。在我们的业务中,每次查询2小时内的数据,并且数据的写入是匀速的,这意味着每次匹配出来的document个数基本是固定的,那么为何会出现这个查询越来越慢的问题?而且,我们发现,即使Filter匹配的document个数为0,也同样需要很久才能返回结果

  另一方面,经过对比验证,可以确定是新增加的hash字段导致了数据存储空间比原先增加了近一倍。
  带着这些问题,笔者进行了详细的调研,最终锁定Global OrdinalsHigh Cardinality两个核心概念。其中,github上面的一个issue Terms aggregation speed is proportional to field cardinality 给了很大的启发。


Global Ordinals

什么是Ordinals? 

  假设有10亿条数据,每条数据有一个字段status(keyword类型),其值有三种可能性:status_pending、status_published、status_deleted,那么每条数据至少需要14-16 Bytes,也就是说需要将近15GB内存才能装下所有数据。

Doc | Term
-------------------------------
0 | status_pending
1 | status_deleted
2 | status_published
3 | status_pending

  •  

  为了减少内存使用,考虑将字符串排序后进行编号,形成一张映射表,然后在每条数据中使用相应字符串的序号来表示。通过这样的设计,可以将所需内存从15 GB减少为1 GB左右。
  这里的映射表,或者说映射表中的序号,就是Ordinals。

Ordinal | Term
-------------------------------
0 | status_deleted
1 | status_pending
2 | status_publishedDoc | Ordinal
-------------------------------
0 | 0 # deleted
1 | 2 # published
2 | 1 # pending
3 | 0 # deleted

  •  

什么是Global Ordinals? 

  当我们对status字段做Terms聚合查询时,请求会透过Coordinate Node分散到Shard所在的Node中执行,而针对每个Shard的查询又会分散到多个Segment中去执行。
  上述的Ordinals是per-segment ordinals,是针对每个Segment里面的数据而言,意味着同一个字符串在不同的per-segment ordinals中可能对应的序号是不同的。比如,在Segment 1中只有status_deleted(0)和status_published(1)两个值,而Segment 2中有3个值:status_deleted(0),status_pending(1),status_published(2)。
  这样就面临一个抉择:方案一,在完成per-segment的查询后,将相应的序号转换成字符串,返回到Shard层面进行合并;方案二,构建一个Shard层面的Global Ordinals,实现与per-segment ordinals的映射,就可以在Shard层面完成聚合后再转换成字符串。
  经过权衡,Elasticsearch(Lucene)选择了方案二作为默认方法:构建Global Ordinals。

为何会影响聚合查询?

  构建Global Ordinals的目的是为了减少内存使用、加快聚合统计,在大多数情况下其表现出来的性能都非常好。之所以会影响到查询性能,与其构建时机有关:


  • 由于Global Ordinals是Shard级别的,因此当一个Shard的Segment发生变动时就需要重新构建Global Ordinals,比如有新数据写入导致产生新的Segment、Segment Merge等情况。当然,如果Segment没有变动,那么构建一次后就可以一直利用缓存了(适用于历史数据)。
  • 默认情况下,Global Ordinals是在收到聚合查询请求并且该查询会命中相关字段时构建,而构建动作是在查询最开始做的,即在Filter之前

  这样的构建方式,在遇到某个字段的值种类很多(即下文所述的High Cardinary问题)时会变的非常慢,会严重影响聚合查询速度,即使Filter出来的document很少也需要花费很久,也就是上文笔者遇到的问题,即在High Cardinary情况下,构建Global Ordinals非常慢。因为我们新加的hash字段对于每条数据都不一样,所以当写入越来越多的数据后,聚合查询越来越慢(大概超过5000W条之后)。

有哪些优化方法?

  虽然在Lucene 7.1中,针对global ordinals的构建有些优化(LUCENE-7905),但是仍然不能避免这样的问题。目前有这样几种优化方法(或者说是缓解之法,目前尚未发现完美的方法):


  • 增加Shard个数。因为Global Ordinals是Shard层面的,增加Shard个数也许可以缓解问题,前提是:第一,要能确定有问题的字段的值种类可以通过该方式减少在单个Shard中的量;第二,确保Shard的个数增加不会影响到整体的性能。
  • 延长refresh interval,即减少构建Global Ordinals的次数来缓解其影响,前提是要能接受数据的非实时性。
  • 修改execution_hint的值。在Terms聚合中,可以设置执行方式是map还是global_ordinals,前者的意思是直接使用该字段的字符串值来做聚合,即无需构建Global Ordinals。这样的方式,适用于可以确定匹配文档数据量的场景,并且不会引起内存的暴增,比如在笔者的业务场景中,每次只查询2小时内的数据量。这也是当前我们的优化方法。

GET /_search
{"aggs" : {"tags" : {"terms" : {"field" : "status","execution_hint": "map" }}}
}

  •  

High Cardinality 

  相信看完上文,读者已经知道什么是High Cardinality了。所谓High Cardinality,指的是Large Number of Unique Values,即某个字段的值有很多很多种,比如笔者业务中的那个hash字段。在Elasticsearch,High Cardinality会带来各种问题,百害而无一利,所以应该尽量避免,避免不了也要做到心中有数,在出问题时可以及时调整。


  • High Cardinality会导致构建Global Ordinals过程变慢,从而导致聚合查询变慢、内存使用过高。
  • High Cardinality会导致压缩比率降低,从而导致存储空间增加,特别是像hash值这样完全随机的字符串。
  • 对High Cardinality字段执行Cardinality聚合查询时,会受到精度控制从而导致结果不精确。

 

  本文结合笔者在实践过程中遇到的由High Cardinality引起Global Ordinals构建过慢,从而导致聚合查询变慢的问题,阐述了Global Ordinals和High Cardinality两个核心概念,希望对遇到类似问题的人有所帮助。目前,针对我们的业务场景,相关的调整有:第一,使用"execution_hint": "map"来避免构建Global Ordinals;第二,尝试在数据上传端增加对压缩友好的唯一键来作为去重对象,比如uuid4;第三,减小index的切割时间,比如从weekly index变成daily index,从而降低index中单个shard的数据量。

 

 


推荐阅读
  • 颜色迁移(reinhard VS welsh)
    不要谈什么天分,运气,你需要的是一个截稿日,以及一个不交稿就能打爆你狗头的人,然后你就会被自己的才华吓到。------ ... [详细]
  • 本文介绍了机器学习手册中关于日期和时区操作的重要性以及其在实际应用中的作用。文章以一个故事为背景,描述了学童们面对老先生的教导时的反应,以及上官如在这个过程中的表现。同时,文章也提到了顾慎为对上官如的恨意以及他们之间的矛盾源于早年的结局。最后,文章强调了日期和时区操作在机器学习中的重要性,并指出了其在实际应用中的作用和意义。 ... [详细]
  • 本文讨论了如何使用IF函数从基于有限输入列表的有限输出列表中获取输出,并提出了是否有更快/更有效的执行代码的方法。作者希望了解是否有办法缩短代码,并从自我开发的角度来看是否有更好的方法。提供的代码可以按原样工作,但作者想知道是否有更好的方法来执行这样的任务。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • GreenDAO快速入门
    前言之前在自己做项目的时候,用到了GreenDAO数据库,其实对于数据库辅助工具库从OrmLite,到litePal再到GreenDAO,总是在不停的切换,但是没有真正去了解他们的 ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
  • Ihaveaworkfolderdirectory.我有一个工作文件夹目录。holderDir.glob(*)>holder[ProjectOne, ... [详细]
  • 从相邻元素对还原数组的解题思路和代码
    本文介绍了从相邻元素对还原数组的解题思路和代码。思路是使用HashMap存放邻接关系,并找出起始点,然后依次取。代码使用了HashMap来存放起始点所在的adjacentPairs中的位置,并对重复的起始点进行处理。 ... [详细]
  • 本文讨论了在使用Git进行版本控制时,如何提供类似CVS中自动增加版本号的功能。作者介绍了Git中的其他版本表示方式,如git describe命令,并提供了使用这些表示方式来确定文件更新情况的示例。此外,文章还介绍了启用$Id:$功能的方法,并讨论了一些开发者在使用Git时的需求和使用场景。 ... [详细]
  • 使用freemaker生成Java代码的步骤及示例代码
    本文介绍了使用freemaker这个jar包生成Java代码的步骤,通过提前编辑好的模板,可以避免写重复代码。首先需要在springboot的pom.xml文件中加入freemaker的依赖包。然后编写模板,定义要生成的Java类的属性和方法。最后编写生成代码的类,通过加载模板文件和数据模型,生成Java代码文件。本文提供了示例代码,并展示了文件目录结构。 ... [详细]
  • 花瓣|目标值_Compose 动画边学边做夏日彩虹
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Compose动画边学边做-夏日彩虹相关的知识,希望对你有一定的参考价值。引言Comp ... [详细]
author-avatar
小丘
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有