Grails, Вставка большого количества данных с помощью withTransaction приводит к 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)

В каком контексте это выполняется? Работа с кварцем? Контроллер? Когда мы делали это в прошлом, использование контроллера позволило нам установить цикл, который может ограничивать размер пакетной обработки, при этом размер последующей транзакции в Сервисе соответственно ограничен.

j pimmel 10.01.2009 23:08
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
12
1
15 504
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

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

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

Я бы улучшил этот код, используя session.clear () на каждой N-й итерации цикла, а не на каждой.

Pavel Vlasov 26.07.2013 16:37

полностью согласен ... глядя на этот код сейчас, я не уверен, что он вообще работает.

Gareth Davis 26.07.2013 17:20

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

Другие вопросы по теме