1. N+1이란 무엇인가.
ORM을 사용하여 DB와 상호작용을 할 때 발생하는 성능 이슈 중 하나이다. DB에 있는 데이터을 엔티티로 로드하는 과정에서 연관 엔티티를 어떻게 로드 할 것인가에서 나오는 문제이다.
ORM의 영속성 컨텍스트(persistence context)는 db의 데이터를 객체로 변환하여 영속성을 가질 수 있는 환경을 만들어 주는데
데이터의 영속성이라함은 애플리케이션의 실행 상태나 데이터를 영구적으로 저장하여, 프로그램이 종료된 후에도 사라지지 않고 유지되는 데이터의 특성을 말한다. 즉, 데이터를 영구적인 저장소로 저장하여 데이터가 지속될 수 있게 하는 것이다. 이는 컴퓨터가 재시작되어도 데이터를 보존하고 재사용을 가능하게 한다.
이런 데이터를 DB 밖에서 영속성을 유지한 상태로 객체화 시켜 어플리케이션에서 객체 상태로 사용할 수 있도록 ORM이 지원해주며 JPA는 이를 지원한다.
JPA의 Entity Manager는 영속 컨텍스트는 엔티티의 생명주기를 관리하는데, 이렇게 관리된 데이터 객체를 엔티티 매니져는 프로그램의 엔티티 객체와 매핑해준다.
여기서 프로그램의 엔티티 객체에 매핑하여 가져오는 과정의 전략에서 N+1이 나온다.
영속 상태를 유지한 엔티티 객체를 가져올 때 , 엔티티 객체 안에 연관 엔티티가 존재한다면 언제 가져올까?
를 선택할 수 있기 때문이다.
예를 들어 ContestEntity 라는 엔티티가 있다고 가정하자.
@Data
@Table(name = "contest")
@Entity
public class ContestEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String title;
@JoinColumn(name = "participant_id")
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<ParticipantEntity> participants;
}
그리고 대회에 연관되는 참가자 엔티티 ParticipantEntity가 있다고 가정하자.
@Data
@Table(name = "participant")
@Entity
public class ParticipantEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
}
대회에는 2가지 대회가 들어가 있고 각 대회마다 참가자가 2명씩 존재하도록 데이터를 세팅했다.
아래는 해당 조회 결과 값을 보여준다.
//조회 메서드
@GetMapping("/contests")
public List<ContestEntity> getContests() {
return contestRepository.findAll();
}
//결과
[
{
"id": 1,
"title": "1차 사차탈춤 대회",
"participants": [
{
"id": 1,
"name": "강감찬"
},
{
"id": 2,
"name": "야율융서"
}
]
},
{
"id": 2,
"title": "2차 봉산탈춤 대회",
"participants": [
{
"id": 3,
"name": "성종"
},
{
"id": 4,
"name": "강조"
}
]
}
]
ContestEntity만 조회하였고 participant 는 Lazy 조회를 하였기 때문에 조회가 안될 것으로 예상했으나
조회가 되며 심지어 ContestEntity의 갯수 N만큼 N번 조회를 한다!
이것이 N+1의 문제이다.
실제로 조회된 쿼리 로그는 아래와 같다
contest 가 2개 이기 때문에 participant 가 2번 조회된다.
Hibernate:
select
ce1_0.id,
ce1_0.title
from
contest ce1_0
Hibernate:
select
p1_0.participant_id,
p1_0.id,
p1_0.name
from
participant p1_0
where
p1_0.participant_id=?
2024-03-13T17:21:39.470+09:00 TRACE 21476 --- [TestProject] [nio-8080-exec-1] org.hibernate.orm.jdbc.bind : binding parameter (1:BIGINT) <- [1]
Hibernate:
select
p1_0.participant_id,
p1_0.id,
p1_0.name
from
participant p1_0
where
p1_0.participant_id=?
2024-03-13T17:21:39.473+09:00 TRACE 21476 --- [TestProject] [nio-8080-exec-1] org.hibernate.orm.jdbc.bind : binding parameter (1:BIGINT) <- [2]
N+1이 뭔지는 알았으니 이제 무엇 때문에 나는지 알아보자
그럴러면 연관관계에 대해 알아야 한다.
연관 관계란 무엇인가?? 아래 글에서 계속
JoinColumn 과 연관 관계의 주인이란?
JoinColumn이란 JoinColumn이란 엔티티 간의 관계를 매핑할 때 사용하는 어노테이션이다. 관계가 외래키 (foreign key)로 묶여있는 엔티티 간의 참조 관계를 나타내준다. 여기에서 각 테이블은 연관 관
dogfootsleep.tistory.com