hibernateを使用して大量のデータを読むときのoutofmemory
-
19-09-2019 - |
質問
データベースから大量のデータをエクスポートする必要があります。これが私のデータを表すクラスです:
public class Product{
...
@OneToMany
@JoinColumn(name = "product_id")
@Cascade({SAVE_UPDATE, DELETE_ORPHAN})
List<ProductHtmlSource> htmlSources = new ArrayList<ProductHtmlSource>();
... }
ProductHtmlSource
- 実際にエクスポートする必要がある大きな文字列が含まれています。
エクスポートされたデータのサイズはJVMメモリよりも大きいため、チャンクでデータを読んでいます。このような:
final int batchSize = 1000;
for (int i = 0; i < 50; i++) {
ScrollableResults iterator = getProductIterator(batchSize * i, batchSize * (i + 1));
while (iterator.getScrollableResults().next()) {
Product product = (Product) iterator.getScrollableResults().get(0);
List<String> htmls = product.getHtmlSources();
<some processing>
}
}
のコード getProductIterator
:
public ScrollableResults getProductIterator(int offset, int limit) {
Session session = getSession(true);
session.setCacheMode(CacheMode.IGNORE);
ScrollableResults iterator = session
.createCriteria(Product.class)
.add(Restrictions.eq("status", Product.Status.DONE))
.setFirstResult(offset)
.setMaxResults(limit)
.scroll(ScrollMode.FORWARD_ONLY);
session.flush();
session.clear();
return iterator;
}
問題は、各データチャンクを読んだ後にセッションをクリアすることにもかかわらず、 Product
オブジェクトはどこかに蓄積され、私はOutMemoryの例外を取得します。問題は、メモリエラーが発生しなくても、コードの処理ブロックではありません。 1000のオブジェクトが簡単にメモリに座るため、バッチのサイズも問題ではありません。
プロファイラーは、オブジェクトが蓄積することを示しました org.hibernate.engine.StatefulPersistenceContext
クラス。
スタックトレース:
Caused by: java.lang.OutOfMemoryError: Java heap space
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:99)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:518)
at java.lang.StringBuffer.append(StringBuffer.java:307)
at org.hibernate.type.TextType.get(TextType.java:41)
at org.hibernate.type.NullableType.nullSafeGet(NullableType.java:163)
at org.hibernate.type.NullableType.nullSafeGet(NullableType.java:154)
at org.hibernate.type.AbstractType.hydrate(AbstractType.java:81)
at org.hibernate.persister.entity.AbstractEntityPersister.hydrate(AbstractEntityPersister.java:2101)
at org.hibernate.loader.Loader.loadFromResultSet(Loader.java:1380)
at org.hibernate.loader.Loader.instanceNotYetLoaded(Loader.java:1308)
at org.hibernate.loader.Loader.getRow(Loader.java:1206)
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:580)
at org.hibernate.loader.Loader.doQuery(Loader.java:701)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:236)
at org.hibernate.loader.Loader.loadCollection(Loader.java:1994)
at org.hibernate.loader.collection.CollectionLoader.initialize(CollectionLoader.java:36)
at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:565)
at org.hibernate.event.def.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:63)
at org.hibernate.impl.SessionImpl.initializeCollection(SessionImpl.java:1716)
at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:344)
at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:86)
at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:109)
at org.hibernate.collection.PersistentBag.size(PersistentBag.java:225)
**at com.rivalwatch.plum.model.Product.getHtmlSource(Product.java:76)
at com.rivalwatch.plum.model.Product.getHtmlSourceText(Product.java:80)
at com.rivalwatch.plum.readers.AbstractDataReader.getData(AbstractDataReader.java:64)**
解決
GetProductiterator()は開始行と行数を予想している間、開始および終了行番号を使用してgetProductiterator()を呼び出しているようです。 「上限」が高くなると、より大きなチャンクでデータを読んでいます。 GetProductitator()の2番目の引数としてBatchSizeを渡すことを意味すると思います。
他のヒント
直接的な答えではありませんが、この種のデータ操作のために、私は使用します Statelesssessionインターフェイス.
Keithlは正しいです - あなたはますます増加している限界を渡しています。しかし、とにかくそれを壊すことは意味がありません。スクロールカーソルの全体的なポイントは、一度に行を処理するため、チャンクに分割する必要がないことです。フェッチのサイズは、より多くのメモリを使い果たすためにデータベースへの旅行を減らします。一般的なパターンは次のとおりです。
Query q = session.createCriteria(... no offset or limit ...);
q.setCacheMode(CacheMode.IGNORE); // prevent query or second level caching
q.setFetchSize(1000); // experiment with this to optimize performance vs. memory
ScrollableResults iterator = query.scroll(ScrollMode.FORWARD_ONLY);
while (iterator.next()) {
Product p = (Product)iterator.get();
...
session.evict(p); // required to keep objects from accumulating in the session
}
とはいえ、エラーはgethtmlSourcesであるため、問題はセッション/カーソル/スクロールの問題と完全に無関係になる可能性があります。これらのHTML文字列が巨大で、ずっと参照されている場合は、連続したメモリが不足しているだけかもしれません。
ところで、ScrollableresultsにGetScrollableresultsメソッドが表示されません。
愚かに見えるリスクがある - あなたはこれを別の方法で行うことを考えましたか?
個人的には、データベースから「遠く」のバッチ処理を行わないようにします。どのデータベースを使用しているのかわかりませんが、通常、データベースからデータセットを効率的に引き出して、途中で適度に単純な操作を伴う場合でも、ファイルに効率的に引き出すメカニズムがあります。ストアドプロシージャ、特定の輸出ユーティリティ。データベースベンダーから他に何が利用できるかを調査します。
例外Stacktraceを投稿できますか? GCに適したJVMオプションを渡すことで解決できます。
これは関連していると思います - Java StringBuilder巨大なオーバーヘッド.
非常に大きな文字列が作成されており、例外を引き起こしているスタックトレースから見ています。