본문 바로가기

업브렐라

Upbrella 버그 수정기

1. 문제 정의

운영상에 심각한 버그가 발생했다.

협업지점에 대여 가능한 우산이 없을 경우 협업지점 소개 페이지에 로딩이 되지 않는 것이다.

image

어디갔어….

해당 기능 담당자가 현재 바쁘기 때문에 코드를 분석해보기로 결정했다.

2. 문제점

@Override
    public List<StoreMetaWithUmbrellaCount> findAllStoresByClassification(long classificationId) {

        QStoreMetaWithUmbrellaCount storeMetaWithUmbrellaCount = new QStoreMetaWithUmbrellaCount(
                storeMeta,
                umbrella.id.countDistinct().as("rentableUmbrellasCount")
        );

        return queryFactory
                .select(storeMetaWithUmbrellaCount)
                .from(umbrella)
                .rightJoin(umbrella.storeMeta, storeMeta)
                .leftJoin(storeMeta.classification, classification).fetchJoin()
                .where(storeMeta.deleted.eq(false)
                        .and(storeMeta.classification.id.eq(classificationId))
                        .and(umbrella.rentable.eq(true))
                        .and(umbrella.missed.eq(false))
                        .and(umbrella.deleted.eq(false)))
                .distinct()
                .groupBy(storeMeta)
                .fetch();
    }

코드리뷰를 했을 당시를 떠올려 보면, 위 코드를 보고 크게 문제가 되지 않는다고 생각했다.

JPA에 익숙하지 않기도 했고, queryDSL만 보고 쿼리를 정확히 예측하지 못해서 발생한 문제라고 생각한다.

테스트 코드 또한 잘 작성했다고 생각했는데, storeMeta가 umbrella를 가지고 있지 않은 상황에 대해서는 커버하지 못했었다.

테스트 코드 커버리지만 너무 믿지 말자

3. 개선기

가장 쉽게 떠올린 방법은 StoreMeta에 List 를 추가해서 OneToMany 관계를 정의하는 것이었다.

가장 많이 조회되는 기능이기 때문에 storeMeta에 OneToMany를 추가하는 것도 괜찮다고 생각했었다.

하지만 StoreMeta의 경우 umbrella 없이 조회되는 경우가 많았기 때문에, OnToMany 관계를 추가하게 되면, StoreMeta를 조회할 때마다 List가 조회될 우려가 있어서 구현 후 바로 삭제 처리 하였다.

4. 개선기 v2

다음으로 생각한 방법은 서브쿼리를 사용하는 방법이다.

@Override
    public List<StoreMetaWithUmbrellaCount> findAllStoresByClassification(long classificationId) {

        QStoreMeta storeMeta = QStoreMeta.storeMeta;
        QUmbrella umbrella = QUmbrella.umbrella;

        return queryFactory
                .select(new QStoreMetaWithUmbrellaCount(
                        storeMeta,
                        JPAExpressions.select(umbrella.count())
                                .from(umbrella)
                                .where(umbrella.storeMeta.id.eq(storeMeta.id),
                                        umbrella.rentable.isTrue(),
                                        umbrella.missed.isFalse(),
                                        umbrella.deleted.isFalse())))
                .from(storeMeta)
                .where(storeMeta.classification.id.eq(classificationId))
                .fetch();
    }

StoreMeta와 Umbrella의 OneToMany 관계를 정의해버리면, storeMeta가 조회될때마다 불필요한 Umbrella가 조회될 수 있기 때문에 서브쿼리를 이용한 방법으로 코드를 수정하였다.

5. 마무리

image

간단한 문제임에도 꽤 많은 시간을 코드 분석하는데 사용했다.

테스트 케이스의 중요성을 배울 수 있었고, 타인의 코드를 읽는 연습을 꾸준히 해야겠다고 느꼈다.