为什么在从具有 8GB 堆的 800MB 索引中获取所有文档时有时会出现 OOM?

Why am I sometimes getting an OOM when getting all documents from a 800MB index with 8GB of heap?

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

在开始索引过程本身之前,我需要 "clean" 索引并删除与磁盘上的实际文件不匹配的文档(例如:文档之前已经被索引并且从那时起已经移动,所以如果它出现在结果页面上,用户将无法打开它。

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

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 出现在生产机器上时,它出现在 "index cleaning" 部分并且堆栈跟踪读取:

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,所以我认为只存储 "small" 数据可以避免 OOM,但它们仍然存在。此外,当我为客户启动项目时,我们只有 8GB 的​​ RAM(因此堆为 2GB),然后我们将其增加到 20GB(堆为 5GB),现在增加到 32GB(堆为 8GB)并且 OOM 仍然出现,尽管与其他 SO 问题(包含数百万文档)中描述的内容相比,该索引并没有那么大。

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

所以对我来说可能存在内存泄漏。这就是为什么我在我的开发机器上使用 800MB 索引跟踪 Netbeans post on Memory Leaks。据我所知,我猜想存在内存泄漏,因为在 "index cleaning"(下面的陡峭线条)期间,在索引幸存世代数量后索引不断增加:

怎么办,8GB的heap相对于索引特性来说已经是一个巨量的heap了?所以增加堆似乎没有意义,因为 OOM 只出现在 "index cleaning" 期间,而不是在实际索引大型文档时出现,而且它似乎是由幸存的几代人引起的,不是吗?创建一个查询对象然后在其上应用 getResults 会有助于垃圾收集器吗?

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

感谢任何帮助

过了一段时间我终于遇到了 this post。它准确描述了我的问题

An out of memory (OOM) error typically occurs after a query comes in with a large rows parameter. Solr will typically work just fine up until that query comes in.

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

The rows parameter for Solr can be used to return more than the default of 10 rows. I have seen users successfully set the rows parameter to 100-200 and not see any issues. However, setting the rows parameter higher has a big memory consequence and should be avoided at all costs.

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

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

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

请注意,将行数设置为200并没有导致存活代数增加很多(我没有测量),但在我的测试用例中并没有表现得更好(不到2%)比“100”设置:

下面是我用来从索引 (from Solr's 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。