Пакетная работа с JPA работает очень медленно - PullRequest
0 голосов
/ 04 января 2019

Я столкнулся с проблемой производительности моего пакетного задания, написанного с использованием JPA (с использованием OpenJPA), которое работает как простое Java-приложение.Я пытаюсь вставить огромный список объектов, например, более 10 миллионов записей.Я знаю, что этот дизайн не является правильным.Но я получу этот объем данных внезапно, и я не смогу разделить всю работу.

Я разделил список с подсписками размером 100 000 каждый.И я вызываю метод транзакции JPA для каждого из этого подсписка.В каждой такой транзакции я очищаю список, когда он достигает 2000. Насколько я понимаю, для 1 миллиона записей он совершает 100 транзакционных вызовов.

Как только работа началась, я увидел, что 6 миллионов записей вставляются в течение 15-20 минут, а среднее время составляет всего одну минуту для 300 000. Но после того, как оно достигло 6-6,5 миллионов, работа выполняется очень медленноПримерно 10 тысяч за 4-6 минут, ощущение остановилось.Но он продолжает работать, и нет недостатка памяти в куче.

Может кто-нибудь сказать, что ошибка в моем коде.Я пробовал с разными размерами чанка (25K, 50K, 100K) для подсписка.Я не понимаю, чем вызвана эта медлительность после середины Иова.Должен ли я очищать EM после каждой транзакции?Я также увеличил размер пула соединений.

Вот мой код:

    @Stateless()
    @LocalBean
    @TransactionAttribute(TransactionAttributeType.NEVER)
    public class BatchService{

    @EJB 
    private PersonService personService;

    public void run(List<Person> personList) {
            int totalEventSize = personList.size();
            int quotient = totalEventSize / 100000;
            int modulo = totalEventSize % 100000;
            int totalIterations = quotient + (modulo != 0 ? 1 : 0);
            int startCount = 0;
            int endCount = 0;
            for (int i = 1; i <= totalIterations; i++) {
                if (i == totalIterations) {
                    endCount = totalEventSize;
                } else {
                    endCount = startCount + 100000;
                }
                List<Person> subList = personList.subList(startCount, endCount);
                personService.create(subList);
                startCount = endCount;
            }

        }

    }

    @Stateless
    @LocalBean
    public class PersonService implements Serializable {

    @EJB
    private PersonDLService personDLService;

    public void create(List<Person> list) {
            try {
                personDLService.createPerson(list);
            } catch (RuntimeException e) {
                e.printStackTrace();
            }
    }
}


    @Stateless
    @LocalBean
    @TransactionAttribute(TransactionAttributeType.MANDATORY)
    public class PersonDLService implements Serializable {
        private static final long serialVersionUID = 1L;

        @PersistenceContext(unitName = Constants.PERSISTENCE_UNIT_NAME)
        private transient EntityManager entityManager;

        public void createPerson(List<Person> personObj) {
            for (int i = 0; i < personObj.size(); i++) {
                entityManager.persist(personObj.get(i));
                if (i % 2000 == 0) {
                    entityManager.flush();
                    entityManager.clear();
                }
            }
            System.out.println("***************** COMMITED ****************" + personObj.size());
        }

    }

1 Ответ

0 голосов
/ 03 февраля 2019

Слишком большое количество данных очень часто является проблемой при использовании JPA в пакетных заданиях. А 10 миллионов строк - это много.

Во-первых, я бы использовал лучший API при наличии более 100000 строк: batch jdbc.

Например, с использованием пакета jdbc:

@Stateless
@LocalBean
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public class PersonService implements Serializable {
private static final long serialVersionUID = 1L;

@PersistenceContext(unitName = Constants.PERSISTENCE_UNIT_NAME)
private transient EntityManager entityManager;

public void doIt() {
    // get a jdbc connection from the entityManager (unwrap(Connection.class) is openjpa specific)
    // or you may as well get a jdbc connection from a jdbc DataSource
    try (Connection connection = entityManager.unwrap(Connection.class)) {
        // if Postgresql or Oracle DB, you may need to add a nextval for a sequence in the sql
        String sql = "insert into person (name) values (?)";
        try (PreparedStatement statement = connection.prepareStatement(sql)) {
            int i = 0;
            for (Person person : personList) {
                i++;
                statement.setString(1, person.getName());
                statement.addBatch();
                if (i == 1000) {
                    statement.executeBatch();
                    i = 0;
                }
            }
            if (i > 0) {
                statement.executeBatch();
            }
        }
    }
}
}

Если этого недостаточно, вы можете попытаться добавить connection.commit() каждый миллион строк.

...