13

Grails1.1beta2を使用しています。Grailsアプリケーションに大量のデータをインポートする必要があります。grailsドメインクラスを繰り返しインスタンス化してから保存すると、パフォーマンスが許容できないほど遅くなります。たとえば、電話帳から人をインポートします。

for (each person in legacy phone book) {
    // Construct new Grails domain class from legacy phone book person
    Person person = new Person(...)
    person.save()
}

これは痛々しいほど遅いことがわかりました。Grailsメーリングリストの誰かが、トランザクションでセーブデータをバッチ処理することを提案しています。だから今私は持っています:

List batch = new ArrayList()
for (each person in legacy phone book) {
    // Construct new Grails domain class from legacy phone book person
    Person person = new Person(...)
    batch.add(person)
    if (batch.size() > 500) {
        Person.withTransaction {
            for (Person p: batch)
                p.save()
            batch.clear()
        }
    }
}
// Save any remaining
for (Person p: batch)
    p.save()

これは、少なくとも最初は、より速く動作する必要があります。各トランザクションは500レコードを保存します。時間が経つにつれて、トランザクションはますます長くかかります。最初の数回のトランザクションには約5秒かかり、その後はそこから忍び寄ります。約100回のトランザクションの後、それぞれが1分以上かかりますが、これもまた受け入れられません。さらに悪いことに、Grailsは最終的にJavaヒープメモリを使い果たします。OutOfMemoryErrorJVMヒープサイズを増やすことはできますが、それは例外を遅らせるだけです。

これがなぜであるかについて何か考えはありますか?いくつかの内部リソースがリリースされていないようなものです。パフォーマンスが悪化し、メモリが保持され、最終的にシステムのメモリが不足します。

Grailsのドキュメントによると、 SpringのオブジェクトwithTransactionにクロージャーを渡します。トランザクションを終了/終了するためのTransactionStatus情報が見つかりませんでした。TransactionStatus

編集:これはGrailsのコンソールから実行しています(grails console

編集:メモリ不足の例外は次のとおりです。

Exception thrown: Java heap space

java.lang.OutOfMemoryError: Java heap space
    at org.hibernate.util.IdentityMap.entryArray(IdentityMap.java:194)
    at org.hibernate.util.IdentityMap.concurrentEntries(IdentityMap.java:59)
    at org.hibernate.event.def.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:113)
    at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:65)
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:26)
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
    at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
    at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
    at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:655)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:732)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:701)
    at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140)
4

2 に答える 2

15

Ted Naleid は、 バッチ パフォーマンスの改善に関する素晴らしいブログ エントリを書きました。ここも含めて参考までに。

于 2009-10-28T04:03:29.067 に答える
12

これはすべての休止状態アプリケーションに共通の問題であり、休止状態セッションの増加が原因です。grailsコンソールは、通常のWebリクエストで使用されることがわかっている「ビューでセッションを開く」パターンと同様の方法でHibernateセッションを開いたままにしていると思います。

解決策は、現在のセッションを取得し、各バッチの後でそれをクリアすることです。コンソールを使用してSpringBeanを取得する方法がわかりません。通常、コントローラーまたはサービスの場合は、メンバーとして宣言するだけです。次に、で現在のセッションを取得できますsessionFactory.getCurrentSession()。クリアするには、を呼び出すsession.clear()か、オブジェクトsession.evict(Object)ごとに選択的に使用する必要がありPersonます。

コントローラ/サービスの場合:

class FooController {
    def sessionFactory

    def doStuff = {
        List batch = new ArrayList()
        for (each person in legacy phone book) {
            // Construct new Grails domain class from legacy phone book person
            Person person = new Person(...)
            batch.add(person)
            if (batch.size() > 500) {
                Person.withTransaction {
                    for (Person p: batch)
                        p.save()
                    batch.clear()
                }
                // clear session here.
                sessionFactory.getCurrentSession().clear();
            }
        }
        // Save any remaining
        for (Person p: batch)
            p.save()
        }
    }
}

お役に立てれば。

于 2009-01-10T21:45:46.613 に答える