본문 바로가기
기술/Spring-Boot

JPA에서의 페이징 기법, 빠를까?

by Zabee52 2021. 12. 23.

unique record의 Paging과 n번의 find

코드를 개선하려고 살펴보던 중 궁금한 점이 생겼다.

MySQL DB에 페이징을 시도할 때, findAll은 limit에 해당하는 레코드의 수를 탐색하고나면 그 즉시 중단할까? 아니면 전부 탐색하고 정해진 데이터 개수만 잘라서 출력해주는 것일까?

 

결과 먼저 말하자면, 전부 탐색하고 데이터 개수만 잘라서 출력해준다. 지극히 비효율적이다. 그리고 느리다. 아래는 실험 후 찾아본 자료이다.

 

MySQL LIMIT 최적화(feat. 구글이 검색결과를 최대 1,000건만 제공하는 이유)

제가 말하는 것이 사실도, 정답도 아닐 수 있으니 비판적으로 읽어주시면 감사하겠습니다. MySQL LIMIT 최적화하는 방법 MySQL에서 페이징 처리를 위하여 LIMIT 키워드를 제공한다. 오라클에서는 페이

jeong-pro.tistory.com

 

실험 방식은 다음과 같다.

대상 레코드 : username, nickname - 둘 다 unique 설정이 되어있다.

이와 같은 상황이라면,

findAllByUsernameOrNickname(username, nickname)

다음 쿼리를 시행했을 때 나오는 데이터는 반드시 2개 이하이다.

그러므로,

List<User> user = findAllByUsernameOrNickname(username, nickname)
Page<User> user = findAllByUsernameOrNickname(username, nickname, PageRequest.of(0, 2))

둘의 속도를 비교하면 알 수 있게 될 것이다.

 

실험을 위해 MySQL을 준비.. 하려고 했으나 사실 못 했다. 이 실험을 위해 로컬 환경에 MySQL을 설치하는게 귀찮았고, RDS를 설정해서 하는 건 돈이 아까웠다.

그래서 H2-Console에서 시행했다. 어차피 비슷한 결과가 나올 것이라고 생각한다.

@loop 1000000 insert into user(username, nickname, password) values(concat('user',?), concat('nickname',?), '1234') ;

H2-Console에 100만개의 레코드를 넣고 시간을 비교해봤다. 루프문 명령어는 아래의 문서를 통해 알아냈다.

 

Tutorial

  Tutorial Starting and Using the H2 Console Special H2 Console Syntax Settings of the H2 Console Connecting to a Database using JDBC Creating New Databases Using the Server Using Hibernate Using TopLink and Glassfish Using EclipseLink Using Apache Active

www.h2database.com

1회차
paging : 388
none-paging : 335

벌써부터 뭔가 싸늘하다.

2회차
paging : 385
none-paging : 347

알고보니 순서때문이 아닐까? 하는 생각에 순서를 바꿔서 진행해봤다.

3회차
none-paging : 358
paging : 395
4회차
none-paging : 323
paging : 349

 

더이상의 실험은 무의미했다. 페이징이 오히려 느리다.

여기서 얻을 수 있는 결과는

 

1. 페이징은 Full-Scan을 한 뒤 n개의 레코드만 잘라서 내보내준다. (H2-Console 환경이긴 하지만 MySQL도 다르진 않을 것이다.)

2. 게다가 추가적인 어떤 시간의 소모(아마도 정렬 알고리즘)가 있다.

 

결국 findAll()로 인해 너무 많은 데이터가 오고가는 그 부하를 제거할 수 있는 점을 제외하면 이득이 없는 기능인 것이다.

제대로 알지도 못 하고 그동안 이거 좋은듯 좋아라 하면서 사용했던 나는 반성을 해 본다.

 

생각해보면 Full-Scan 하는게 당연하다. 페이징에 기본적으로 달려있는 정렬 기능을 시행하려면 Full-Scan을 해야하는게 맞는 것이다. 조금만 생각해보면 되는 것을.. 허허

 

 

그러면 또 실험해보고싶은게 있다.

단순 데이터 두 번 불러오는게 그러면 더 빠르지 않을까?

Optional<User> found = userRepository.findByUsername(username);
Optional<User> found2 = userRepository.findByNickname(nickname);

이미 사용하던 코드가 있어서, 그냥 핸디캡 주는셈 치고 Optional도 씌워줬다.

 

그럼 시간측정 들어간다.

1회차
2 times call : 37

아니 그냥 2회차 비교가 필요가 없다.. 말 할 것도 없이 훨씬 빠르다.

이미 수량이 제한되어있는 데이터를 받아오는 상황에선 findBy를 2번 쓰는게 나은 것이다. 추측하건대 두 번째 데이터는 찾아오는데 걸리는 시간이 1이지 않았을까 싶다. 그래. 그런 것이다. 난 배웠다.

 

이번 실험으로 얻은 교훈은...

findAll 사용은 신중하게 하자!

댓글