这周是个忙碌的一周,忙着4.19大促的各种事情,忙着帮业务上线,忙着开发,忙着对大索引的性能调优,忙着模板的优化,忙着支持扩容,今天忙着处理故障,但静下心来总结,总想写点什么,那就把这周调优的一些过程记录下来,分享给大家,如有需要欢迎来撩继续探讨。
如果有人叫你帮忙建一个上亿级的索引,应该从何入手?我的建议是简单做个规划,直接开建,因为只有第一个版本出来了,才能做base,基于这个之上才能一步步优化。
机器级别的优化
在建索引之前,集群的一些处理这里就不再多说了,集群节点的分布你得首选安顿好,JVM的内存也总得规划好,32G 目前是个不错的实践,没什么太大缘由就暂时先不要去捣鼓什么128G的heap了,CMS调教过后也没什么太大问题,不过在着手你的大索引之前,你得先确保你的机器先把Full GC这种问题先解决了,OS得有足够的cache 给Lucene;总而言之,建这种庞然大物得先确保机器层面,至少是ES 进程级别这个level是健康良好的运行着。至于这些怎么优化,ES官网专门就有一篇系统性配置的参数来教导,Full GC问题我之前的文章也提及过,这里就不赘述了。
建shard的一些建议
对于少些多读来说,经过我们的这段时间的经验,建议还是少分片多replica,这样读性能能达到最有,然后从这个基准开始如果写也很多的话,那么慢慢考虑多些分片,比如我们有一个几乎不写的大索引,只有3分片,然后就是不断复制replica,而另外一个大索引,则安排了很多分片。
另外一个会左右你设计多少个分片的因素就是“routing”,对于超大索引,十分十分十分建议要设计routing,刚刚说了, 对于写也高的索引,无形就要设计多分片,把每个shard的性能提上去,但是分片一多了,之后的问题就非常多了,比如我们在调试一些多维度的查询,聚合等,发现最后节点间的通信消耗就非常大,所以,最后我的个人总结,适当的多分片再配合routing,可以比较满意的服务读写都比较高的超大索引。
merge segments 千万不能忘
建好索引,一般的你就开始写Query开搞了,打住,在此之前,老司机提醒你,搞一个force merge真的非常重要,在我们的性能测试结果告诉你,merge 到2个segments 的分片的性能可以说有时候2倍于你刚建好的索引都毫不夸张。
那你可能说,我这索引写并发都很高啊,搞merge有个卵用;别忘了,虽然写还是会搞出一堆的小segments出来,但是之前的segments merge了就是merge了,起码能提高那90% 的segments的性能,剩下的一堆小文件,就让它吧。
我们的建议是,规划好一个定时任务,比如每天凌晨,每天中午12点,每天下午6点,总有些时间你的索引是不忙的,这个时候,尽早地赶紧地迅速地执行一个force merge,让你的索引起飞。你会发现这种琐碎的操作让你的索引 TXT变AVI
happy test + profile
好了,这次终于建好索引了,那就就你的索引随意执行一些happy test吧,比如搞几个符合查询,搞几个全表扫,搞几个聚合,如果每个操作你都可以70ms内返回,那么恭喜你,其实你的索引基本都算很快了,先偷着乐几天好好歇歇先把。
很不幸,你的查询总能找到一些查询是需要几百甚至上秒级的延迟,或者发现你的CPU消耗非常严重。那赶紧加个Profile:true 吧,其实profile这种级别的诊断几乎能治疗你90%以上的懒散综合征了。
我这里讲两个我们的案例:
- 对于数据库的一些数字类型或者布尔类型或者枚举类型,我们往往也倾向于在ES也建立一个short或integer或long类型,现在告诉你这是大错特错了,ES对于检索速度用term 才是最快的,也就是说,如果你并不需要数字类型的range操作,或者真的需要对这个字段做聚合的话,请果断把它mapping成keyword,你塞个数字进去?别怕,ES会自动转换,我们的经验总结是,对于10亿级别的索引,对一个数字做term 耗时是毫秒到10级毫秒级别不等,而对一个keyword 做term查询,往往不需要1ms。而这种差别对于只有枚举类型的数字的时候,比如只有一个true/false,状态位(只有1,2,3,4)等,性能往往要好10倍以上。
这里具体的原因在之前的文章已经分析过了
而有时候却又不得不做一些类似max ,min这种操作,我的经验是如果实在避免不了,诱惑当前,还是直接在原字段下再做一个keyword的field,一举两得。
- 在用profile时我们发现对keyword做terms的aggregation 要比对数字做aggregation要慢不少,我们怀疑的一个方向是String是要在做term aggregation时是要取 global ordinary 的,这个开销应该是比较客观的,我前两天在一次测试时发现这种查询平均需要400ms以上,但是在profile的统计又展示不出来究竟哪里耗时了,关于这个问题我提了一个issue去ES官方,希望得到官方的回复,有兴趣的可以继续跟这个topic
https://github.com/elastic/elasticsearch/issues/29275
我的建议就是,要agg时尽量用数字字段
好了,就这样用profile好好调教你的字段吧,基本就是那个字段慢就调哪个字段,nested慢就尝试放出来,放不出来尝试调整内部结构,多用filter减少打分,多用可以大范围压缩结果集的filter。
从profile的统计我们也看出,用了routing的性能也是非常有好处的。
并发和CPU运算之间取个权衡
好了,是否对调教完后你的毫秒级延迟的查询表示很满意?那我给出的最后一个问题就是,你的查询需要的并发量是多少,它顶的住CPU么?
经常有人问我,这个索引可以顶多少QPS?我可以说这是ES最回答不了的问题,不同分片,不同机器性能,甚至不同内存,一个简单的查询可以去到10K QPS,而一个慢查询可能连100QPS都顶不住;所以查询的分布一定要合理规划,慢查询和快查询相互隔离往往是个好的想法, 但是别忘了shard之间会内部通信,所以最后效果也不大。
那我要说的就是,希望你最后对你要上的索引和查询对CPU的影响要有个大概的评估,评估一下在多大的并发下是否撑得起这个CPU消耗,实在不行,那么尝试去掉某些aggregation,把一些结果尽量放到前端域去解析吧,ES要做的就是尽量快的检索出来就好了。比如我得到一个需求就是Group By A 然后取任意一条 A下面的B,我折腾出一个查询出来,最后发现就是吃CPU,而这个方案妥协的最后结果就是,放弃做Group by了,直接约束A直接查询条件返回B字段在前端做抽取吧。
总结
好了,经历这番挣扎, 我相信你的这个超大索引应该不至于会很难看吧,我最后折腾了一周的索引最后性能如何? 我也不知道,拭目以待吧。
大家还有好的建议或者问题,欢迎来撩。
网友评论