스프링 공부/ChatUniv

[ChatUniv] No Offset?이게 왜 좋을까?

장아장 2023. 9. 29. 19:44

페이징을 최적화 시키기 위한 방법이 무엇일까를 이야기하게 되었다. 

근데, 이전에 페이징을 왜 최적화 시켜야할까? 라는 생각이 들었다. 

우리는 JPA에서 offset, limit를 이용해 페이징을 만들 수 있고, 이를 통해 우리가 데이터베이스에서 모든 데이터를 리스트로 받아오고 연산할 필요가 없는데?

 

이에 대한 내용은, 실제 offset limit을 어떻게 두는지에 따라 달라진다. 

우리가 어떤 쿼리를 쓸까? 실제 pageable를 이용하면 어떤 방식의 쿼리가 나올까?

두 개의 다른 프로젝트의 코드지만, 비교를 위해 가져왔다.

@Override
    public Page<MessageResponseDto> searchMessage(MessageSearchRequestDto messageSearchRequestDto) {
        Pageable pageable = PageRequest.of(messageSearchRequestDto.getPage(), messageSearchRequestDto.getSize());
        QueryResults<MessageResponseDto> result = jpaQueryFactory
                .select(new QMessageResponseDto(message.id, message.content.content, message.receiver.nickname.nickname,
                        message.sender.nickname.nickname, message.createdDate))
                .from(message)
                .where(checkReadCondition(messageSearchRequestDto, messageSearchRequestDto.getReadMessageType()),
                        checkSearchCondition(messageSearchRequestDto, messageSearchRequestDto.getSearchMessageType()))
                .orderBy(message.createdDate.desc())
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetchResults();
        return new PageImpl<>(result.getResults(), pageable, result.getTotal());
    }

이런 코드가 있을 때, 우리는 아무 이상 없이 페이징이 된다고 느낀다. 

왜냐고? offset, limit를 이용해서 offset을 이용한 시작 인덱스, limit를 이용한 페이지당 반환할 개수를 알려주니까!

 

하지만, 여기에서 나는 최적화되지 못하는 이유가 있다. 

 

그러면 이번 페이지 이전의 데이터들은 어떤 방식으로 처리될까?

 

 

offset이전의 페이지들은 다 무시될까? 아니다. 

이전 페이지들에서도, 이 offset을 구하기 위해 이전 페이지에 들어가는 애들인지 아닌지 확인하고 다 넘겨주어야 한다.

(안읽는 책들을 한장씩 다 넘겨야된다...)

 

나니아 연대기 책(겁나 두껍다)를 어제 1004페이지까지 읽었는데, 오늘 이전에 읽은 부분부터 다시 읽는데, 우리가 1003페이지까지 다 눈으로 보면서 넘어가야 하나? 

절대 아니다. 이전 페이지는 버린다.

 

이 버린다는 개념을 No Offset에 적용시켜서 최적화를 하게 되었다. 

어떻게 할까?

일단 limit는 동일하게 사용한다. 한 페이지에 몇개만 반환하면 될 지는 정해져 있다. 

그렇다면, 이번 페이지에 들어갈 데이터가 무엇인지는 어디서 설정할까?

 

where에서 아예 offset, limit를 생각하지 않게 버렸다. 

@Override
    public List<CommentPagingResponse> findComments(final Long pageSize, final Long boardId, final Long commentId) {
        return jpaQueryFactory
                .select(Projections.constructor(CommentPagingResponse.class,
                        boardComment.id,
                        boardComment.content))
                .from(boardComment)
                .where(checkBoardId(boardId),
                        (ltCommentId(commentId)))
                .orderBy(boardComment.id.desc())
                .limit(pageSize)
                .fetch();
    }

 

이번엔 No offset이 적용된 프로젝트의 코드를 가져왔다. 

여기에서 모든 코멘트는 id의 역순으로 반환시킨다. 

그렇다면? 이번 페이지에서 반환할 첫 id의 이전은 전부 where로 치워버리는 것이다. 

ltCommentId라는 로직을 이용해, 이번 페이지의 첫 코멘트 id를 반환해준다. 

 

[--1][--2][--3][--4][--5][--6][--7][--8][--9][--10][--11][--12][--13][--14][--15]....
[이전 페이지니까 이건 추가하지 말아야지~~~~~~~~~~~~~~~~~~~~][여기는 페이지에 넣어야지~~~~~~~~~~~]


[--11][--12][--13][--14][--15]....
[여기서부터 limit만 담으면 끝!!!! ]

이런 구조의 차이가 존재할 것이다. 

결국 where에서 우리가 페이징 처리해야할 표본을 전체를 받아오고, 여기서 페이지를 구분짓는것보단, 

페이지를 구분짓기 시작하는 부분부터만 가져와서 한 페이지만큼 짤라서 보내주는게 더 효율적이니깐

 

만약 한 페이지에 100개의 데이터가 들어가는데, 내가 1201페이지 요청을 받았다면?

이제 슬슬 차이가 날 것이다. 

 

이걸 위해서 단순 offset, limit가 아닌, 이전 요청한 페이지의 마지막 id와 limit(페이지당 데이터 수)를 입력으로 받게 된다. 

이를 통해, 데이터를 조회할 때, 해당 id 다음의 데이터들 중에 limit개수만큼만 뽑아내면 된다. 

 

이렇게만 보면 참 좋은데, 이 방식의 단점도 존재한다. 

 

바로 3페이지 띄워주세요!!!!!! 

하면 이전 id를 알 수 없다. 

 

이런 문제 때문에, 실제 아래에 페이지가 있어, 이를 통한 이동이 아닌, 다음 페이지와 이전 페이지로 처리할 수 있는 로직에서만 이야기한 로직이 동작할 수 있다. 

 

이제 일반 페이지에 대한 No Offset 방법에 대해서 찾아봐야겠다. 

더 공부해서 이 컨텐츠도 속편이 나올 수 있도록 해야겠다.

 

그럼...twenty thousand...🔥

 

'스프링 공부 > ChatUniv' 카테고리의 다른 글

[ChatUniv] KOMORAN을 이용한 자연어 처리  (0) 2023.10.01