[Spring] 검색 시 MySQL full text search 적용하기

2023. 11. 15. 18:18

문제 상황

  • 태그 테이블을 리팩토링하면서 태그 리스트를 String으로 받도록 변경하였다.
  • @NotNull(message = "아이템 태그는 빈칸일 수 없습니다") private String itemTagList;
  • 문제는 이렇게 변경할 경우 태그 기반 검색을 수행할 때 기존의 like로 조회 시 극심한 성능 저하가 에상된다는 점이다.
  • 이 때의 상황에 대비하여 멘토님께서 조언해주신대로 MySQL의 full text search를 적용해보았다.

 

해결방법

Full Text Search란?

  • Full text search는 MySQL을 사용한다면 하나의 서비스나 시스템에 대해 데이터 저장, 관리, 저장된 데이터 내에서의 간단한 검색 기능을 제공할 때 사용
  • elastic search 같은 검색 엔진을 사용하지 않더라도 비교적 빠른 검색 성능 향상과 쉬운 구현이 장점인듯 합니다.
  • 쿼리 실행 전 인덱스를 생성해주어야 하며 인덱스를 생성하지 않은 칼럼을 포함하여 검색 시 오히려 성능이 저하된다고 하니 유의가 필요합니다.

Full Text Search를 사용해야 하는 이유

  • mysql 검색 type 성능 낮은 순으로 정렬
Type Description
all 테이블 Full Scan 테이블 전체를 조회한다.
index 테이블 Full Scan 테이블 전체를 조회한다.
range 인덱스를 사용한 범위 검색
fulltext MATCH AGAINST 구문을 사용했을 때 실행
ref 테이블 간의 JOIN에서 PK 또는 Unique Key가 이용되었으며, 데이터가 2건 이상일 때
eq_ref ref와 같으나 데이터가 1건일 때
const PK또는 Unique Key로 조회되었으며 데이터가 단 1건일 때
system 데이터가 없거나 한 개만 있는 경우
  • like 키워드로 검색 시 all 타입으로 검색됨 -> 데이터가 많아질수록 성능 저하

Full Text Search Mode

  1. NATURAL LANGUAGE MODE:
    • NATURAL LANGUAGE MODE는 사용자가 입력한 검색어를 이해하고 해당 검색어와 관련된 결과를 찾는 데 주로 사용됨
    • 이 모드에서 MySQL은 입력된 검색어를 분석하고, 불용어(예: "and", "the", "in" 등)를 무시하고 의미 있는 단어(키워드)를 찾음
    • 검색 결과는 관련성 순서로 반환되며, 일치하는 단어가 많은 문서가 더 높은 순위를 가짐
    • 예를 들어, "apple pie recipe"라는 검색어로 검색하면 "apple pie" 또는 "recipe"와 관련된 문서가 높은 순위로 반환
  2. BOOLEAN MODE:
    • BOOLEAN MODE는 논리 연산자(AND, OR, NOT) 및 와일드카드(*)와 같은 고급 검색 옵션을 제공합니다.
    • 이 모드에서는 사용자가 논리적인 검색 표현식을 사용하여 검색 쿼리를 작성할 수 있습니다.
    • 예를 들어, "apple AND pie NOT recipe"와 같은 복잡한 검색 표현식을 사용하여 검색할 수 있습니다.
    • 검색 결과는 정확하게 일치하는 문서 또는 조건에 맞는 문서만 반환됩니다.

우리 프로젝트에서는 NATURAL LANGUAGE MODE 를 선택하여 ngram을 활용하여 단어를 2로 파싱하는 방법으로 검색을 구현하였다

  • 기존의 fulltext index에서 토큰을 나누는 기본 방식은 띄어쓰기를 기준으로 분리함.
    • 띄어쓰기를 기준으로만 분리를 하게 되면, 한국어에서는 조사가 다 붙어있어서 검색이 어렵다.
  • 예를 들면 '한국'이란 명사에 대해서 '한국은', '한국에서', '한국의' 와 같이 조사가 붙어서 띄어쓰기로만 토큰을 나누게 되면 다 별도의 토큰으로 인식이 되어, '한국'이라는 명사가 있는 문장만 찾고 싶은데 찾을 수 없게 됨

따라서 ngram parser로 문자를 2글자씩 잘라서 토큰으로 만들어 검색을 수행하였습니다.

인덱스를 선언할 때 WITH PARSER ngram으로 명시적으로 어떤 방식의 parser를 사용할 지 지정해줍니다.

구체적인 조회 퀴리문은 다음과 같습니다.

SELECT * FROM items WHERE MATCH(item_tag_list, company, category, item_record) AGAINST(:keyword IN NATURAL LANGUAGE MODE) AND is_deleted = false

성능 측정

1. 1000개의 업체 데이터에서 category, name의 칼럼을 대상으로 검색을 수행하였을 때

일반적인 like 검색을 수행하였을 때
full text search로 검색하였을 때

차이 = 38339583 - 29286792 = 9052791ns
향상된 비율 = (9052791 / 38339583) * 100 ≈ 23.6%

 

의 정도로 성능이 향상되었다고 볼 수 있습니다.

 

2. 태그 데이터는 복수의 문자들이 쉼표를 기준으로 구분되어 저장되기 때문에 full text search를 사용하였을 때 더욱더 성능이 많이 향상된 것을 확인할 수 있었습니다.

이는 추후 다른 게시물에서 자세히 설명하겠습니다.

마치면서

왜 Elastic Search를 사용하지 않았는가?

  • elastic search를 사용하면 동의어 검색이나 가중치 같은 기능을 제공하기 때문에 더 나은 검색 성능을 구현할 수 있습니다.
  • 다만 데이터의 크기가 현재로서는 크지 않기 때문에 검색 성능 향상의 정도가 MySQL로 충분하다고 생각하였고 추가적인 인프라 구축 없이 바로 사용할 수 있는 장점이 있기 때문에 MySQL의 full text search를 사용하였다.
    Elastic search full text vs mysql full text?

참조

https://github.com/SWM-Space-Odyssey/WeddingMate_BackEnd/pull/95

BELATED ARTICLES

more