原文地址:https://cwiki.apache.org/confluence/display/SOLR/SolrPerformanceProblems
本文将回答下面的问题:
- 为什么Solr性能这么差。
- 为什么Solr启动时间那么长。
- 为什么SolrCloud在我的服务器表现正常的情况下出现故障。
本文尝试提供一些基本的信息。为了更好的理解设计的问题,请阅读其他资源,http://www.catb.org/~esr/faqs/smart-questions.html#intro。http://lucene.apache.org/solr/discussion.html。
1 General information
影响Solr性能主要因素是RAM。Solr需要足够的内存做两件事情:一个是java堆,另一个是磁盘缓存。
另一个潜在的问题是很高的查询请求。有时候增加内存可以让solr处理更高的查询速度。如果支持更大的查询,你将需要多台机器上添加索引的多副本进行扩展。对于单例的Solr,或非云的client,可以使用SolrCloud,多副本的时候需要使用负载均衡。
强烈建议Solr运行在64位的Java上。64位java需要64位的操作系统,而64位操作系统需要64位的CPU。32位软件应将没有什么问题,但是32位java限制于2G的顿大小。本文后面部分将会讨论java堆。
1.1 SolrCloud
无论节点数量或可用资源数量多少,当集合数量达到数百个时,SolrCloud都会出现稳定性问题。对于成千上万个集合,集群的任何小问题都可能导致稳定性直线下降,可能在几十分钟内无法恢复。所以尽可能的保持集合数量较少。这些问题是由于SolrCloud需要在zookeeper上更新集群状态。正在试图解决此问题。在Solr4.x状态会保存一个“clusterstate.json”文件。后续版本5.x以后会给每个collection保存一个“state.json”,会在每个collection的znode下保存(/collections/my_collection/state.json)。
因为SolrCloud高度依赖Zookeeper,所以在遇见性能问题会导致操作时间超过zkClientTimeout(https://lucene.apache.org/solr/guide/6_6/solrcloud.html#SolrCloud-SolrCloud_Instance_Zookeeper_Params),引起不稳定。延长超时可能会缓解,但最好是解决根本的性能问题。默认超时时间为15秒,一般就足够了。
Zookeeper设计时就假设他能快速的访问他的数据。如果Zookeeper数据存储和solr的数据存储在相同磁盘上,那么solr的任何性能问题都将导致Zookeeper访问自己的数据出现延迟。这可能导致性能急速下降,每个zk超时后的恢复操作,会进一步导致超时。
Zookeeper会将数据保存在java堆中,所以磁盘的读取性能没有写入性能那么重要。如果磁盘缓存太小无法满足Solr本身的要求,并且zk和solr的数据在一块磁盘,则solr的大量磁盘访问会干扰zk的写入。最好给zk用快速磁盘(例如SSD)可以提升性能。强烈建议将solr和zk的磁盘隔离开。最好为所有zk节点配独立的服务器(至少3个节点),但也不是强要求。
SolrCloud:https://lucene.apache.org/solr/guide/6_6/solrcloud.html
2 High Request Rate
如果你的请求qps很高,通常会极大的影响性能,如果请求超过30qps,需要考虑给索引添加副本。每秒处理上千个请求需要大量的服务器。
处理高查询通常是在多个服务器上设置索引的多副本,并实现负载均衡。SolrCloud可以自动执行这些操作。但也有可能需要外部的负载均衡器。
3 RAM
3.1 OS Disk Cache
索引更新,依赖于读取和写入的速度。搜索时快速随机读取更重要。如果需要满足这些需求,最好的办法是有大磁盘缓存(http://en.wikipedia.org/wiki/Page_cache)。查看https://blog.thetaphi.de/2012/07/use-lucenes-mmapdirectory-on-64bit.html
还可以使用固态硬盘加快Solr的速度,但这并不是完整的替代方案,请查看文档里关于SSD的部分。
简而言之,你需要有足够的内存可以用于索引的高速缓存。假设Solr索引大小为8G,Solr的jvm和其他程序需要4G,那么理想的服务器内存为12G。可以让服务器只有8G内存但是这不能保证缓存命中率。
如果你希望查询延迟尽可能的底,那么最好有足够的内存能缓存所有的索引。如果不需要低延迟,那么也就不需要缓存整个索引。
低延迟的要求并不确定,和索引的内容和查询都有关系。如果索引有很多字段,
(警告)非常重要的是,没有用于确定在高性能下最小内存的计算公式。
Special note regarding the optimize operation
优化索引时会使用大量的磁盘I/O。如果你没有足够的内存来缓存索引,那么优化操作会对正常的Solr造成很大的影响。如果需要经常优化,则需要能缓存100%索引的RAM。如果你有足够的RAM能同时缓存索引的原始版本和优化版本,则优化过程性能是最好的。当主服务器产生大量合并优化时,也会复制到从服务器。
3.2 Java Heap
Java堆是Java程序实际运行所需的内存。
在日志中发现有OutOfMemoryError(OOM)异常,则java进程就会结束。如果看到此问题,则说明Solr不能申请到更多的内存。处理OOM的两种方法,一种是增加机器的内存,另一种是减少solr的jvm内存。通常发生OOM是因为使用的线程/进程数过高。
Solr中的某些配置需要大量的堆内存。包括:
- 大索引。
- 频繁更新。
- 超大文档。
- 频繁使用facet。
- 排序参数大量不同。
- Solr的大缓存。
- RAMBufferSizeMB较大。
- 使用Lucene的RAMDirectoryFactory。
How much heap space do I need?
简单的说:这没有标准的答案。因为你需要足够大的JVM,这样就不会有OOM和垃圾回收问题,但是又不能太大以免浪费和垃圾回收带来的stop world。
复杂的说:必须经过试验。使用GUI工具(jconsole、jvisulavm),可以连接正在运行的solr实例,查看堆的使用情况。对于长期运行的JVM堆,可以使用SPM工具长期监控内存空间和垃圾回收。JVM Memory Poll Monitoring介绍了查看内存和避免OOM。
http://docs.oracle.com/javase/6/docs/technotes/guides/management/jconsole.html
http://docs.oracle.com/javase/6/docs/technotes/tools/share/jvisualvm.html
http://blog.sematext.com/2013/10/21/jvm-memory-pool-monitoring/
http://docs.oracle.com/javase/1.5.0/docs/guide/management/jconsole.html#memory
在jconsole例子中显示一种典型的内存使用攀升,然后垃圾回收释放了内存。你的查询和更新量决定了内存会有多少document。一种经验法则:查看Solr每秒的查询的document,如果每分钟垃圾回收超过该值,则jvm堆太小。经过不断的调整会是垃圾回收能快速工作。
如果Solr服务有很高的查询和更新,则JVM的垃圾回收的锯齿低点就是所需的最小内存。尽量将最大堆设置成改值的125%~150%之间,重复测试查看监控锯齿模式中低点是否明显高于以前,或垃圾回收是否频繁。如果是则表示还要继续调大堆。
另一个确定堆大小设置的是否合理是使用Solr打印的GC日志。gceasy可以帮助分析,和查看Solr的帮助文档。
http://lucene.apache.org/solr/discussion.html
其他经验法则:通常堆越大越好。但是太大垃圾回收时间会很长。
Reducing heap requirements
警告:调整垃圾回收参数不会减少Solr对内存的需要!他可以加快GC,但是无法降低内存使用。它会是锯齿形内存图的高点降低,但不会影响低点。如果你遇到的是内存不足,则调整GC参数是没有用的。更有效的垃圾回收只会让程序花费较长的时候产生OOM,但是不能避免。
下面列表,给出需要大量堆内存的因素,如何减少堆的使用:
将大索引进行切分 - 放到多个shard中:
比较简单的办法是使用SolrCloud。你可能需要重建索引。
这实际上并不会减少索引的总体内存需求(可能还会增加),但是他会分布在多个服务器上,因此每个服务器的内存需求较低。为了实现冗余,不同服务器上要增加副本。
如果查询效率低,则将多个shard放在单个服务器上会更好。随着查询的增加,每个服务器只有replicas会更好。
不要存储所有的字段,特别是大字段。
让你的程序从原始数据源加载详细数据,而不是从solr。
需要注意,这意味着不能使用Atomic Updates。
你还可以使用docValues在经常sorting/facet字段上。
减少用于sort的数量。docValues会对facet和sort的性能和内存使用产生积极影响。
减少solr缓存大小。
减少RAMBufferSizeMB。最新版本的Solr默认是100.如果你有多个core。这个值比较重要,因为每个core都会使用缓冲区。
不要使用RAMDirectoryFactory - 而是使用系统默认的并安装足够的RAM,让操作系统可以缓存整个索引。
GC pause problems
如果你有很大的堆(大于2GB),垃圾回收的暂停是主要的问题。这通常是因为要Full GC要进行“stop world”引起的 - 暂停所有进程执行清理内存。主要有两个解决方案:一种是使用上页面的JDK收费工具例如Zing,另一种是使用免费的JDK。GC调节是一种艺术,对别人有效并不一定对你也有效。
对于Solr使用CMS进行垃圾回收是比较好的,单对于java7以上版本G1是更好的选择。
使用CMD需要手动调整各代的大小。G1会自动调节大小,强制设置会导致性能下降。
如果最大堆设置的太小,则会遇到不同的垃圾回收问题。通常比大堆问题严重:每次Solr申请内存都需要进行Full GC,这样会导致非常慢。堆大小和GC很难调整的很难完美。
Asking for too many rows
如果想快速的查询rows=9999999,在5百万-1000万的数据集查询,会引起很多Full GC。既是实际命中的很少,但是客户端请求大量数据导致分配大量的Java对象(每行一个ScoreDoc)并保存到内存中。将产生大量的GC,并触发Full GC。有时候增加堆会有提升,但会经常进行GC。阅读下面文章会有一些建议。
https://sbdevel.wordpress.com/2015/10/05/speeding-up-core-search/
一种简单解决方案方案是减少rows,或者使用export、cursorMark(流式传输)。
如果你无法控制客户端,可以在solrconfig的invariants设置rows,或者自定义SearchComponent来自定义允许的最大rows。
Tools and Garbage Collection
除非是由于堆太小,否则JVisualVM和JConsole不会显示GC暂停问题。你只能看总计或者平均值。
可以使用下面工具。
http://www.azulsystems.com/jHiccup
https://code.google.com/p/gclogviewer/
https://github.com/chewiebug/GCViewer
3.3 SSD
固态硬盘,超高的传输速度,几乎解决了随机访问数据的延迟问题。
购买高级磁盘之前,需要考虑的是你如果有足够的内存进行缓存,那么磁盘的速度对查询性能影响不大。如果没有足够的内存进行缓存,则磁盘的速度将受影响,但是添加内存会提升性能。
在内存不足的情况下,将索引放到SSD上,则性能会比机械盘会好。如果当前Solr服务器正在使用SSD并发生性能瓶颈,则换更好的SSD也不一定能解决。
通常SSD被说成代替磁盘缓存的RAM。也对也不对。尽管SSD速度惊人,但是RAM(操作系统的磁盘缓存)的速度仍然很高,并且RAM在基于SSD的系统性能中仍然发挥着重要作用。SSD可能不用和机械盘一样多的RAM,但是必须要是用。是用机械盘时,需要50%至100%作为缓存。是用SSD时,如果索引小,则可以减少到25%到50%。
一种解决Solr问题的方法是将您的操作系统和Solr本身放在RAID的常规磁盘上,然后将索引数据放在一个单独的SSD上。 如果SSD发生故障,您的冗余服务器仍将在那里处理请求。
3.4 Asking for help on a memory/performance issue
如果你需要在性能问题寻求帮助,那么需要确定的首页问题是是否有能力让内存获取合理的性能。从操作系统获取信息有助于确定这一点。
4 Extreme scaling
当Solr索引的数量达到几十亿,查询又很高,则对硬件的要求会变得很苛刻。这个规模的索引处理会变得很安规,因为需要很多的服务器,每个服务器都需要很大的内存。SSD变得更加关键,成本会很高。
5 Slow startup
启动缓慢的主要原因有两个。一个是事务日志,一个是suggester组件。
5.1 Slow startup due to the transaction log
尽快可能还有其他问题,但是此问题最常见的原因是在Solr4.0引入的updateLog功能。问题不在于功能本身,而是取决于功能打开时如何配置和使用,transaction log可能会失控。
updateLog功能是为了更新添加事务日志。使用正确,事务日志可以带来好处,SolrCloud需要使用。还有软提交的概念。
如果大量的文档更新发送到索引而不进行提交或只进行软提交,则事务日志将变得很大。Solr启动时将replay事务日志来确保数据不会丢失。对于非常大的日志这会非常慢。使用DataImportHandler大量导入数据会导致日志较大,最好执行一次硬提交。
要解决启动缓慢问题,需要减少事务日志的大小。唯一的办法是经常进行硬提交。硬提交将关闭当前的事务日志并启动一个新的日志。Solr只会保留足够的日志确保可以还原最后100个文档。单不能拆分日志文件,因此如果日志文件非常大则必须保留整个内容在启动的时候进行replay。小型事务日志进行replay速度会很快,因此最好保持他很小。
解决方案是在solrconfig.xml的update handler打开autoCommit:
<updateHandler class="solr.DirectUpdateHandler2">
<autoCommit>
<maxDocs>25000</maxDocs>
<maxTime>300000</maxTime>
<openSearcher>false</openSearcher>
</autoCommit>
<updateLog />
</updateHandler>
大家在进行大量更新而不提交,可能是因为想在全部更新之前是不可见的。此要求需要修改openSearcher=false。使用此选项需要发送硬提交或软提交命令,让其可见。
你可以可以调整autoCommit中的maxDocs和maxTime参数。默认(25000个文档或5分钟)是一个很好的参考,当然在更新量非常高或非常低的情况下,也需要进行调整。
5.2 Slow startup due to the suggester component
如果索引较大,并且在solrconfig.xml中启用了suggester组件,则可能会导致启动缓慢。从4.10.2版本开始默认启动此组件。在5.1版本对此问题进行了修改。
6 Slow commits
通常,打开新的searcher才会提交很慢。提交缓慢的原因包括:
Solr的缓存autowarmCount设置较大。
堆大小问题。堆太大导致问题很少发生,但是会持续发生。
非常频繁的提交。
综上所述,都是没有足够的OS内存用于磁盘缓存。
如果Solr缓存的autowarmCount值较大,则可能会话很长时间才能进行缓存预热。filterCache的升温速度特别慢。解决方案是减少autowarmCount,或减少查询复杂度。
如果你频繁提交,则可以在上次提交完成之前发送新的提交。如果solrconfig.xml中的maxWarmingSearchers很高,则最终可能会使多个新的searcher进行预热,这是高I/O的工作。
如果在不打开新的searcher时提交缓慢,则可能是由于一般性能问题。
7 Slow indexing
慢速索引的原因很多。大多数情况下,solr不会变慢。慢索引最大的原因是从源系统检索信息的速度。
导致索引编制缓慢的其他可能问题包括:在每个更新请求之后提交,在每个更新请求中一次发送一个文档而不是批量处理他们,以及使用一个线程进行索引。这些是Solr外部的问题。可能的解决方案是使用IgnoreCommitOptimizeUpdateProcessorFactory忽略来自clien的提交,而是使用autoCommit的设置。