개발일기

겉핥기 달인이었던 주니어 개발자의 QueryDSL을 통한 성능 개선 후기

코딩하는 배달이 2022. 3. 6. 17:50

여느 때와 같이 재택근무 중이던 평온한 오후에 프론트 쪽에서 슬랙이 날라왔다.

 

 

웬 타임아웃 에러?

 

단순히 유저 전체 리스트 조회하는 호출이였는데, 코드를 뜯어 보니

 

List<UserResponse> allResponseList = oliveUserService.findAllResponseList();
        for (UserResponse userResponse : allResponseList) {
            Long id = userResponse.getId();
            User user = userRepository.findByOliveUserId(id);
            if (user != null) {
                userResponse.setProvider(user.getProvider());
                userResponse.setProviderId(user.getProviderId());
            }

 

요약하면 유저 테이블에서 전체 리스트를 가져온 후, 다른 테이블의 컬럼을 response에 추가하는 작업이였는데

일일이 유저 한명의 아이디로 찾아서 쿼리를 날리고 있었다. (지금 보면 성능을 아예 염두조차 안한 무식한 코드이지만..) 심지어 컨트롤러 단에서 처리를 하고 있다.

 

물론 dev 환경에서는 데이터가 별로 없어서 에러가 나지 않지만, 이런 방식의 문제점은

  • 운영 환경처럼 데이터가 많을 경우 유저 한명당 일일이 쿼리를 날리기 때문에 비효율적이다.
  • JPA 방식이 서툴었기 때문에 mysql에서는 join문으로 처리할 수 있다는 걸 알았지만, 스프링에서 단순히 코드레벨에서 처리하고 있다.

 

처음에는 성능 문제인 줄 모르고 null check error로 착각을 해서, Optional로 바꿔줬다.

 

Optional<User> user = userRepository.findByOliveUserId(id);
            if (user.isPresent()) {
                userResponse.setProvider(user.get().getProvider());
                userResponse.setProviderId(user.get().getProviderId());

 

사실 똑같은 코드다.

 

같은 에러가 난다는 피드백을 받고 나서, 로컬에서 DB를 바꾸고 테스트를 한 후에야 성능문제인걸 알았다. 504에러는 게이트웨이 타임 아웃 에러다.

 

스프링에서는 객체지향 언어와 관계형 데이터베이스의 간극을 줄이기 위해 JPA 인터페이스를 제공하는데, JPQL로 쿼리를 작성하면 테이블을 join해서 데이터 row를 가져올 수 있다. 하지만 SQL, JPQL 모두 문자열이기 때문에, 에러가 날 경우 컴파일 단계가 아닌 애플리케이션 로딩 시점에서 발견할 수 있다. 따라서 스프링 기반 프로젝트에서는 QueryDSL을 제공한다.

 

QueryDSL

  • 쿼리를 자바코드로 작성할 수 있게 도와주는 기술이다.
  • Spring Data JPA로 해결하지 못하는 복잡한 쿼리/동적 쿼리를 해결할 수 있다.
  • 자바코드로 작성하기 때문에 문법오류를 컴파일 시점에 잡아낼 수 있다.

QueryDSL에서는 전용 객체 Entity를 생성하고, 쿼리를 코드로 작성할 수 있다. 장점은 빌드 시점에 IDE의 도움을 받을 수 있어서 실수를 줄일 수 있다는 점이다. 

 

QueryDSL은 객체지향적 관점에서 JAVA 이다. 중복코드를 함수로 extract 할 수 있다.

 

QOliveUser.oliveUser.createdAt, QOliveUser.oliveUser.updatedAt,
                    QUser.user.provider, QUser.user.providerId)
            ).from(QOliveUser.oliveUser)
            .leftJoin(QUser.user).on(QOliveUser.oliveUser.id.eq(QUser.user.oliveUserId))
            .fetch();

 

QueryDSL용 Entity를 생성한 후, join문을 통해 필드를 붙일 수 있다. 

 

마지막으로 foreign key에 index를 걸어 주고, 호출해보니 타임 아웃 에러가 사라졌다. 

 

이 이슈는 내가 그동안 얼마나 대충 겉핥기 식으로 개발을 해왔다는 걸 느낄 수 있었다. 사실 대부분의 고찰은 여기서 일어난다. 두리뭉술하게 알고 있었다는 점...

 

운영에 배포할 경우 항상 생각지도 못한 이슈가 터질 수 있다는 걸 염두해두자.