我经常被问到这样的问题:ES最大能撑多少QPS ?
确实这比证明我爸是我爸的问题更难解释清楚,不过我通常会给出两个选择;1)你是否希望尽量高QPS而不需要管最大latency甚至是总体latency升的很高? 2)你是否希望最大的latency 尽量的低,甚至不允许发生大于XXX的latency?因此这个回答需要你对你需要实现的业务熟悉。
大促在即,最近几个索引被盯上了,因为出现了类似999% 百分位延迟很大的问题,大家都知道,在电商大并发的系统里,任何的延迟抖动最后可能都会导致非常恐怖的崩塌效应,因此最近两周都在仔细地琢磨这个问题,想尽力去解决。
话不多说,出正题,当前我们的索引遇到了下面的问题:
第一个问题,由于terms 只会过滤出非常少量的结果,因此我猜测aggs 是非常稳定的,首先排除,最大嫌疑肯定就是这个Time range 了,因此解决办法也非常迅速,因为terms 结果集很少,那么就直接把Time 的range 放一个script在内存算好了, 结果当然是,延迟抖动没有了!
之前的文章也曾经介绍过,对于不做数学运算,不做聚合的数字类型应该用keyword 来建索引,但是像类似时间这种类型,其实本质上还是用了long来存,所以对它做range时,之前说的那些 Elasticsearch 5x 源码分析(12)对类似枚举数据的搜索异常慢的一种猜测 问题都还是会有,本质都是用了Lucene 新的 Block K-d Tree 的数据结构引起的,因此如果你做的range结果集非常大的话就还是有问题,避免的方式无非就是减少这些bucket的数量,比如采用秒级时间戳落盘,而不是毫秒,这样查询速度提升还是很明显的。
那你应该会问了,新版本ES不是修了这个issue了么,是的,在新的Lucene 里这个查询被一个IndexOrDocValuesQuery ("Points + Doc values") 包了一层,如果Lucene的新solution 还不清楚的再次翻一下上面的这个连接,下面我们从性能的角度再看这个问题。
这张图是从ES的一篇博客中截取,绿色的线指的是如果你的terms查询总是召回01% 的结果集,那么这个查询的延迟 一般是很稳定的。紫色的线就代表这个range 查询随着range 的结果集增大而延迟增大,这个很容易理解,那么再看这个蓝色的线,也就是Lucene的这个solution,它的意思就是在range 的结果集在01%以内时,则还是走了这个field的索引,并且把取得的id集合和terms的id集合做conjunction处理,但是如果range 的结果集大于01%时,怎放弃走range field的索引,而是直接在terms的结果集基础上逐个对doc Value 进行判断。
上面这个图当然是个极度理想化的图,因为这个IndexOrDocValuesQuery永远都是得到一个最低延迟的查询,因此实际情况很可能是下面这个图
首先我们不可能每次的结果集占比都是非常稳定,IndexOrDocValuesQuery是个只智能分配的过程,比如如果这个阈值取1%,而range在这个1%结果集的延迟还是低于对terms的1%结果集做doc Value查询时,那么从蓝线得出,我们这次的tradeoff 是亏了的。但我们仍然觉得这个交易还是划得来的,因为我们只损失了毫秒级而已。
这个问题我在ES55 发现已经修复了,那么为什么还是会带来这么厉害的延迟抖动呢,问题就在于,range 的索引不是常驻内存的。也就是说上面的紫线的结果,在高并发或者甚至地并发随着时间的推移,它总会被GC掉!因此重新取这个cache是无法避免的。
所以如果你想尽量减少这个开销,那么你只能把Query cache调大来缓解,而最致命的是下面这个Lucene的issue
如果你把range查询放在filter里,那么Lucene总是希望尝试去cache这个查询,因此,如果cache丢失了,它下次又会尝试去查询并且cache住。这个问题至少在Lucene 72 还是没有很好解决。
问题一解决办法:
对于你能判断的做完terms 后的结果集是恒定并且很少的话,尽量避免掉对大结果集的字段做range查询,放在内存做是个非常不错的选择。
这个问题我们从头到尾再捋一捋
下面是其中的一个查询例子:
乍一看大家都傻眼了,可以说这就是一个简单的get id的 *** 作,老实说Redis可能轻松就上几W QPS了,这个ES最长延迟竟然去到500ms+ 甚至有1s的,都不太好意思去交代。
但是只要仔细去分析,还是挺容易发现问题的,我们用Lucene的语言去解读这个查询:
接下来逐个去分析,先看FST表,Lucene的FST表大致是一下类似下面这样的结构图
这里简单介绍两句,Lucene对有terms 倒排表,是分开前缀表和后缀表两部分组成,为了不会撑爆HEAP,Lucene会智能推算出一个前缀表来常驻HEAP,比如我们做一个 term :abcd, 那么其实会先查找FST表,找到ab ,或者abc , 然后再从后缀表找到abcd,进而在doc 里查到倒排索引表,如果从数据结构来看,大致入下图
根据热数据特性,计算是tim文件不是常驻 HEAP,那么其实原则上它会失效的可能也是很低的,就是说根据一个前缀,一下找到一堆的id的可能性是非常大的,因此这部分不太可能造成大延迟。
那么在往下看,doc values 保存在dvd 中,并且 id 都是顺序保存的,保存就是一堆的key/value 结构,只不过 key 都是通过Lucene压缩的数据结构,如果最后压缩的文件很少,那么对于一堆的id集合来说,page cache missing 应该会存在,但是频繁missing应该不至于。所以这个地方也是个关注点,cache missing应该还是会有延迟增长的,特别是在大segment merge完后特别明显。
最后就是fetch 的过程了,这个过程就没什么好说了,通过id get doc,最最最坏的情况应该就是在大segments 做完merge,所有文件都没cache, 然后突然来一批稀疏id的时候去load 文件,这种造成最大的延迟的可能性是最大的。
那再回顾这个索引, 单机占用空间50G+, 而我们只有64G内存,30G分配到HEAP,也就是 OS cache 其实30G不到,那么要缓存50G+ 的文件,应该cache 频繁切换的可能还是有的,这里还没算上 1K TPS 的indexing,还有后台的merge 线程所造成的大文件切换。
问题二解决办法:
那么分析到这里的话,999% 百分位延迟的问题似乎就讲得通了,因此我们的措施就是增大内存到96G,减少索引容量,清理掉没用的数据,并且要把refresh 的时间把握好,尽量让Lucene来生成一些不大不小的segments,一方面避免后台频繁merge,一方面也使得切换大文件时OS 能尽快地cache segments文件。
由于我们show hand买中问题二的root cause,那么问题三自然就很容易推断了:
首先,force merge 会强行merge一些比较大的segments,例如 2G + 2G -> 4G ,这些在切指针时就会造成这4G的文件全部missing,OS加载需要一点时间。
其次,FST 表需要重新算,doc values需要重新算,元数据表需要重新算,这些都是额外常驻在HEAP的,因此大索引的话做force merge 其实 HEAP的老生代很容易沾满并且频繁触发GC, 甚至最坏的时候会有full GC,在第三个问题排查的时候我们的节点发生了最高超过6s的GC。
所以最后的总结就是在生产的环境谨慎对待force merge。
Lucene底层原理和优化经验分享(1)-Lucene简介和索引原理
Lucene底层原理和优化经验分享(2)-Lucene优化经验总结
Frame of Reference and Roaring Bitmaps
Better Query Planning for Range Queries in Elasticsearch
Latency spike after big merge
Cache costly subqueries asynchronously
Solr Wiki
以上就是关于如何用Solr搭建大数据查询平台全部的内容,包括:如何用Solr搭建大数据查询平台、YDB是什么、阿里 ES 面试题等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)