밤빵's 개발일지
[TIL]20240816 제이미터와 N+1문제 본문
JMeter와 N+1 문제의 관계는 성능 테스트와 데이터베이스 쿼리 최적화라는 두 가지 관점에서 중요한 이슈이다. N+1 문제는 데이터베이스 쿼리 성능을 저하시키는 일반적인 문제 중 하나로, 많은 쿼리를 불필요하게 발생시키는 원인이다. 반면, JMeter는 애플리케이션의 성능을 테스트하는 도구로, N+1 문제가 성능에 어떤 영향을 미치는지를 명확하게 파악할 수 있게 해준다.
🤓JMeter와 N+1 문제
JMeter는 Apache Software Foundation에서 제공하는 오픈 소스 소프트웨어로, 웹 애플리케이션의 성능 테스트와 부하 테스트를 수행할 수 있는 도구이다. 주로 HTTP 요청을 시뮬레이션하여 애플리케이션이 다양한 트래픽 상황에서 어떻게 동작하는지를 측정한다. 반면, N+1 문제는 객체-관계 매핑(ORM)에서 발생하는 성능 문제로, 하나의 쿼리를 수행한 후 연관된 데이터를 가져오기 위해 추가적인 쿼리를 반복해서 실행하는 상황을 말한다. 이로 인해 대규모 데이터 처리 시 성능 저하가 발생할 수 있다.
▶ N+1 문제의 간략한 이해
N+1 문제는 다음과 같은 시나리오에서 발생한다. 지난번에 다룬예시와 같은 상황으로"Order"와 "OrderItem"이라는 두 개의 엔티티가 있고, Order가 여러 OrderItem과 연관되어 있다고 가정하고, 모든 Order와 각각의 Order에 속한 OrderItem을 가져오는 쿼리를 작성할 때, 먼저 모든 Order를 가져오기 위한 1개의 쿼리가 실행되고, 그 후 각 Order에 대해 OrderItem을 가져오기 위한 N개의 추가 쿼리가 발생한다. 결과적으로 N+1개의 쿼리가 실행되는 상황이 발생하는 것이다. 이는 데이터베이스와의 불필요한 트래픽을 발생시켜 성능 저하를 초래한다.
▽ 예시 코드: N+1 문제 발생 예시
// Order 엔티티
@Entity
public class Order {
@Id
private Long id;
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY) // 연관된 엔티티를 지연 로딩으로 가져옴
private List<OrderItem> orderItems;
// Getter, Setter
}
// OrderItem 엔티티
@Entity
public class OrderItem {
@Id
private Long id;
@ManyToOne
@JoinColumn(name = "order_id")
private Order order;
// Getter, Setter
}
// Service에서 N+1 문제를 유발하는 쿼리 예시
public List<Order> findAllOrdersWithItems() {
List<Order> orders = orderRepository.findAll(); // 첫 번째 쿼리: 모든 Order 가져오기
for (Order order : orders) {
order.getOrderItems().size(); // N개의 쿼리: 각 Order의 OrderItem을 가져오기
}
return orders;
}
▶JMeter와 N+1 문제의 관계
JMeter는 애플리케이션의 성능을 테스트하기 위한 도구로, N+1 문제와 같은 성능 문제를 식별하는 데 매우 유용하다. N+1 문제가 발생할 경우, JMeter를 사용하여 여러 요청을 동시에 시뮬레이션할 때 응답 시간이 크게 증가하거나 서버 리소스가 과도하게 사용되는 것을 관찰할 수 있다.
▷ JMeter를 사용한 N+1 문제 테스트 방법
→ 테스트 계획 생성:
JMeter에서 테스트 계획을 생성하고, HTTP 요청을 시뮬레이션할 스레드 그룹(Thread Group)을 추가한다.
→ HTTP 요청 설정:
N+1 문제가 발생할 가능성이 있는 엔드포인트에 대한 HTTP 요청을 구성한다. 예를 들어, GET /orders 요청을 통해 모든 Order와 연관된 OrderItem을 가져오는 요청을 설정할 수 있다.
→ 반복 실행 및 동시 사용자 시뮬레이션:
스레드 그룹의 설정을 통해 동시 사용자 수와 요청 반복 횟수를 정의하여 여러 사용자가 동시에 해당 엔드포인트를 요청하도록 시뮬레이션한다.
→ 결과 분석:
JMeter의 결과 분석기를 통해 응답 시간, 처리 시간, 에러율 등을 확인한다. N+1 문제가 존재할 경우, 요청 수가 증가함에 따라 응답 시간이 비정상적으로 증가하고 데이터베이스의 부하가 급격히 상승하는 것을 확인할 수 있다.
▶JMeter를 통한 N+1 문제 해결의 중요성
JMeter를 통해 성능 테스트를 수행하면, N+1 문제로 인해 성능이 저하되는 부분을 명확히 파악할 수 있다. 성능 테스트 결과를 바탕으로 문제를 해결하기 위한 최적화 방법을 적용할 수 있고, 최적화가 적용된 후에도 다시 JMeter로 테스트를 수행하여 개선된 성능을 확인할 수 있다.
▶ N+1 문제 해결 방법과 JMeter로 검증
N+1 문제를 해결하기 위한 몇 가지 방법이 있다. 다음은 대표적인 방법들과 각각의 장단점, 그리고 해결 방법을 적용한 후 JMeter를 통해 성능이 개선된 것을 확인하는 과정이다. (제이미터로 확인을해야하는데 아직 제이미터 사용법이 서툴어서 추후 추가하는 방식으로 진행해야 할 것 같다.)
▽방법 1: @OneToMany와 fetch = FetchType.LAZY 대신 fetch = FetchType.EAGER 사용
@Entity
public class Order {
@Id
private Long id;
@OneToMany(mappedBy = "order", fetch = FetchType.EAGER) // 즉시 로딩으로 변경
private List<OrderItem> orderItems;
}
→ 장점:
한 번의 조인 쿼리로 필요한 데이터를 모두 가져올 수 있다.
쿼리 수가 줄어들어 성능이 개선된다.
→ 단점:
불필요한 데이터까지 모두 가져올 수 있어 메모리 사용량이 증가할 수 있다.
연관된 엔티티의 개수가 많을 경우 성능이 오히려 저하될 수 있다.
▽ 방법 2: @EntityGraph 사용
// OrderRepository 인터페이스에 추가
@EntityGraph(attributePaths = {"orderItems"}) // 연관된 엔티티를 즉시 로딩
@Query("SELECT o FROM Order o")
List<Order> findAllWithOrderItems();
→ 장점:
JPQL과 함께 사용하여 필요한 데이터만 즉시 로딩할 수 있다.
원하는 연관 엔티티만 선택적으로 가져와 메모리 사용을 최적화할 수 있다.
→ 단점:
특정 쿼리에 대한 설정이므로, 여러 곳에서 사용될 때 매번 설정해야 한다.
▶ JMeter를 통한 성능 개선 검증
→ 최적화 적용 전 테스트:
N+1 문제가 존재하는 상태에서 JMeter를 사용하여 동시 사용자 수가 많아질 때 성능 저하를 확인한다.
→ 최적화 적용 후 테스트:
fetch = FetchType.EAGER 설정을 적용하거나 @EntityGraph를 사용하여 최적화한 후, JMeter로 다시 동일한 성능 테스트를 수행한다.
→ 결과 비교:
최적화된 코드의 성능이 이전보다 개선되었는지 확인하고, 응답 시간과 처리량의 변화를 비교 분석한다.
▶정리
N+1 문제는 데이터베이스 쿼리 최적화와 애플리케이션 성능에 중요한 영향을 미치는 문제로, JMeter와 같은 성능 테스트 도구를 통해 그 문제를 명확히 파악하고 해결할 수 있다. 프로젝트를 함께 진행하는 팀원이 JMeter를 사용하여 성능 테스트를 진행하던 중 N+1 문제에 대해 언급했을 때, 이 두 가지 개념이 어떻게 서로 연결되서 해결이되는지 결과가 궁금해졌다. 그래서 이번 기회에 두 개념의 관계와 최적화 방법에 대해 학습하고 개발일지를 작성하게 되었다. 기영이머리처럼 삐죽삐죽 이쁜 그래프가 나와야 된다고 하는데 그 결과값을 얼른 이 개발일지에 추가로 첨부하고싶다.
'개발Article' 카테고리의 다른 글
[WIL]20240818 팀 프로젝트 마무리! Redis와 캐싱&세션 (0) | 2024.08.18 |
---|---|
[TIL]20240817 다대다(Many-to-Many) 연관관계 (0) | 2024.08.17 |
[TIL]20240815 스프링 스케줄러 (0) | 2024.08.15 |
[TIL]20240814 N+1 문제해결 (0) | 2024.08.14 |
[TIL]20240813 Nginx (0) | 2024.08.13 |