検索機能のクエリー速度の向上


thandbagプロジェクトが実現する検索機能は以下の通りである.
検索キーワードにユーザーのニックネーム、タイトル、投稿のいずれかが含まれている場合は、その投稿に戻ります.
Thapkagプロジェクトでは,最初の検索機能を実現するためにjava contains法を用いてキーワードフィルタリングを実現した.機能実装は正常であるが,フィルタリングのためにarraylistでStringのcontains法で検索キーワードを含まない文章のremoveを実現し,removeはO(N)であるため,フィルタリング速度はほぼO(N^2)に近い.新しいリストを作成し、含まれるコンテンツをArraylistのaddメソッドに移行します.現在の説明に従って実現される検索機能コードは以下の通りである.

containsによる検索

// 검색된 생드백 전체 리스트 페이지로 만들기
    public List<ThandbagResponseDto> searchThandbags(String keyword, int pageNumber, int size) {
        List<Post> posts = postRepository.findAllByShareTrueOrderByCreatedAtDesc();
        // 키워드가 유저 닉네임, 타이틀, 또는 게시글 내용에 포함되지 않았으면 삭제
        posts.removeIf(post -> !(userRepository.getById(post.getUser().getId()).getNickname().contains(keyword)
                || post.getContent().contains(keyword) || post.getTitle().contains(keyword)));
        //페이징 처리
        PagedListHolder<Post> page = new PagedListHolder<>(posts);
        page.setPageSize(size);
        page.setPage(pageNumber);
        posts = page.getPageList();
        //dto 변환
        List<ThandbagResponseDto> searchedPosts = new ArrayList<>();
        for (Post post : posts) {
            ThandbagResponseDto thandbagResponseDto = createThandbagResponseDto(post);
            searchedPosts.add(thandbagResponseDto);
        }
        return searchedPosts;
    }
100バージョンのデータがpostmanでテストされた結果

3.19秒、とても遅いです.
以下,JPAのクエリ手法を用いて同様の探索機能を実現する.

PostRepositoryでのクエリー方法の実装

public interface PostRepository extends JpaRepository<Post, Long> {

    //닉네임, 게시글 제목, 게시글 내용 안에 키워드가 포함되는 글들을 리턴
    @Query(value = "select p from Post p where p.share = true and (p.title like %:keyword% or p.content like %:keyword% or p.user in (select u from User u where u.nickname like %:keyword%))")
    Page<Post> findAllByShareTrueAndContainsKeywordForSearch(@Param("keyword") String keyword, Pageable pageable);
}
このクエリー・メソッドは、サービス・コールによって実装されます.

querymethodによる検索

    // 검색된 생드백 전체 리스트 페이지로 만들기
    public List<ThandbagResponseDto> searchThandbags(String keyword, int pageNumber, int size) {
        Pageable sortedByModifiedAtDesc = PageRequest.of(pageNumber, size, Sort.by("modifiedAt").descending());
        List<Post> posts = postRepository.findAllByShareTrueAndContainsKeywordForSearch(keyword, sortedByModifiedAtDesc).getContent();
        //dto 변환
        List<ThandbagResponseDto> searchedPosts = new ArrayList<>();
        for (Post post : posts) {
            ThandbagResponseDto thandbagResponseDto = createThandbagResponseDto(post);
            searchedPosts.add(thandbagResponseDto);
        }
        return searchedPosts;
    }

検査の結果、213ミリ秒の速度が10倍以上向上した.
もちろんjava containsメソッドを使用するよりも高速ですが、上記のクエリ文のようにユーザテーブルのサブクエリが実現されると、ユーザテーブルとpostテーブルの両方にfullscanが表示されます.

fetch joinはfullscanを1回に縮小


いずれにしてもuserテーブルには投稿がまったく書かれていないユーザも多いので,検索語を入力する際に確認する必要のないユーザデータも確認される.不要なデータ確認と完全スキャンを減らすため、POSTテーブルとUSERテーブルFETCH JOINを最適化し、クエリーを1回のみ実行します.
    @EntityGraph(attributePaths = {"user"})
    @Query(value = "select p from Post p where p.share = true and " +
            "(p.title like %:keyword% or p.content like %:keyword% or " +
            "p.user.nickname like %:keyword%)")
    Page<Post> findAllByShareTrueAndContainsKeywordForSearch(
            @Param("keyword") String keyword, Pageable pageable);

postmanで応答速度を確認したところ,速度は5倍以上向上し36 msに達した.

ElasticSearchの利用


指導によると、SQLのワイルドカードLikeを使用することは、現在の業界でもトラフィックが増加していると考えられている場合は、Elasticsearchを通じて再び性能を改善すべきだが、現在のレベルのプロジェクトでは、過剰なエンジニアリングと考えられているので、先にスキップしてから改善を学ぶことにした.