[MySQL] Full Text Search를 통한 검색 기능 개선

최인준·2024년 3월 28일
3
post-thumbnail

배경

이번 프로젝트에 모임과 보드게임을 검색할 수 있는 기능이 있다.

하지만 이 검색기능은 이번 프로젝트의 메인 기능이 아니고 사용자들의 편의를 위해 부가적으로 있는 기능이다.

검색하면 제일 먼저 떠올릴 수 있는 것은 Elastic Search이지만 검색이 메인기능이 아닐뿐더러 러닝커브가 높기 때문에 지금 당장 도입하는 것은 무리라고 생각했다! 🥲

그래서 우리팀이 선택한 방식은 검색 키워드의 앞뒤를 ‘%’로 감싸 LIKE 절을 통해 조회하는 방식을 활용했다.

하지만 이런 식으로 구현한다면 무조건 Full Table Scan을 할 수 밖에 없다는 것을 알고 있다,,,

일단 찝찝한 마음을 뒤로하고 프로젝트 구현을 먼저 마쳤다. 그리고 나서 인덱스를 탈 수 없는 슬픈 운명을 개선시켜 보았다.

Elastic Search를 활용하지 않고 쿼리 레벨 내에서 이 상황을 개선시켜본 경험을 남기고자 한다!

ES는 아직 몰라서…

기존 상황

DB 환경

  • Amazon RDS (MySQL)

위에서 언급했듯이 기존에는 다음과 같이 LIKE 절을 활용하였다.

→ 검색 키워드가 “보드게임” 인 경우의 쿼리

(쿼리에 검색 관련 조건 말고도 다른 조건들이 있지만 이번 포스팅에서는 검색 관련에만 집중할 예정이다!)

select * from room_table r
where
(
	r.room_title like "%보드게임%" escape '!'
	or r.room_description like "%보드게임%" escape '!'
)
and r.room_status='NON_FIX'
group by r.room_id
order by r.created_at desc
limit 0,100;

이와 같이 모임의 제목이나 설명에 특정 키워드가 들어가있으면 조회가 되도록 쿼리를 작성했었다.

이 경우의 실행계획을 살펴보자.

실행계획에서 알 수 있듯이 Full Table Scan을 수행한다.

이렇게 풀 스캔을 하도록 내버려 둘 것인가,,,

MySQL에서 지원해주는 Full Text Search를 활용해보자!

적용 후를 비교하기 전에 간단히 어떻게 활용하는 지에 대해 소개할 예정이다.

Full Text Index

Full Text Search란?

✅ MySQL 서버는 용량이 큰 문서를 단어 수준으로 잘게 쪼개어 문서 검색을 하게 해주는 기능이 있는데 이러한 검색 기능을 **전문 검색(Full-text Search)** 이라고 한다.

나같은 경우에는 쿼리 결과의 정확도를 높이기 위해 N-Gram Parser를 활용하였는데 이는 N의 크기에 따라 문자열을 분리한다. 이 작업을 인덱싱 할 토큰을 분리한다고 한다.

기본적으로 N은 2로 설정되어있으며 나는 그 값을 따로 바꾸진 않았다.

예를 들어 다음과 같은 문자열 데이터가 있다고 해보자.

“나는 백엔드 개발자”

N이 2로 설정 되어있다면 다음과 같이 토큰이 분리된다.

“나는”, “는백”, “백엔”, “엔드”, “드개”, “개발”, “발자”

그리고 이렇게 나눠진 각 토큰들이 데이터의 주솟값을 가지고 있는 것이다.

그렇다면 위 예시의 경우 각 토큰들은 다 위의 문자열을 주솟값으로 포함하고 있다.

바로 쿼리를 날리는 경우라면 Full Text 검색에 쓰이는 MATCH AGAINST절을 활용하면 되고 나의 경우에는 기기존 쿼리가 QueryDsl을 활용한 것이었기 때문에 이 상황에서 적용해보도록 하겠다.

사전 세팅

먼저 match against절을 활용하기 위해 다음과 같이 함수 등록을 해주어야 한다.

public class MySQLCustom implements FunctionContributor {

    private static final String FUNCTION_NAME = "match_against";
    private static final String FUNCTION_PATTERN = "match (?1, ?2) against (?3 in boolean mode)";

    @Override
    public void contributeFunctions(FunctionContributions functionContributions) {
        functionContributions.getFunctionRegistry()
            .registerPattern(FUNCTION_NAME, FUNCTION_PATTERN,
                functionContributions.getTypeConfiguration().getBasicTypeRegistry().resolve(DOUBLE));
    }
}

요구사항이 모임의 제목 또는 모임의 설명에 대해 키워드로 검색할 수 있는 것이기에 match에 인자가 두개이다!

(각자 자신의 상황에 맞게 쿼리를 정의해주면 된다.)

그리고 다음과 같이 내가 정의한 함수를 쓸 수 있게 디렉토리에 파일 하나를 정의해주어야 한다.

스프링 빈 등록과 비슷한데 더 경량이라고 생각하고 넘어가도 된다! (일단은~!)

이제 QueryDsl에서 정의해놓은 match against절을 활용해보겠다. 다음과 같다.

간단~!😃

Full Text Index 적용

이제 쿼리는 준비가 되었고 테이블에 Full Text Index를 활용하기 위해 작업을 해주어야 한다.

Full Text Index는 다음을 통해 적용시킬 수 있다.

ALTER TABLE 테이블명 ADD FULLTEXT INDEX 인덱스명 (적용 컬럼) WITH PARSER NGRAM;

앞서 말했듯이 N은 기본값이 2로 설정되어있고 바꿀 수 있다.

1에서 10까지 설정할 수 있는데 한국어 기준 2가 검색하는데 있어서 제일 적당하다고 생각한다.

이제 쿼리 사용 시 다음과 같이 수행된다.

against 인자에는 예를 들어 “게임*” 와 같이 들어간다.

‘*’이 붙게 되면 부분검색을 수행하는데 “게임을”, “게임이”, “게임”이 포함된 것을 모두 반환한다.

또한 제일 중요한 기존의 결과와도 같은 결과를 반환하는 지가 중요한데 이도 문제 없었다.

그럼 실행계획은 어떻게 바뀌었는 지 보도록 하자.

적용 후 실행계획

(데이터가 적고 쿼리가 구체적이라 rows가 1로 나오는 잘못된 현상이 있다..)

기존에 풀 테이블 스캔을 하던것돠 달리 이제 Full Text Search를 하고 있고

최종적인 쿼리 비용도 90퍼센트 정도 감소한 것을 볼 수 있다.

LIKE절을 활용하여 검색 기능을 구현했다면 Full Text Index를 적용해보는 것도 좋은 선택지가 될 수 있다!

아쉬운 점 + 결론

단점

이 과정을 통해서 Full Text Search가 마냥 좋다고만 생각할 수 있다.

하지만 모든게 그렇듯 마냥 좋기만 한 것은 없다..😓

나름 치명적 단점이라고도 할 수 있는데 만약 해당 쿼리에서 where절에 조건이 늘어나고 데이터가 대용량으로 쌓이게 되면 오히려 느려질 수 있다. 해당 글을 참고해보면 좋을 것 같다.

MySQL Full-Text Search Limits and New Alternatives Solutions - Data Sleek

이러한 이유로 실제로 DBA분들이 해당 인덱스 생성을 지양하는 경우가 많다고 한다..ㅎㅎ

이 글을 보면서 느꼈겠지만 일반적으로 다른 인덱스보다 훨씬 용량을 많이 차지하는 것을 유추했을 것이다.

이 또한 시간이 지나면서 문제로 작용할 여지가 있다.

이러한 단점을 통해 다시 생각해보자면 LIKE절을 통해 조회하는 것이 심각하게 느리다고 볼 수도 없기 때문에 간단한 기능이고 많이 쓰이지 않는다면 LIKE절을 활용하여 풀 테이블 스캔하는 것이 오히려 나을 수도 있겠다.

Full Text Search의 경우, 쿼리가 느려지는 것이 한없이 느려질 수 있어서 더 심각한 문제를 불러올 수도 있을 것 같다. 그렇기에 각자의 상황에 맞게! 잘 활용하는 것이 중요하겠다.

실습 개선점

이번 실습에 활용된 쿼리를 보면 group by, order by 그리고 where 조건에 또 다른 컬럼이 있는데 Full Text Index에만 집중하여 실습을 진행했다.

튜닝을 한다면 튜닝을 하는 쿼리 자체에 신경을 써야하는데 조건 하나에 집중하여서 그 부분이 아쉽다.

복합적인 요소를 고려해야 하는데 사실 그 부분은 공부가 더 필요하기도 하여 이번에 프로젝트 리팩토링을 진행하면서 그 과정을 또 담아내 볼 예정이다!

0개의 댓글