티스토리 뷰

728x90

회사 코드를 보던 중 연관관계 되어 있지 않은 엔티티를 보게 되었는데

이 두 엔티티를 통해 작업을 해야하는 상황이 발생하여 글을 작성하게 되었다.

 

어떻게 하면 데이터 4000건을 불러오는데 1분이 넘는 시간이 걸리며 그로 인한 "아웃오브메모리"문제를 해결할 수 있을까?

 

++ 22.08.19 추가 : Projection을 사용하여 해당 Tuple 문제를 해결할 수 있었음

 

1.  아웃오브메모리 문제 발생


아웃오브메모리가 발생한 이유.

해당 페이지의 데이터를 불러오는데 연관관계 매핑이 맺혀 있지 않았다.

아무래도 추후에 개발된 페이지이거나, 설계할 때 기획에서 빠져있었지 않았다 짐작해 본다.

 

아무튼 이러한 연관관계없는 테이블을 억지 매핑을 하기 위해

이중 for문을 돌려놓고, @setter와 @getter를 활용하며 값을 넣어주고 있었고,

Querydsl에서 필요 없는 join을 걸어놔서 전체탐색을 하는 바람에 

페이지의 4000건을 불러오는데 1분이 넘는 시간이 걸려 운영에서 "아웃오브메모리"가 발생했다.

 

어떻게 하면 성능을 끌어올릴 수 있을까?

728x90

1-1 연관관계가 맺혀있지 않은 예시


아래와 같은 2가지의 테이블이 존재하며 문제가 발생했다고 가정한다.

1. 토론후기 테이블과 마일리지 테이블이 존재한다.

2. 토론후기와 마일리지 테이블은 매핑(연관관계 x) 되어있지 않다.

3. 토론후기를 작성한 후 웹서버에서 관리자가 마일리지를 지급하면 마일리지 테이블의 fkey 칼럼에 값이 생성된다.

 

즉 토론후기의 id(review_id)가 마일리지의 fkey에 존재유무를 확인하여 

 

있을 경우 마일리지 지급

없을 겨우 마일리지 미지급

 

을 나타낸다.

 

 

*참고 이미지

 

토론후기 테이블

마일리지 테이블

 

 

 

2. 문제 해결


문제해결을 위해 나는

두 테이블 전체 조회 후 프런트 단에서 id값과 fkey 값 비교를 택했고, 리팩토링 과정에서 Querydsl을 이용한 연관관계없는 엔티티 외부조인 방법을 택했다.

 

1. 프런트 단에서 토론후기 id값과  마일리지 fkey값 비교 방법

아래의 로직 흐름대로 각각의 reviews, points 데이터를 가지고 온다.

ModelAndView를 사용해서 setViewName에 정의된 "admin/debate-reviews/reviews 파일로 "points"와"reviews"를 보낸다.

⬇️MainController

@GetMapping("/debate-review/reviews")
public ModelAndView getReviews(ModelAndView mav) {
   List<DebateReview> reviews = debateReviewDao.getReviewsByYearMonthDescForAdmin();

   List<PointHistory> points = pointHistoryDao.findPointType();

   mav.addObject("points", points);
   mav.addObject("reviews", reviews);
   mav.setViewName("admin/debate-reviews/reviews");
   return mav;
}

 

DebateReviewDaoCustom

List<DebateReview> getReviewsByYearMonthDescForAdmin();

DebateReviewDaoCustomImpl

public List<DebateReview> getReviewsByYearMonthDescForAdmin() {
   return queryFactory.selectFrom(debateReview)
         .where(debateReview.status.ne(ArticleStatus.DELETE))
         .orderBy(debateReview.createdAt.desc())
         .fetch();
}

 

PointHistoryDaoCustom

List<PointHistory> findPointType();

PointHistoryDaoCutomImpl

public List<PointHistory> findPointType() {
        return queryFactory.selectFrom(pointHistory)
                .fetch();
    }

프론트 단(controller에서 points와 reviews를 전달)

keyList = [];
/*[# th:each="point : ${points}"]*/
var fkey = /*[[${point.fkey}]]*/"";
keyList.push(fkey);

/*[/]*/

/*[# th:each="review : ${reviews}"]*/
if (keyList.includes(id)) {
    pointHistory += "지급";
} else {
    pointHistory += "심사중";
}
/*[/]*/

keyList =[]를 생성한 다음 프런트로 전달된 forEach를 사용해 points를 풀어 point의 fkey keyList=[ ]에 담는다.

위와 동일한 방법으로 reviews를 풀고

keyList = []에 담긴 값과 review의 id가 존재하면 "지급", 존재하지 않는다면 "심사 중" 데이터를 담아준다. 

 

2. queryDsl을 활용한 방법

앞단의 내용은 생략하고 queryDsl만 다루겠다.

@Override
public List<MileageCheckDto> getClubsByYearMonthAndKeyword() {
   List<MileageCheckDto> result = queryFactory
         .select(new QMileageCheckDto(
         	debateReview.id,
            debateReview.writer,
            debateReview.oneLine,
            pointHistory.fkey
            ))
         .from(debateReview)
         .leftJoin(pointHistory).on(debateReview.id.eq(pointHistory.fkey)))
         .fetch();


   return result;
}

토론후기 테이블과 마일리지 테이블의

연관관계없는 엔티티 외부조인에 대한 Querydsl이다.

 

하나하나 설명을 하자면

1. @QueryProjection을 활용하여 엔티티가 아닌 DTO에 세팅한 값을 넘겨주었다. 

ㄴ @QueryProjection을 사용한 이유는 여기서 더 자세히 다룬다. https://study-easy-coding.tistory.com/142 

2. from(debateReview) -> 기준이 될 테이블이다. 쉽게 말해 pointHistory가 존재하지 않더라도 debateReview의 값은 가져온다.

3. leftJoin(pointHistory) -> join을 할 건데 

4.. on(debateReview.id.eq(pointHistory.fkey))) -> debateReview.id와 pointHistory.fkey가 같으면 join을 할 거다.

5.. fetch() -> 리스트로 결과를 반환하는 방법이다. 만약에 데이터가 없으면 빈 리스트를 반환해 준다.

 

이렇게 하면 결괏값으로 DTO에 담긴 값을 List 타입으로 리턴해주게 된다.

.

.

.

 

애초에 테이블 설계를 잘했다면... feat 연관관계 매핑이었다면...

List<Tuple> result = queryFactory
       .select(debateReview, pointHistory)
       .from(debateReview)
       .Join(debateReview.pointHistory, pointHistory)
       .fetch();
       return result;

 

 

 

 

 

혹시 더 좋은 방법이 있다면 댓글로 피드백 부탁드리겠습니다!

728x90
댓글
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday