Подтвердить что ты не робот

Grails, Вставка большого количества данных, используя результатыTransaction в OutOfMemoryError

Я использую Grails 1.1 beta2. Мне нужно импортировать большое количество данных в мое приложение 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 транзакций каждый из них занимает минуту, что еще раз неприемлемо. Хуже всего то, что в конечном итоге у Grails в конечном итоге закончится память кучи Java. Я могу увеличить размер кучи JVM, но это просто задерживает исключение OutOfMemoryError.

Любые идеи, почему это так? Это похоже на то, что некоторые внутренние ресурсы не выпускаются. Производительность ухудшается, память удерживается, а затем, в конце концов, в системе заканчивается память.

В соответствии с документацией Grails, withTransaction передает закрытие объекту Spring 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)
4b9b3361

Ответ 1

Это распространенная проблема со всеми приложениями спящего режима и вызвана ростом сеанса спящего режима. Я предполагаю, что консоль Grails содержит сеанс hibernate, открытый для вас аналогично шаблону "открытый сеанс в представлении", который, как я знаю, он использует для обычных веб-запросов.

Решение состоит в том, чтобы получить текущий сеанс и очистить его после каждой партии. Я не уверен, как вы можете использовать spring bean с помощью консоли, как правило, для контроллеров или служб, которые вы просто объявляете их членами. Затем вы можете получить текущий сеанс с помощью 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()
        }
    }
}

Надеюсь, что это поможет.

Ответ 2

Тед Нейлид написал отличную запись в блоге об улучшении производительности партии. В том числе здесь в качестве ссылки.