본문 바로가기

[Project] 프로젝트 삽질기44 (feat limit, take)

어가며

NestJS와 TypeORM을 활용하여 프로덕트를 만들고 있습니다. TypeORM의 QueryBuilder를 주로 사용하는데요. 데이터 조회 쿼리를 구성하면서 테이블 join을 하는 과정에서 limit을 사용했더니, 원하는 데이터를 제대로 조회하지 못하는 문제를 발견했습니다. 어디서, 왜 이런 문제가 발생했는지 탐구하고 싶어 이 글을 적습니다. 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

no offset 페이지네이션

API를 개발하면서, 성능을 개선시키기 위해 쿼리 실행 계획을 분석하면서 코드를 작성하고 있습니다. 기능 중, 유저 포인트 내역을 조회하는 기능의 성능 테스트를 위해 데이터베이스에 5만 개의 시드 데이터를 넣고 쿼리 실행 계획을 분석했습니다.

 

 

 

쿼리 실행계획을 보면서 offset을 활용하는 로직이 성능 상의 비효율을 내고 있다는 것을 파악했습니다. 그럼 왜 offset를 사용하는 것이 문제인지 파악해야 했습니다.

 

 

출처: https://jojoldu.tistory.com/528

 

 

 

offset을 활용하면 예를 들어 offset 10000, limit 20이라고 했을 때 최종적으로 10,020개의 행을 읽어야 한다는 것을 알 수 있었습니다. offset을 활용하지 않으면서도 페이징 처리를 할 수 있도록 설계해야 했습니다. 저는 효율적인 페이징 처리를 위해 offset을 활용하는 것이 아닌, 인덱스를 활용하는 것을 선택했습니다.

 

 

 

 

WHERE의 AND 조건으로 point_id가 특정 값보다 작은 값들을 가져오게끔 설정했습니다. point_id는 PK이므로 인덱스 적용이 가능합니다. WHERE 조건에 user_id와 point_id 칼럼을 조회 조건으로 구성함으로써 offset을 적용했을 때 보다 더욱 빠르게 데이터를 가져올 수 있었습니다. 

 

no offset을 활용하여 페이징 처리를 하면서, 다른 비즈니스 로직에서 leftJoinAndSelect를 사용하는 쿼리문을 추가로 작성했습니다. 쿼리문을 작성할 때 LIMIT을 활용했는데, 여기서 문제가 발생했습니다.

 

 

 dataSource
      .getRepository(Post)
      .createQueryBuilder('posts')
      .leftJoinAndSelect('posts.PostImgs', 'postImgs')
      .orderBy('posts.createdAt', 'DESC')
      .addOrderBy('postImgs.seq', 'ASC')
      .limit(limit);

    if (categoryId) {
      queryBuilder.where('posts.category_id =:categoryId', {
        categoryId,
      });
    }

 

 

위와 같은 코드를 구성하는 과정에서, postImgs에서 가져오는 사진 데이터가 일부 누락되어서 가져오는 문제를 겪었습니다. 왜 이 문제를 겪는지 살펴보니, TypeORM의 limit 동작 방식 때문에 문제를 겪었다는 것을 알 수 있었습니다. limit을 사용하면, select을 할 때 limit을 먼저 실행하고, Join을 하기 때문에, 원하는 데이터를 모두 가져올 수 없다는 문제가 있었습니다. 

 

문제 해결을 위해 take를 사용했습니다. take를 활용하여 먼저 테이블 join을 해서 데이터를 읽어온 후 그 후에 페이징 처리를 하는 방식이었습니다. 

 

문제를 해결하며, typeorm의 take와 limit이 어떤 점이 다른지 알 수 있었던 좋은 기회였습니다. 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

마치며

공부를 하며 그동안 아주 기초적인 것들을 제대로 공부하지 않았다는 것을 깨닫습니다. 좋은 기술을 배우는 것도 좋지만, 기술의 기반이 되는 기초적인 지식을 먼저 쌓는 것이 중요하다는 것을 다시금 깨달았습니다. 기초가 튼튼한 개발자가 되고 싶습니다.

 

 

 

 

 

 

 


 

 

 

출처

 

No sub-select or limit on left join possible · Issue #457 · typeorm/typeorm

It doesn't seem possible to make sub select let query = db.getRepository(User) .createQueryBuilder('u') .where('u.email = :email') .setParameter('email', username) .andW...

github.com

 

1. 페이징 성능 개선하기 - No Offset 사용하기

일반적인 웹 서비스에서 페이징은 아주 흔하게 사용되는 기능입니다. 그래서 웹 백엔드 개발자분들은 기본적인 구현 방법을 다들 필수로 익히시는데요. 다만, 그렇게 기초적인 페이징 구현 방

jojoldu.tistory.com