본문 바로가기

spring

[MileStone 프로젝트] EAGER // LAZY (로딩 전략)

최근에 진행한 쇼핑몰 프로젝트 MileStone에서, 저는 커뮤니티 게시판을 구현하던 중 예상치 못한 성능 문제에 직면했습니다. 이 문제는 Hibernate의 로딩 전략에 대한 이해를 다시금 깊게 만드는 계기가 되었고, 이를 통해 해결 방법을 찾게 되었습니다. 이번 포스팅에서는 EAGER와 LAZY 로딩 전략의 차이와, 이 프로젝트에서의 문제 해결 과정 정리하기 위해 해당 글을 작성하려 합니당

 

MileStone 프로젝트와 커뮤니티 게시판

 

MileStone은 다양한 제품을 판매하는 쇼핑몰 플랫폼으로, 사용자들 간의 활발한 소통을 위해 커뮤니티 게시판을 도입했습니다. 이 커뮤니티 게시판에서는 스프링으로 CRUD 구현 및 페이징 처리기능과 조회수 등등 한 번 도전해보고 싶어, 계획하였습니다

 

프로젝트 초기에는 빠른 개발을 위해 Hibernate의 EAGER 로딩을 기본으로 사용했습니다. 이는 관계된 모든 데이터를 한 번에 가져오는 방식으로, 간단한 구현을 가능하게 해주었지만, 엔티티가 많아지고 데이터가 쌓이면서 성능 문제가 발생하기 시작했습니다.

 

EAGER 로딩이란?

EAGER 로딩은 엔티티가 로드될 때 연관된 모든 엔티티를 즉시 로드하는 전략입니다. 예를 들어, 게시물을 로드할 때 해당 게시물에 달린 모든 댓글과 댓글 작성자 정보도 함께 로드됩니다.

@Entity
public class Board {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String title;

    @OneToMany(mappedBy = "board", fetch = FetchType.EAGER)
    private List<Comment> comments = new ArrayList<>();
}​

 

위의 코드에서 Board 엔티티가 로드될 때, 해당 게시물에 달린 모든 Comment 엔티티가 즉시 로드됩니다. 초기 개발 단계에서는 이 전략이 편리했지만, 문제는 데이터가 많아지면서 발생했습니다.

 

MileStone 프로젝트에서의 문제 발생

프로젝트가 발전하면서 게시물의 수가 늘어나고, 각 게시물마다 달린 댓글과 그 댓글에 대한 작성자 정보까지 한 번에 로드하려다 보니, 성능이 급격히 저하되었습니다. 특히, 커뮤니티 게시판에서 게시물 목록을 조회할 때, 필요하지 않은 정보까지 모두 로드하게 되어 데이터베이스 부하가 심각해졌습니다.

 

N+1 문제

이 문제의 핵심은 바로 N+1 문제였습니다. EAGER 로딩을 사용하면 게시물 1개를 조회할 때, 그와 연관된 댓글과 작성자 정보를 각각 추가로 로드하기 때문에 게시물의 수가 많아질수록 쿼리가 기하급수적으로 증가하게 됩니다. 이로 인해 성능이 저하되고, 사용자 경험에도 악영향을 미치게 되었습니다.

 

public Page<MenuBoardDTO> getBoards(int page, int size, String sort) {
    Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt"));
    return boardRepository.findAll(pageable).map(Board::toMenuDTO);
}

 

Hibernate: select b1_0.board_id, b1_0.title, b1_0.created_at from boards b1_0 order by b1_0.created_at limit ?,?
Hibernate: select c1_0.comment_id, c1_0.content from comments c1_0 where c1_0.board_id=?

 

해당 메서드를 실행했을 때, Hibernate는 다음과 같은 쿼리를 생성하였고 만약 게시물을 10개를 조회할 때,  추가로 댓글에 대한 쿼리가 10번씩 발생하게 되므로, 성능이 크게 저하가 될 수 있다는 사실을 깨달았습니다.

 

 

 

 

 

EAGER -> LAZY 로딩 적용

 

 

LAZY 로딩을 통한 문제 해결

이 문제를 해결하기 위해, EAGER 로딩을 LAZY 로딩으로 전환했습니다. LAZY 로딩은 연관된 엔티티가 실제로 필요할 때까지 로드를 지연시키기 때문에, 불필요한 데이터 로드를 막을 수 있습니다.

 
@OneToMany(mappedBy = "board", fetch = FetchType.LAZY)
private List<Comment> comments = new ArrayList<>();

 

 

성능개선 확인

 

Board 엔티티에 LAZY 로딩을 적용함으로써, 게시물 목록을 조회할 때 불필요한 댓글 로드가 발생하지 않게 되었고, 성능이 크게 개선되었습니다. 이전과 달리 지금은 LAZY 지연로딩 설정으로 인해 댓글을 필요로 하는 상황에서는 댓글 필드에 접근할 때만 쿼리가 발생하도록 마무리 되었습니다

Hibernate: select b1_0.board_id,b1_0.board_content,b1_0.created_at,b1_0.member_id,m1_0.member_id,m1_0.address_detail,m1_0.registration_date,m1_0.status,m1_0.user_address,m1_0.user_email,m1_0.user_name,m1_0.user_password,m1_0.user_phone_num,b1_0.board_title,b1_0.board_viewcount from boards b1_0 join members m1_0 on m1_0.member_id=b1_0.member_id order by b1_0.created_at limit ?,?

 

실제 쿼리를 비교해보니 커뮤니티 게시판에서 게시물 목록을 조회할 때 발생하는 쿼리 로그를 확인한 결과, 더 이상 불필요한 댓글 로드가 발생하지 않는 것을 확인하였고 게시물 목록을 조회할 때는 게시물 정보만 찍어내고, 댓글 정보는 사용자가 특정 게시물을 클릭하여 자세히 볼 때 참조가 되도록 최적화가 되었습니다

 

마무리

 

 

이번 MileStone 프로젝트에서 커뮤니티 게시판을 구현하면서 EAGER와 LAZY 로딩 전략의 중요성을 다시 한번 깨닫게 되었습니다. 초기 개발 단계에서 편리함을 위해 사용했던 EAGER 로딩이 성능 문제를 야기했고, 이를 LAZY 로딩으로 전환함으로써 문제를 해결할 수 있었습니다.