Study/JPA

[JPA] OSIV를 활용한 성능 최적화

lsh2613 2023. 9. 19. 22:16

OSIV

  • Open Session In View - 하이버네이트
  • Open EntityManager In View - JPA

JPA 표준 이전에 하이버네이트를 사용해와서 관례상 OSIV라고 부른다.

스프링에서는 spring.jpa.open-in-view를 통해 설정할 수 있다.

 

 

Open EntityManager In View 말 그대로 View 레벨에서 EntityManager를 open 열어두겠다. = 사용하겠다. 라는 의미이다.

EntityManager : 영속성 컨텍스트

 

영속성 컨텍스트는 DB와 통신하며 데이터를 주고 받는다. 따라서 영속성 컨텍스트는 기본적으로 트랙잭션 범위 안에서 사용해야 하지만 Contoller, View 단에서도 사용할 수 있도록 영속성 컨텍스트 범위를 늘려주는 것이 바로 Open EntityManager In View이다.

 

OSIV: ON

OSIV: OFF

 

이게 왜 문제가 될까? off로 두었을 때 문제점을 살펴보자.

 

문제점

위 그림처럼 OSIV: OFF로 두면 영속성 컨텍스트는 Controller에서는 접근이 끊긴다. LAZY 페치 전략을 사용한 필드를 Controller에서 프록시 초기화를 한다고 생각해보자.

// Controller 레벨
@GetMapping("/api/v1/orders")
public List<Order> ordersV1() {
    List<Order> all = orderRepository.findAll();
    for (Order order : all) {
        order.getMember().getName(); //Lazy 강제 초기화
        order.getDelivery().getAddress(); //Lazy 강제 초기환
        List<OrderItem> orderItems = order.getOrderItems();
        orderItems.stream().forEach(o -> o.getItem().getName()); //Lazy 강제 초기화
    }
    return all;
}
// application.yml
spring:
  jpa:
    open-in-view: false

이대로 실행하면 proxy 객체를 초기화할 수 없다고 에러가 뜨면서 no session 이라는 문구가 뜬다.

 

그럼 굳이 off를 할 필요 없는 거 아닌가? on으로 하게 되면 단점이 존재한다.

 

OSIV: ON 단점

OSIV는 영속성 컨텍스트와 데이터베이스의 커넥션 리소스를 유지함으로써 영속성 컨텍스트의 생존 범위를 늘린 것이다. 그런데 이 전략은 너무 오랜시간동안 DB 커넥션 리소르를 사용하기 때문에, 실시간 트래픽이 중요한 애플리케이션에서는 커넥션이 모자랄 수 있다. 이것은 결국 장애로 이어진다.

 

예를 들어 컨트롤러에서 외부 API를 호출하면 그 대기시간 만큼 커넥션 리소스를 반환하지 못하고, 유지해야 한다.

 

따라서 성능 최적화를 위해선 OSIV: OFF를 설정해야 한다.

 

그럼 위 문제를 어떻게 해결할 수 있을까?

 

해결 방법

간단하다. 영속성 컨텍스트를 트랜잭션 범위에서만 사용할 수 있다면 컨트롤러의 해당 비즈니스 로직을 서비스 단으로 옮기면 된다.

@GetMapping("/api/v1/orders")
public List<Order> ordersV1() {
    List<Order> all = orderRepository.findAll();
    orderService.lazyInitializationInService(all);
    return all;
}

 

보통 비즈니스 로직은 특정 엔티티를 등록 및 수정하는 것이므로 성능이 크게 문제 되지 않는다.

그런데 복잡한 화면 출력을 위한 쿼리는 화면에 맞추어 성능 최적화가 중요해진다. 하지만 그 복잡성에 비해 핵심 비즈니스에 큰 영향을 주는 것은 아니다. 이 둘의 관심사를 명확하게 분리하는 선택은 유지보수 관점에서 충분히 의미가 있다. 

결론은 다음 처럼 분리하는 것이다.

 

패키지 계층

  • OrderService
    • 핵심 비즈니스 로직1
    • 핵심 비즈니스 로직2
    • 핵심 비즈니스 로직3
    • OrderQueryService
      • 화면이나 API에 맞춘 서비스 (주로 읽기 전용 트랜잭션 이용)