首页 > 解决方案 > 为什么从 8GB 堆的 800MB 索引中获取所有文档时有时会出现 OOM?

问题描述

我需要刷新由 SOLR 7.4 管理的索引。我使用 SOLRJ 在具有 8 个 CPU 和 32GB 内存(8GB 堆用于索引部分,24GB 用于 SOLR 服务器)的 64 位 Linux 机器上访问它。要刷新的索引大小约为 800MB,包含大约 36k 个文档(根据 Luke 的说法)。

在开始索引过程本身之前,我需要“清理”索引并删除与磁盘上的实际文件不匹配的文档(例如:文档先前已被索引并从那时起已移动,因此用户将无法如果它出现在结果页面上,则打开它)。

为此,我首先需要获取索引中的 Document 列表:

final SolrQuery query = new SolrQuery("*:*"); // Content fields are not loaded to reduce memory footprint
        query.addField(PATH_DESCENDANT_FIELDNAME); 
        query.addField(PATH_SPLIT_FIELDNAME);
        query.addField(MODIFIED_DATE_FIELDNAME);
        query.addField(TYPE_OF_SCANNED_DOCUMENT_FIELDNAME);
        query.addField("id");
        query.setRows(Integer.MAX_VALUE); // we want ALL documents in the index not only the first ones

            SolrDocumentList results = this.getSolrClient().
                                               query(query).
                                               getResults(); // This line sometimes gives OOM

当 OOM 出现在生产机器上时,它会出现在“索引清理”部分,堆栈跟踪显示:

Exception in thread "Timer-0" java.lang.OutOfMemoryError: Java heap space
at org.noggit.CharArr.resize(CharArr.java:110)
at org.noggit.CharArr.reserve(CharArr.java:116)
at org.apache.solr.common.util.ByteUtils.UTF8toUTF16(ByteUtils.java:68)
at org.apache.solr.common.util.JavaBinCodec.readStr(JavaBinCodec.java:868)
at org.apache.solr.common.util.JavaBinCodec.readStr(JavaBinCodec.java:857)
at org.apache.solr.common.util.JavaBinCodec.readObject(JavaBinCodec.java:266)
at org.apache.solr.common.util.JavaBinCodec.readVal(JavaBinCodec.java:256)
at org.apache.solr.common.util.JavaBinCodec.readSolrDocument(JavaBinCodec.java:541)
at org.apache.solr.common.util.JavaBinCodec.readObject(JavaBinCodec.java:305)
at org.apache.solr.common.util.JavaBinCodec.readVal(JavaBinCodec.java:256)
at org.apache.solr.common.util.JavaBinCodec.readArray(JavaBinCodec.java:747)
at org.apache.solr.common.util.JavaBinCodec.readObject(JavaBinCodec.java:272)
at org.apache.solr.common.util.JavaBinCodec.readVal(JavaBinCodec.java:256)
at org.apache.solr.common.util.JavaBinCodec.readSolrDocumentList(JavaBinCodec.java:555)
at org.apache.solr.common.util.JavaBinCodec.readObject(JavaBinCodec.java:307)
at org.apache.solr.common.util.JavaBinCodec.readVal(JavaBinCodec.java:256)
at org.apache.solr.common.util.JavaBinCodec.readOrderedMap(JavaBinCodec.java:200)
at org.apache.solr.common.util.JavaBinCodec.readObject(JavaBinCodec.java:274)
at org.apache.solr.common.util.JavaBinCodec.readVal(JavaBinCodec.java:256)
at org.apache.solr.common.util.JavaBinCodec.unmarshal(JavaBinCodec.java:178)
at org.apache.solr.client.solrj.impl.BinaryResponseParser.processResponse(BinaryResponseParser.java:50)
at org.apache.solr.client.solrj.impl.HttpSolrClient.executeMethod(HttpSolrClient.java:614)
at org.apache.solr.client.solrj.impl.HttpSolrClient.request(HttpSolrClient.java:255)
at org.apache.solr.client.solrj.impl.HttpSolrClient.request(HttpSolrClient.java:244)
at org.apache.solr.client.solrj.SolrRequest.process(SolrRequest.java:194)
at org.apache.solr.client.solrj.SolrClient.query(SolrClient.java:942)
at org.apache.solr.client.solrj.SolrClient.query(SolrClient.java:957)

我已经从查询中删除了内容字段,因为已经存在 OOM,所以我认为只存储“小”数据可以避免 OOM,但它们仍然存在。此外,当我为客户启动项目时,我们只有 8GB 的​​ RAM(因此堆 2GB),然后我们将其增加到 20GB(堆 5GB),现在增加到 32GB(堆 8GB)并且 OOM 仍然出现,虽然与其他 SO 问题(包含数百万个文档)中描述的相比,该索引并没有那么大。

请注意,在将 800 MB 索引从生产机器复制到我的开发机器后,我无法在功能较弱的开发机器上重现它(16GB RAM,因此 4GB 堆)。

所以对我来说可能存在内存泄漏。这就是为什么我在我的开发机器上使用 800MB 索引关注Netbeans 在内存泄漏上的帖子。从我所看到的情况来看,我猜想存在内存泄漏,因为在“索引清理”期间索引幸存代的数量后索引不断增加(下面的陡峭线):

在我的项目中幸存几代

怎么办呢,8GB的堆和索引特性相比已经是一个巨大的堆了?所以增加堆似乎没有意义,因为 OOM 仅在“索引清理”期间出现,而不是在实际索引大型文档时出现,而且它似乎是由幸存的世代引起的,不是吗?创建一个查询对象然后应用getResults它会帮助垃圾收集器吗?

是否有另一种方法来获取所有文档路径?或者也许逐块检索它们(分页)即使对于少量的文档也有帮助?

任何帮助表示赞赏

标签: solrsolrj

解决方案


过了一会儿,我终于看到了这个帖子。它准确地描述了我的问题

内存不足 (OOM) 错误通常发生在带有大行参数的查询之后。Solr 通常可以正常工作,直到该查询出现。

所以他们建议(强调是我的):

Solr 的 rows 参数可用于返回超过默认值的 10 行。我已经看到用户成功地将 rows 参数设置为 100-200 并且没有看到任何问题。但是,将 rows 参数设置得更高会产生很大的内存后果,应该不惜一切代价避免

这就是我在每页检索 100 个结果时看到的内容:

在此处输入图像描述

尽管垃圾收集器的活动更加密集并且计算时间更长,但幸存的世代数量已大大减少。但是,如果这是避免 OOM 的成本,那没关系(请参阅程序每次索引更新都会损失几秒钟,这可能会持续几个小时)!

将行数增加到 500 已经使内存泄漏再次发生(幸存的世代数增加):

在此处输入图像描述

请注意,将行号设置为 200 并不会导致存活代数增加很多(我没有测量),但在我的测试用例中(小于 2%)并没有比“100”好多少环境 :

在此处输入图像描述

所以这是我用来从索引中检索所有文档的代码(来自 Solr 的 wiki):

SolrQuery q = (new SolrQuery(some_query)).setRows(r).setSort(SortClause.asc("id"));
String cursorMark = CursorMarkParams.CURSOR_MARK_START;
boolean done = false;
while (! done) {
 q.set(CursorMarkParams.CURSOR_MARK_PARAM, cursorMark);
 QueryResponse rsp = solrServer.query(q);
 String nextCursorMark = rsp.getNextCursorMark();
 doCustomProcessingOfResults(rsp);
 if (cursorMark.equals(nextCursorMark)) {
  done = true;
 }
cursorMark = nextCursorMark;
}

TL;DR:不要使用太大的数字,query.setRows即不大于 100-200,因为更大的数字很可能会导致 OOM。


推荐阅读