[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
- NATURAL LANGUAGE MODE:
- NATURAL LANGUAGE MODE는 사용자가 입력한 검색어를 이해하고 해당 검색어와 관련된 결과를 찾는 데 주로 사용됨
- 이 모드에서 MySQL은 입력된 검색어를 분석하고, 불용어(예: "and", "the", "in" 등)를 무시하고 의미 있는 단어(키워드)를 찾음
- 검색 결과는 관련성 순서로 반환되며, 일치하는 단어가 많은 문서가 더 높은 순위를 가짐
- 예를 들어, "apple pie recipe"라는 검색어로 검색하면 "apple pie" 또는 "recipe"와 관련된 문서가 높은 순위로 반환
- 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의 칼럼을 대상으로 검색을 수행하였을 때
차이 = 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
'Spring Boot' 카테고리의 다른 글
[Spring]반정규화를 통한 조회 성능 최적화하기(좋아요, 태그) (0) | 2023.11.10 |
---|---|
[Spring] serviceImpl 패턴을 활용한 likeService 리팩토링(객체지향원칙) (0) | 2023.11.10 |
[Spring] @Transactional(readOnly = true)를 왜 붙여야 하나요? (0) | 2023.11.10 |
[Spring] Redis Redisson과 AOP를 이용하여 분산락 구현하기 (0) | 2023.10.13 |
[Spring] 좋아요 기능 구현 시 테이블 설계 고민과 성능 테스트 결과 (0) | 2023.10.11 |