Hibernate-Search flushToIndexes导致java.lang.OutOfMemoryError(堆空间)

时间:2018-12-11 18:04:44

标签: java hibernate-search

我有一个使用Hibernate的Spring应用程序,并通过Hibernate-Search连接到Elasticsearch。
为了简化示例,我将仅放置必需的注释和代码。

我有一个实体 A ,包含在多个 B 实体中(很多,实际上是8000个左右)。
B 实体还包含许多嵌入的详细信息(实体 C E ,...)。
这些实体都与 @IndexedEmbedded @ContainedIn Hibernate-Search注释关联(请参见下面的示例)。
我已经创建了服务,修改了 A 对象的字段,并强制通过 flushToIndexes 进行刷新。

在刷新时,Hibernate-Search更新 A 索引,并且由于 @ContainedIn 而在8000 B 索引上传播。 但是由于某种原因,要更新 B 索引,Hibernate-Search会一次加载链接到 A 对象的每8000个 B 对象, 以及那些 B 对象( C E 等)中包含的每个详细信息。
所有这一切都需要很长时间,并且仅以 java.lang.OutOfMemoryError:Java堆空间结尾。


@Entity
@Table(name = "A")
@Indexed
public class A {

    @ContainedIn 
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "a") 
    private Set<B> bCollection;

    @Field
    @Column(name = "SOME_FIELD")
    private String someField;                            // Value updated in the service
}

@Entity
@Table(name = "B")
@Indexed
public class B {

    @IndexedEmbedded
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "A_ID")
    private A a;

    @IndexedEmbedded
    @OneToOne(fetch = FetchType.LAZY, mappedBy = "b")
    @Fetch(FetchMode.JOIN)  
    private C c;                                         // Some other details

    @IndexedEmbedded
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "b")
    private Set<E> eCollection;                          // Some other details
}

// My service
aObject.setSomeField("some value");
fullTextSession.flushToIndexes();

将JVM分配的内存增加(从8GB增加到24 GB,对于大约10000个对象来说实际上是很多)并没有解决任何问题。 因此,我认为整个数据集的加载需要超过24 GB ...

但是,问题似乎比看起来复杂得多〜
那是个错误吗?那很常见吗?我做错了什么 ?我该怎么解决?
是否有一些隐藏的Hibernate-Search配置,以避免此行为?

1 个答案:

答案 0 :(得分:1)

这是Hibernate Search的局限性。 @ContainedIn仅在小型协会中表现相对较好;像您这样的大型实体确实会触发所有关联实体的加载,并且效果会很差,或者在最坏的情况下会触发OOM。

该问题尚未解决,因为问题非常复杂。我们将需要使用查询代替@ContainedInHSEARCH-1937)的关联,这很简单。但更重要的是,我们需要执行分块(定期刷新/清除),这可能会对用户会话产生副作用,或者在用户事务之外执行(HSEARCH-2364),这两种操作都可能会带来不良后果

解决方法是@ContainedIn上添加A.bCollection,并手动处理重新索引编制:https://docs.jboss.org/hibernate/search/5.11/reference/en-US/html_single/#manual-index-changes

与我在another answer中提到的类似,您可以采用以下两种策略之一:

  1. 简单的方法:使用质量索引器定期重新索引所有B实体,例如每天晚上。
  2. 困难的路径:A发生更改时,请将“此实体更改”信息保存在某处(这很简单,就像在实体A上存储“上次更新日期/时间”,或在其中添加一行事件表)。同时,有一个定期的过程检查这些更改,加载受影响的B型实体,并为它们重新编制索引。最好以可管理的大小批量执行此操作,如果可以的话,每批次执行一次事务(这样可以避免一些麻烦)。

第一个解决方案非常简单,但是有一个很大的缺点,即Person索引最多可能过24小时。取决于您的用例,这可能没事,也可能没有。如果您有许多B型实体(读取为数百万个),并且完全重新索引花费的时间超过几分钟,那么这也不可行。

第二种解决方案很容易出错,基本上可以完成Hibernate Search的工作,但即使对于非常大的表也可以使用,并且数据库更改和重新索引之间的延迟会大大缩短。