[JPA] API개발과 성능 최적화

maxxyoung·2022년 11월 4일
0
  • API 폴더를 Controller와 구분해서 따로 둘 것(공통 예외 처리가 다를 수 있으니 분리 한 것)
  • entity를 노출하지 말고 API 스펙에 맞춰 DTO를 만들것(넘나 중요!!)
  • DTO에 유효성 체크 할 것(@NotEmpty, @Size ...)
  • command와 query 분리
  • 바로 Json Array를 반환하지 말고 껍데기를 한 번 씌워서 return하기(return 스펙이 변했을 때 유연하게 대처하기 위해)

조회에 관한 API 성능 개선

  • 지연로딩과 조회 성능 최적화

    • API에서 엔티티를 직접 노출할 때 양방향 연관관계가 걸린 곳은 엔티티로 json 객체를 만들면서 무한루프가 돌 수 있으니 한 곳을 @JsonIgnore처리 해야함 -> DTO 사용할 것
    • 즉시 로딩을 설정했을 경우 해당 API에서 필요하지 않은 데이터를 항상 조회하여 성능에 문제가 생길 수 있음. 즉시 로딩으로 설정하면 성능 튜닝이 어려워짐. 따라서 지연 로딩을 기본으로 하고, 성능 최적화가 필요한 경우에는 페치 조인을 사용
    • 쿼리 방식 선택 권장 순서
      1. 우선 엔티티를 DTO로 변환하는 방법을 선택
      2. 필요하면 페치 조인으로 성능을 최적화. 대부분의 성능 이슈가 해결됨
      3. 그래도 안되면 DTO로 직접 조회하는 방법을 사용 (객체 그래프 조회)
      4. 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접 사용(쿼리를 그대로 이용하는 것이여서 api 명세가 변하면 같이 변하기 때문에 유연한 대처가 어려움)
  • 컬렉션 조회 최적화

    • DTO 안에 엔티티 객체 들어가는 것 안됨
    • 엔티티 안의 엔티티는 DTO 안의 DTO로 변경해야함
    • fetch join으로 가져올 경우, DB에서는 데이터 기준으로 join이 되고 JPA에서는 DB결과 값을 객체로 만들기 때문에 예상과는 다른 결과가 나올 수 있음(ex. 객체가 2개가 나올거라고 생각했지만 join결과 4row가 나와 객체가 4개가 생성될 수 있음)
    • disctict를 쓰면 쿼리에서 distict(같은 row 중복 제거)를 사용하고 JPA에서는 entity id를 기준으로 중복 객체를 제거해줌
    • collection fetch join을 이용하면 다(N)기준으로 row가 생성되어 데이터 모두 가져온 후, 메모리에서 페이징하기 때문에 위험함
    • collection fetch join을 2개 이상 이용하면 데이터가 부정확하게 조회될 수 있음
  • 페이징 한계 돌파

    • ToOne(OneToOne, ManyToOne) 관계를 모두 페치조인, 컬렉션은 지연로딩
    • 지연 로딩 성능 최적화를 위해 hibernate.default_batch_fetch_size , @BatchSize 를 적용
      • hibernate.default_batch_fetch_size: 글로벌 설정
      • @BatchSize: 개별 최적화 이 옵션을 사용하면 컬렉션이나, 프록시 객체를 한꺼번에 설정한 size 만큼 IN 쿼리로 조회
      • size는 DB, 애플리케이션이 순간 부하를 견딜 수 있을 만큼의 100 ~ 1000 사이 숫자 추천
  • OSIV와 성능 최적화

    • JPA에서 OSIV는 영속성 컨텍스트가 어디까지 살아 있나에 대한 설정임
    • spring.jpa.open-in-view : true(기본 값)
      • 최초 데이터베이스 커넥션 시작 시점부터 API 응답이 끝날 때 까지 영속성 컨텍스트와 데이터베이스 커넥션 유지
      • 커넥션이 계속 살아있으므로 지연로딩 가능
      • 디비 커넥션을 오랜시간 사용하기 때문에 실시간 트래픽이 중요한 애플리케이션에서는 커넥션이 모자를(마를) 수 있어 장애로 이어질 수 있음
    • spring.jpa.open-in-view : false
      • 트랜잭션을 종료할 때 영속성 컨텍스트를 닫고, 데이터베이스 커넥션도 반환
      • 커넥션 리소스를 낭비하지 않을 수 있음
      • 모든 지연로딩을 트랜잭션 안에서 처리해야 함 또는 fetch join 사용
    • command와 query의 분리
      • 보통 비즈니스 로직은 특정 엔티티 몇 개를 등록하거나 수정하는 것이므로 성능이 크게 문제가 되지 않음
      • 복잡한 화면을 출력하기 위해 쿼리는 화면에 맞추어 성능을 최적화 하는 것이 중요
      • 크고 복잡한 애플리케이션을 개발한다면 핵심 비즈니스와 복잡한 조회 쿼리를 분리하는 것이 유지보수 측면에서 의미 있음(OrderService: 핵심 비즈니스 로직, OrderQueryService: 화면이나 API에 맞춘 서비스)
      • 영한쌤은 실시간 API는 OSIV를 끄고, ADMIN 처럼 커넥션을 많이 사용하지 않는 곳은 OSIV를 켬

참고
자바 ORM 표준 JPA 프로그래밍
인프런 강좌 - 실전! 스프링 부트와 JPA 활용1

profile
오직 나만을 위한 글. 틀린 부분 말씀해 주시면 감사드립니다.

0개의 댓글