성능 최적화(3) - 배치 처리

2024. 2. 13. 22:04spring/JPA

수백만 건의 데이터를 배치 처리해야 하는 상황이라 가정해보자. 일반적인 방식으로 엔티티를 게속 조회하면 영속성 컨텍스트에 아주 많은 엔티티가 쌓이면서 메모리 부족 오류가 발생한다. 따라서 이런 배치 처리는 적절한 단위로 영속성 콘텍스트를 초기화해야 한다. 또한 2차 캐시를 사용하고 있다면 2차 캐시에 엔티티를 보관하지 않도록 주의해야 한다.

 

JPA 등록 배치

수만 건 이상의 엔티티를 한 번에 등록할 때 주의할 점은 영속성 컨텍스트에 엔티티가 계속 쌓이지 않도록 일정 단위마다 영속성 컨텍스트의 엔티티를 데이터베이스에 플러시하고 영속성 컨텍스트를 초기화해야 한다.

 

public static void main(String[] args) {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
    EntityManager em = emf.createEntityManager();

    EntityTransaction tx = em.getTransaction();
    tx.begin();

    try {

      	for (int i = 0; i < 100; i++) {
        	Product product = new Product("item"+i, 10000);
            em.persist(product);
            
            if (i % 100 == 0) {
            	em.flust();
                em.clear();
            }
        }

        tx.commit();
    } catch (Exception e){
        e.printStackTrace();
        tx.rollback();
    } finally {
        em.close();
    }

    emf.close();
}

 

예제는 엔티티를 100건 저장할 때마다 플러시를 호출하고 영속성 컨텍스트를 초기화한다.

 

수정 배치는 아주 많은 데이터를 조회해서 수정한다. 이때 수많은 데이터를 한번에 메모리에 올려둘 수 없어서 2가지 방법을 주로 사용한다.

 

- 페이징 처리

- 커서

 

JPA 페이징 배치 처리

public static void main(String[] args) {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
    EntityManager em = emf.createEntityManager();

    EntityTransaction tx = em.getTransaction();
    tx.begin();

    try {
    
    	int pageSize = 100;

      	for (int i = 0; i < 10; i++) {
        	List<Product> products = em.createQuery("select p from Product p",
            	Product.class)
                    .setFirstResult(i*pageSize)
                    .setMaxResults(pageSize)
                    .getResultList();
            
            //수정
            for (Product product : products) {
            	product.setPrice(product.getPrice() + 1000);
            }
            
            em.flust();
            em.clear();
        }

        tx.commit();
    } catch (Exception e){
        e.printStackTrace();
        tx.rollback();
    } finally {
        em.close();
    }

    emf.close();
}

 

예제는 한 번에 100건씩 페이징 쿼리로 조회하면서 상품의 가격을 1000원씩 증가한다. 그리고 페이지 단위마다 영속성 컨텍스트를 플러시하고 초기화한다.

 

 

하이버네이트 scroll 사용

JPA는 JDBC 커서를 지원하지 않으므로 하이버네이트 세션을 사용해야 한다. 하이버네이트는 scroll이라는 이름으로 JDBC 커서를 지워한다.

public static void main(String[] args) {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
    EntityManager em = emf.createEntityManager();

    EntityTransaction tx = em.getTransaction();
    Session session = em.unwrap(Session.class);
    
    tx.begin();
	
    ScrollableResults scroll = session.createQuery
    	("select p from Product p")
        	.setCacheMode(CacheMode.IGNORE) //2차 캐시 끔
            .scroll(ScrollMode.FOWARD_ONLY)
            
    int count = 0;

   	while (scroll.next()) {
    	Product p = (Product) scroll.get(0);
        p.setPrice(p.getPrice() + 1000);
        
        count++;
        if (count % 100 == 0) {
        	session.flush();
            session.clear();
        }
    }
	tx.commit();
    session.close();
}