Spring + Hibernate performance issue (dirty checking)
От: InstantI Украина  
Дата: 23.01.12 09:17
Оценка:
Доброго времени суток. В моем приложении каждую минуту происходит сканирование игровых серверов, с последующим занесением результатов в базу данных. Столкнулся с серьезной проблемой производительности. Занесение результатов в БД (около 2000 записей) занимает ~30 секунд.
Есть следующий код:
@Service
public class ServerScannerImpl implements ServerScanner
{
    @Transactional
    public void process()
    {
        ...
        ... // Здесь код сканирования, далее занесение результата в БД
        handleResult();        
    }  
    
    private void handleResult()
    {
        fillServerInfo(server, info); // Для каждого сервера (примерно 2000) вызывается этот метод
    }
    
    private void fillServerInfo(Server server, ServerInfo info)
    {
        ...
        ...
        Map map = mapService.getMapByHttpName(mapHttpName, game.getId());
        if (map == null)
        {
            map = new Map();
            map.setName(info.getMap());
            map.setGame(game);
            map.setHttpName(mapHttpName);
            mapService.addMap(map);
        }
        server.setMap(map);
        ...
        ...
        serverService.addSever(server);
    }

    @Autowired
    private ServerService serverService;
    
    @Autowired
    private MapService mapService;
}

С помощью профайлера выяснил, что 80% времени выполнения приходится на этот метод:
Map map = mapService.getMapByHttpName(mapHttpName, game.getId());

@Service
@Transactional(readOnly = true)
public class MapServiceImpl implements MapService
{    
    public Map getMapById(Integer id)
    {
        return mapDAO.getMapById(id);
    }
   
    public Map getMapByHttpName(String httpName, Integer gameId)
    {
        return mapDAO.getMapByHttpName(httpName, gameId);
    }    
    
    @Transactional(readOnly = false)
    public void addMap(Map map)
    {
        mapDAO.addMap(map);
    }    
    
    public List<Map> getAllMaps()
    {
        return mapDAO.getAllMaps();
    }    
    
    @Transactional(readOnly = false)
    public void removeMap(Map map)
    {
        mapDAO.removeMap(map);
    }
    
    @Autowired
    private MapDAO mapDAO;
}

По call tree видно, что каждый раз, перед выполнением запроса к базе, происходит dirty checking:
org.hibernate.internal.SessionImpl.autoFlushIfRequired(Set)

А поскольку метод mapService.getMapByHttpName(mapHttpName, game.getId()) вызывается ~2000 раз, то львиная доля времени уходит на dirty checking.
Добавил в MapServiceImpl readOnly = true к аннотации @Transactional, но на ситуацию это не повлияло. Я новичек в этой области, поэтому не представляю как решить эту проблему. Спасибо за помощь.
Re: Spring + Hibernate performance issue (dirty checking)
От: maloi_alex СССР  
Дата: 23.01.12 11:45
Оценка: 4 (2)
Здравствуйте, InstantI, Вы писали:

Попробуй поэкспериментировать с Session.setFlushMode() т.е. поменять FlushMode до выполнения запроса.

В документации там такая штука написана:

В режиме AUTO (по умолчанию) изменения сбрасывается только при завершении транзакции или если в сессии есть изменения, которые могут отразиться на результатах запроса.

В режиме COMMIT изменения сбрасываются только при вызове Transaction.Commit.

В режиме NEVER / MANUAL изменения сбрасываются только если вручную вызвать Session.flush(). Этот режим наиболее эффективен для readonly транзакций.

Мне кажется можно попробовать вариант MANUAL (если в сессии нет изменений) или COMMIT (если в сессии есть изменения).
Re: Spring + Hibernate performance issue (dirty checking)
От: Аноним  
Дата: 23.01.12 11:52
Оценка:
Здравствуйте, InstantI, Вы писали:

II>Доброго времени суток. В моем приложении каждую минуту происходит сканирование игровых серверов, с последующим занесением результатов в базу данных. Столкнулся с серьезной проблемой производительности. Занесение результатов в БД (около 2000 записей) занимает ~30 секунд.


а зачем тут хибернейт? чего он тут упрощает\ускоряет процесс разработки?
Re: Spring + Hibernate performance issue (dirty checking)
От: konsoletyper Россия https://github.com/konsoletyper
Дата: 23.01.12 12:13
Оценка:
Здравствуйте, InstantI, Вы писали:

II>[/java]

II>По call tree видно, что каждый раз, перед выполнением запроса к базе, происходит dirty checking:
II>
II>org.hibernate.internal.SessionImpl.autoFlushIfRequired(Set)
II>

II>А поскольку метод mapService.getMapByHttpName(mapHttpName, game.getId()) вызывается ~2000 раз, то львиная доля времени уходит на dirty checking.
II>Добавил в MapServiceImpl readOnly = true к аннотации @Transactional, но на ситуацию это не повлияло. Я новичек в этой области, поэтому не представляю как решить эту проблему. Спасибо за помощь.

А что там делает MapDao.getMapByHttpName? session.createQuery()? Тогда там не столько dirty checking, сколько просто отправка изменений в БД. Т.е. Будет много перемежающихся между собой запросов SELECT и UPDATE. Ну тут только вручную загрузить нужные entity в память, применить к ним изменения и вызвать session.flush() или session.commit(). Причём, ещё и настроить batch update в Hibernate. Как узнать, какие именно записи нужно загружать в память — не знаю. Лично я с таким сталкивался на проводке документов. Я заранее знал перечень документов и выковыривал из их позиций перечень товаров/складов. В память, соответственно, загружал остатки по этим товарам/складам.

А вообще, как тут уже правильно сказали, задача слишком простая, чтобы в ней возиться ещё и с ORM.
Re[2]: Spring + Hibernate performance issue (dirty checking)
От: InstantI Украина  
Дата: 23.01.12 13:00
Оценка:
Здравствуйте, maloi_alex, Вы писали:

_>Попробуй поэкспериментировать с Session.setFlushMode() т.е. поменять FlushMode до выполнения запроса.


Спасибо! Добавил строчку sessionFactory.getCurrentSession().setFlushMode(FlushMode.COMMIT); Время с ~30 секунд снизилось до ~1 секунды.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.