Удалить огромное количество строк из таймера EJB - PullRequest
1 голос
/ 17 февраля 2010

Мне нужно удалить миллионы строк из таблицы в EJB Timer. Проблема заключается в том, что таймер имеет время ожидания транзакции 90 секунд, поэтому я должен разделить работу на куски размера укуса.

Поскольку я не знаю, сколько строк можно удалить за 90 секунд, алгоритм должен зацикливаться и удалять несколько строк за раз, пока время почти не истекло.

Проблема в том, что: Как можно элегантно ограничить количество удаляемых строк в JPA? Удаление выполняется для всех строк, имеющих отметку времени ранее определенной даты.

Полагаю, можно найти 1000-ую самую старую строку, а DELETE WHERE timestamp <= {1000th-oldest-row.timestamp} Это, однако, не очень элегантно, и мне нужно было бы перейти к последнему ряду из 1000, чтобы получить метку времени.

Во-вторых, таймер должен снова сработать немедленно, если таблица не будет чистой после 90 секунд. Это легко решаемо, но опять же не очень элегантно.

Ответы [ 4 ]

2 голосов
/ 19 февраля 2010

Вы по-прежнему будете сталкиваться с проблемами истечения срока действия транзакции с имеющимся у вас решением.

Хитрость заключается в том, чтобы выполнить каждый блок в отдельной транзакции, как показано ниже в псевдокоде.

@Entity

@NamedQueries ( value = {
    @NamedQuery (
        name = pagedDeleteExpiredItems
        query=    DELETE FROM MyTable
            WHERE (<table key>) IN (
                SELECT <table key> FROM (
                SELECT ROWNUM AS row_num, <table key> FROM MyTable
                WHERE timestamp <= :currentTime
                )
                WHERE row_num < :pageSize
            )
    )
})

public class MyEntity {
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    int doPagedDeleteExpiredItems(Date currentTime, int pageSize) {
        Query query = em.createNamedQuery("pagedDeleteExpiredItems");
        query.setParameter("currentTime", currentTime);
        query.setParameter("pageSize", pageSize);
        int deleteCount = query.executeUpdate();
        return deleteCount;
    }
}


@EJBTimer
public class DeleteExpiredItemsTimer {

    @EJB(beanName = "MyEntity")
    MyEntity myEntity;

    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    void handleTimeout(Timer timer) {
        Date currentTime = getCurrentTime()
        int pageSize = 100
        int deleteCount;
        do {
            myEntity.doPagedDeleteExpiredItems(currentTime, pageSize);
        } while(deleteCount>0);
    }
}
1 голос
/ 17 февраля 2010

Один трюк, который я использовал в SQL, это УДАЛЕНИЕ ТОП 1000 (или 100 или 10000, в зависимости от среднего числа строк на странице), например:

DELETE top 1000 WHERE timestamp <= @ExpirationDate

Повторно вызывайте это до тех пор, пока строки не будут удалены (проверьте с помощью @@ rowcount) или пока не закончится время. Может ли этот метод быть реализован в JPA?

1 голос
/ 17 февраля 2010

У нас было похожее требование, и вот как мы его решили. Я использовал EJB 3.0.

  1. Таймер запускается при запуске приложения. сервер запускается (или модуль развертывается) в ServletContextListener.
  2. Когда таймер срабатывает, он обрабатывает до 100 ожидающих строк. Затем необходимо упорядочить результат запроса и ограничить количество строк.
  3. Если было 100 строк, таймер планирует следующий тайм-аут с 0ms. После этого транзакция фиксируется, и таймер снова запускается в новой транзакции.
  4. Если строк было меньше 100, таймер планирует следующий тайм-аут в 90sec.

Если, скажем, 250 строк, таймер срабатывает три раза подряд. Существует только небольшая проблема, если нужно обработать ровно 100 строк, и в этом случае таймер срабатывает два раза подряд, но второй выстрел фактически ничего не обрабатывает. Но в целом все работало нормально.

0 голосов
/ 17 февраля 2010

Решил проблему, получив отсортированный список строк, подлежащих очистке, и используя setFirstResult (int), аналогично setMaxResults (int). Таким образом, я получаю заказ предмета с максимальным количеством шагов от самого старого.

Query expired = dm.createNamedQuery("getExpiredElements");
expired.setParameter("currentTime", getCurrentTime());
expired.setMaxResults(maxCount);
expired.setFirstResult(maxCount);
@SuppressWarnings("unchecked")
List<Item> expiredChunk = (List<Item>) expired.getResultList();
long lastChunkEndTime = expiredChunk.get(0).getEndTime();
Query query = em.createNamedQuery("deleteExpiredItems");
query.setParameter("currentTime", lastChunkEndTime);
int result = query.executeUpdate();
return result >= maxCount;

Функция возвращает истину (по крайней мере), если она должна быть выполнена снова.

...