[ PROJECT ]/PetHarmony

[🚨 TroubleShooting] N+1이 뭔데....

김강니 2024. 9. 13. 03:00

SNS에서 개발자 신입들이 N+1문제 일으키기 쉽다고 해서 혹시 내 코드도..?

슬쩍 챗지피티한테 물어봤는데 문제가 꽤나 있다네...ㅋㅋ

 

그래서 코드 고쳐보려고 합니다......

우선 n+1이 뭐냐면

 

N+1문제?

데이터베이스ORM(Object-Relational Mapping) 사용 시 자주 발생하는 성능 문제 중 하나입니다. 특히 JPAHibernate와 같은 ORM을 사용할 때 자주 발생한다.

 

N+1 문제는 기본적으로 1개의 쿼리가 추가로 N개의 쿼리를 발생시키는 비효율적인 상황을 의미한다. 예를 들어, A 테이블과 B 테이블이 관계를 맺고 있을 때, A 테이블에서 데이터를 조회한 후, 관련된 B 테이블의 데이터를 가져오려고 하면 추가 쿼리가 여러 번 발생할 수 있음...

이렇게 쿼리 실행이 반복되면서 성능이 안좋아진다.

 

⬇️⬇️ ORM 설명 ⬇️ ⬇️ 

더보기

ORM(Object-Relational Mapping)

객체와 데이터베이스 테이블 간의 매핑을 자동으로 처리해주는 기술이다. 개발자가 객체 지향적으로 코드를 작성하면 ORM이 이를 기반으로 데이터베이스와 상호작용할 수 있게 해준다.

ORM을 사용하면 SQL을 직접 작성하지 않아도 데이터베이스와 통신할 수 있어서 생산성이 높아지는 장점이 있다.

대표적으로 JPAHibernate가 있다.

 

ORM을 사용하면 객체 지향적인 코드 작성이 가능해지고, 데이터베이스의 테이블 구조를 객체 모델로 매핑하여 작업할 수 있다. 하지만 이로 인해 N+1 문제와 같은 성능 문제가 발생할 수 있다. 기본적으로 ORM은 데이터를 자동으로 매핑해주지만, 잘못된 설계나 쿼리 작성으로 인해 쿼리가 비효율적으로 실행될 수 있기 때문이다.

   ↕

MyBatis

SQL 매퍼로, SQL을 직접 작성하고 그 결과를 객체와 매핑하는 방식이다. ORM과 달리 개발자가 SQL을 직접 제어할 수 있어 복잡한 쿼리나 성능 최적화에 유리하다. 하지만 SQL을 일일이 작성해야 하므로 유지보수나 생산성 면에서 부담이 될 수 있다.

 

📖 정리

ORM의 장점: 객체 지향적인 개발 방식, 생산성 향상, 자동화된 쿼리 처리.

MyBatis의 장점: SQL 제어 가능, 복잡한 쿼리 최적화 가능, 성능 튜닝 용이.

 

그래서 내 코드는 어디가 문제였냐면(사실 N+1 문제가 아니었음)

게시물 리스트를 한 번에 가져오는 것처럼 보였지만, DTO 변환 과정에서 추가적인 쿼리가 발생하면서 성능 저하가 생긴 것임.....

 

기존에 작성한 게시물 리스트 조회 메소드

여기에서는 boardPage는 쿼리한방으로 가져오는데 이 boardPage를 DTO로 변환하는 메소드에서...

 

이 buildBoardListResponseDTO에서 각 게시물의 이미지 유무를 알기위해 existsByBoard_BoardId가 총 10개의 게시물을 가져온다고 치면 10번 실행되는거....

 

긍께 게시물 리스트를 가져오기 위해 한 번의 쿼리가 실행된 후, 각 게시물에 대해 다시 N번의 쿼리가 발생하여 여러 번의 쿼리가 실행...

 

근데 다시 찾아보니까 이건 N+1문제가 아니고 그냥 비효율적인 쿼리 실행 문제였음..ㅋㅋ

 

그래서 내 코드는 진짜로 어디가 N+1 문제였냐면

N+1문제는 부모테이블에서 자식테이블을 oneToMany로 설정해야 일어나는 문제인건가..? 했는데 리스트에서 여러 자식 엔티티를 조회할 때, 각 자식 엔티티가 부모 엔티티를 Lazy Loading 방식으로 가져온다면 N+1 문제가 발생한다고 함..

 

만약에 100개의 댓글을 조회할때 comment테이블 100번 + board테이블 100번 쿼리가 실행된다.

 

지금 comment에서 board랑 user를 참조하고 있음.

그래서 comment리스트를 불러오는 코드를 실행해보니까 진짜 user테이블을 반복해서 참조하고 있었음...

 

 

그래서 댓글 리스트 불러오는걸 Fetch Join으로 바꿔서 실행했더니

아래가 기존에 사용한 메소드

 

데이터가 적어서 시간이 진짜로 단축된건가 싶지만 암튼 단축됐음..!!

그리고 쿼리문이 반복실행되지않고 서브쿼리로 실행됐다!!

 

 

근데 또 서브쿼리가 엄청 길어지길래 이게 좋은게 맞나싶어서.,,,,

 

Fetch Join을 통한 서브쿼리의 장점:

1. 성능 최적화: 한 번의 쿼리로 여러 테이블을 조인하여 데이터를 가져오기 때문에, 다수의 쿼리가 발생하는 N+1 문제를 해결할 수 있습니다.

2. 효율적인 데이터 로딩: 한 번에 필요한 데이터를 모두 로드하여 성능을 크게 개선할 수 있습니다. 특히 대규모 데이터 조회 시 유리합니다.

 

Fetch Join을 통한 서브쿼리의 단점:

1. 쿼리 복잡성 증가: 서브쿼리가 길어지면 쿼리의 복잡도가 올라가고 가독성이 떨어집니다. 이는 나중에 다른 개발자나 자신이 코드를 다시 볼 때 어려움을 줄 수 있습니다.

2. Join의 제약: JPA의 Fetch Join은 제약이 있습니다. 예를 들어, Fetch Join으로 페이징을 할 경우 문제가 발생할 수 있습니다.

3. 데이터 중복: 여러 연관된 엔티티를 조인할 때, 결과에서 데이터가 중복되거나 예상하지 못한 결과를 가져올 수 있습니다.

 

암튼 목표에 따라서 적절하게 사용해야함!

Fetch Join말고 Batch Fetching 또는 DTO Projection도 있다는데 이건 다음에 알아보도록 하지....