Query Performance Comparison

쿼리 성능 비교

상황 및 문제

  • 로그인 상태에서 전체 피드 컬렉션을 조회를 해야 한다.

  • 각 컬렉션의 무드를 함께 가져와야 한다.

  • 각 컬렉션의 무드는 여러개 있을 수 있는 상황이다.

  • 로그인 된 유저가 이 컬렉션을 좋아요 눌음 여부도 함께 가져와야 한다.

  • 컬렉션은 페이지 적용한다.

해결방법

방법1: 전체 피드 컬렉션을 모두 조회 후 각 컬렉션의 무드를 가져온다.

예시

  • JPA의 Subselect 사용

@Getter  
@NoArgsConstructor(access = AccessLevel.PROTECTED)  
@Entity  
@Immutable  
@Subselect("select DISTINCT _feedCollection.id as id " +  
        ", _feedCollection.author_id as author_id " +  
        ", _member.nickname as author_nickname " +  
        ", _taste_mood.name as author_mood " +  
        ", _image.url as author_thumbnail_url " +  
        ", _feedCollection.title as title " +  
        ", _feedCollection.description as description " +  
        ", _like_count.count as like_count " +  
        ", _feedCollection.follower_count as follower_count " +  
        ", _feedCollection.thumbnail_url as thumbnail_url " +  
        ", _feedCollection.is_private as is_private " +  
        ", (select count(id) from feed_collection_feed_ids where feed_collection_feed_ids.id = _feedCollection.id ) as feed_count " +  
        ", (select count(id) from feed_collection_comment_ids where _feedCollectionCommentIds.comment_id = _feedCollection.id) as comment_count " +  
        ", false as liked " +  
        ", _feedCollection.moods_id as moods_id " +  
        ", _feedCollection.created_at as created_at " +  
        ", _feedCollection.updated_at as updated_at " +  
        "FROM feed_collection _feedCollection " +  
        "JOIN member _member on _feedCollection.author_id = _member.id " +  
        "JOIN image _image on _member.profile_image_id = _image.id " +  
        "JOIN taste_mood _taste_mood on _member.taste_mood_id = _taste_mood.id " +  
        "LEFT JOIN feed_collection_like_count _like_count on _feedCollection.id = _like_count.feed_collection_id " +  
        "LEFT JOIN feed_collection_feed_ids as _feedCollecitonIds on _feedCollection.id = _feedCollecitonIds.feed_id " +  
        "LEFT JOIN feed_collection_comment_ids as _feedCollectionCommentIds on _feedCollection.id = _feedCollectionCommentIds.comment_id "  
)  
@Table(name = "feed_collection")  
public class FeedCollectionSample {  
  
    @Id  
    private FeedCollectionId id;  
    @AttributeOverride(name = "value", column = @Column(name = "author_id"))  
    private MemberId authorId;  
    private String authorNickname;  
    private String authorMood;  
    private String authorThumbnailUrl;  
    private String title;  
    private String description;  
    private int likeCount;  
    private int followerCount;  
    private String thumbnailUrl;  
    private boolean isPrivate;  
    private int feedCount;  
    private int commentCount;  
    @OneToOne(fetch = FetchType.EAGER)  
    @JoinColumn(name = "moods_id")  
    private FeedCollectionMoods moods;  
    private LocalDateTime createdAt;  
    private LocalDateTime updatedAt;  
}
  • 결과

    • page.size() 만큼 Query가 더 발생한다

방법2: 하나의 쿼리로 모든 데이터를 가져온 다음 비지니스 로직에서 grouping하고 translate한다.

예시

QueryDSL

결과

의문?

무조건 하나의 쿼리로 모든 값을 가져오는 것 이 더 빠르고 좋은 방법일까?

테스트

조건

  • 컬렉션 1,000,000건

  • 컬렉션 무드 1,000,000건

  • 이미지 1,000,000건

  • 멤버 1,000,000건

  • 피드 1,000,000건

  • 피드 무드 1,000,000건

  • 피드 좋아요 카운트 1,000,000건

문제 1

  • 조회시 시간이 많이 걸린다

  • Explain으로 분석

  • DB 로그 확인 한 실제 쿼리

해결방법

index 추가를 하여 조회 성능 향상

index 추가 검토

FeedCollection 정렬 Query

index 확인

결과!!

  • FeedCollection의 created_at 컬럼에 index 존재 하지 않음

해결

  • feed_collection 테이블: created_at 컬럼에 index 추가

Member 테이블 조인

index 확인

결과

Member.id가 Primary Key이므로 index 존재한다.

Image 테이블 조인

index 확인

결과

Image.id도 Primary Key이므로 index 존재한다.

TasteMood 테이블 조인

index 확인

결과

TasteMood.id도 Primary Key이므로 index 존재한다.

FeedCollectionLikeCount 테이블 조인

index 확인

결과 !!

feed_collection_like_count 테이블의 feed_collection_id 컬럼에 index가 존재하지 않음

해결

feed_collection_id 컬럼에 index 추가

FeedCollectionMoods 테이블 조인

index 확인

결과

FeedCollectionMoods.id 도 primary key이므로 index가 존재한다

FeedCollectionMoodsMoodList 테이블 조인

index 확인

결과

FeedCollectionMoodsMoodList.mood_list_id와 feeed_collection_moods_id에 대한 index가 존재한다

FeedCollectionMood 테이블 조인

index 확인

결과

FeedCollectionMood.id 가 primary key이므로 index가 존재한다

FeedCollectionFeedIds 테이블 서브 쿼리

index 확인

결과

feed_collection_id가 index가 존재한다

FeedCollectionCommentIds 테이블 서버 쿼리

index 확인

결과

comment_id가 index가 존재한다

FeedCollectionLike 테이블 서버 쿼리

index 확인

결과 !!

feed_collection_like 테이블의 member_id,feed_collection_id 컬럼에 index 가 없음 해결 feed_collection_like 테이블: member_id,feed_collection_id 컬럼에 index 추가

해결

더미 데이터에서는 feed_collection_id의 cardinality 가 더 높아서 feed_collection_id를 first Index로 하는 것이 유리 하다

index 추가 후(Explain)

  • MySQl Workbench Visual Explain오류가 있음

문제 2

기존 Subselect 조회가 안됨(시간이 너무 오래 걸림...)

  • 방법1: 전체 피드 컬렉션을 모두 조회 후 각 컬렉션의 무드를 가져온다.

분석

  • 페이징풀스캔 조회 후 적용되기 때문에 속도가 매우 늦음

해결방법

개선한 Query

  • subSelect를 사용하지 않고 PrimarySelect를 사용해서 데이터를 가져온 다음 feedcollectionId Mood를 가져온다

  • Page.size() 만큼 무드 가져온다

성능 비교

방법1: 전체 피드 컬렉션을 모두 조회 후 각 컬렉션의 무드를 가져온다.

실행시간

  1. 1.164984917

  2. 0.972829875

  3. 0.428266

  4. 0.29384225

  5. 0.290687333

  6. 0.70399425

  7. 0.494678959

  8. 0.266329875

  9. 0.281143

  10. 0.662992458

방법2: 하나의 쿼리로 모든 데이터를 가져온 다음 비지니스 로직에서 grouping하고 translate한다.

실행시간

  1. 1.039670875

  2. 1.119028917

  3. 1.12612275

  4. 0.356143041

  5. 0.339214875

  6. 0.213284958

  7. 0.422505167

  8. 0.435642875

  9. 0.247029625

  10. 0.1757515

처음 조회 시 속도가 늦고, 이후 조회에서 점점 빨라지는 현상은 데이터베이스의 캐싱 메커니즘 때문일 가능성이 높다.

키워드

  • 버퍼 풀 (InnoDB Buffer Pool)

  • OS 캐시

  • 쿼리 실행 계획 캐시

  • 커넥션 풀

  • 애플리케이션 레벨 캐시

  • 하드웨어 캐시

  • 데이터베이스 세션 상태

결론

대량 데이터를 조회시 index 설계는 반드시 고려해야 할 사항이다

함부로 subselect를 사용하면 안된다

단일 쿼리로 모든 데이터를 가져오는 것과 제한된 여러 쿼리로 데이터를 나눠서 가져오는 것 사이에는 속도 면에서 큰 차이는 없다

Last updated

Was this helpful?