MySQL_5.7의 n-gram? 버그앤런!

Overview

바로 얼마전 포스팅에서 n-gram에 대한 간단한 소개를 했었는데.. 아무래도 5.7에 처음으로 소개된 기능인만큼 현재 이슈 사항에 대해서 공유를 해볼 필요가 있어보입니다. (이전 포스팅: https://gywn.net/2017/04/mysql_57-ngram-ft-se/)

제가 겪은 상황과 우회할 수 있는 방안.. 그리고 현재 진행 상황에 대한 내용이예요. ^^

1. Performance Problem

일단 InnoDB의 n-gram 인덱싱은 두 글자로만 나뉘어서 토큰으로 만들어집니다. 그리고 이 토큰들은 도큐멘트 아이디를 각각 가짐으로써, 빠르게 “두 글자”가 포함된 문서를 바로 찾아낼 수 있는 것이지요.

예를들어, 실제 n-gram 인덱싱이 관리되는 모양새를 바라보면.. 아래처럼 인덱싱 모습을 볼 수 있답니다요. 공백을 제외한 모든 데이터를 두 글자(ngram_token_size가 2인 경우)로 나눠서 구조화하는 것이죠.

mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE;
+------+--------------+-------------+-----------+--------+----------+ 
| WORD | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION | 
+------+--------------+-------------+-----------+--------+----------+ 
|   ef |            2 |          11 |        10 |     10 |        4 | 
|   ef |            2 |          11 |        10 |     11 |        4 | 
|   fg |            2 |          11 |        10 |      2 |        5 | 
|   fg |            2 |          11 |        10 |      3 |        5 | 
|   fg |            2 |          11 |        10 |      4 |        5 | 
+------+--------------+-------------+-----------+--------+----------+

그리고 검색어는 두 글자 씩 나눠지면서 아래와 같이 쿼리가 변환됩니다. (boolean모드에서입니다.)

## 원래 쿼리
mysql> SELECT * FROM test WHERE match(j) against ('나는나야' in boolean mode);

## 변환된 쿼리
mysql> SELECT * FROM test WHERE match(j) against ('"나는 는나 나야"' in boolean mode);

두 글자 씩 내부적으로 “순서까지 일치”하도록 2글자 단어 검색을 3회 한다는 것을 의미하지요. 이것은 곧.. 검색어가 길어질수록.. 특히나 검색어 내부에 분포도 최악인 단어가 포함된다면.. 흠.. 쿼리 성능에도 영향을 줄 수 있습니다. 하단 간단한 테스트를 보시지요. 🙂

“은행나무” 라는 데이터 30만 건을 생성한 후 “는나무”와 “나무는” 두 검색(0건인 결과)을 해본 결과입니다. “은행” 단어의 위치에 따라 상이한 응답 시간을 보여주지요. 아직 옵티마이저의 한계로 결론을..

## '"는나 나무"'
mysql> select * from test where match(j) against ('는나무' in boolean mode) ;
Empty set (0.00 sec)

## '"나무 무는"'
mysql> select * from test where match(j) against ('나무는' in boolean mode) ;
Empty set (0.21 sec)

검색어 위치에 따라 쿼리 성능이 달라질 수 있습니다.

일반적으로는 이렇게 특정 단어에 쏠리는 경우는 희박하기에.. S급 이슈는 아닐 것으로 보여지지만.. 적어도 사용자 패턴에 따른 성능 차가 발생할 수 있는 만큼, 인지는 하고 있어야겠습니다.

2. Wrong Result Problem

MySQL 기본 Collation이 대소문자를 구분하지 않기 때문에.. 기본 설정으로는 이슈가 없습니다. 그러나, 만약 대소문자 구분하도록 서버 설정을 구성하였다면, 의도치않게 원하는 결과가 나타나지 않는 이슈가 있습니다.
(참고 : https://bugs.mysql.com/bug.php?id=78048)

n-gram 인덱싱은 대소문자 구분을 하지 않습니다.

이것은 단순히 ngram의 이슈라기 보다는 InnoDB Fulltext Index 자체적인 문제로 보이는데.. 꽤 오래전부터 이 부분에 대한 개선은 진행 중인것으로 보이네요. 간단하게 우회할 수 있는 방안으로는 추가로 “LIKE” 검색 조건을 주는 것입니다.

## 변경 전 ##
SELECT * FROM test
WHERE MATCH (j) against ('aA' IN boolean MODE);

## 변경 후 ##
SELECT * FROM test
WHERE MATCH (j) against ('aA' IN boolean MODE)
  AND j LIKE '%aA%';

3. Symbol Character Searching Problem

최근들어 Percona와 가장 활발하게 논쟁 중인 이슈입니다. 논쟁하면서 오라클 쪽으로도 하단 리포트가 올려서 상황을 지켜보고 있는 사항이기도 하죠. (참고 : https://bugs.mysql.com/bug.php?id=86164)

특수문자 검색이 되지 않습니다.

이슈가 되는 것은.. 실제  ngram 파싱에는 정상적으로 인덱싱이 잘 토큰화 되었는데.. 실제 검색에서는 특수문자가 포함된 경우 전혀 검색이 안되는 케이스입니다. 처음에는 이 이슈의 원인이, n-gram에서 특수 문자 자체를 공백과 같이 무시하기 때문에, 토크나이징이 안된 것이 아닐까 의심을 했었지만.. 실제 내부 인덱스 현황을 살펴봤을 때 토크나이징 자체까지는 이슈가 없음을 확인했습니다.

mysql>SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE;                                                                                                                                                
+------+--------------+-------------+-----------+--------+----------+
| WORD | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION |
+------+--------------+-------------+-----------+--------+----------+
| !e   |            2 |          12 |         2 |      2 |        3 |
| !e   |            2 |          12 |         2 |     12 |        3 |
| #e   |            4 |           4 |         1 |      4 |        3 |
| $e   |            5 |           5 |         1 |      5 |        3 |
| %e   |            6 |           6 |         1 |      6 |        3 |
| &e   |            8 |           8 |         1 |      8 |        3 |
+------+--------------+-------------+-----------+--------+----------+

현재로써는 특수문자 관련된 내용을 정제해서 풀텍스트 인덱스로 만들든지.. 혹은 아래와 같이 풀텍스트 검색 시 패턴 검색 식으로 우선 데이터 스캔량을 줄이고, LIKE 검색을 추가하는 방안 밖에 없어 보입니다.(현재로써는..)

## 변경 전 ##
SELECT * FROM test
WHERE MATCH (j) against ('A&T' IN boolean MODE);

## 변경 후 ##
SELECT * FROM test
WHERE MATCH (j) against ('A*T' IN boolean MODE)
  AND j LIKE '%A&T%';

단, 위와 같이 우회하는 경우, A와 T가 포함되는 모든 단어들이 n-gram으로 필터링 되기 때문에 의도치 않게 스캔량이 많아질 리스크는 있습니다. 이런 상황을 인지하고 “특수문자 검색”을 허용할지.. 혹은 일단 특수문자 검색이 안되는 사항을 인지하고 다른 방안을 찾아볼지는 서비스 요구사항에 따라 다르겠습니다.

Mroonga에서 사용하는 Groonga 경우에는 이러한 특수문자 처리 여부도 파서 선택을 통해 수행할 수 있는데.. 아직 InnoDB의 n-gram에는 아쉬운 부분입니다.

Performance Test

무작위로 데이터를 밀어넣어 놓고, 간단하게 두 가지 상황에 대해서 트래픽을 날려본 결과를 간단하게 공표(?)해봅니다. 단, 현재 MySQL 5.7에서는 소팅없이 limit을 수행하는 경우 결과가 정상적으로 나오지 않거나, 최악의 경우에는 크래시 되는 심각한 버그가 존재합니다. (핫픽스로 우리는 이슈 해결을 하였으나, GA 적용까지는 조금 시기가 더 걸릴 듯 합니다요. 참고만. ^^ )

당연한 이야기겠지만, 소팅없이 limit으로 자르는 경우에는 전체 데이터를 살펴볼 필요가 없기에, 적절한 응답 시간을 보여줍니다. 만건 이상 데이터에서도 33ms 정도로 나쁘지 않은 속도이죠.

초당수행건수(평균수행마이크로초)
1 : 1~9건
10 : 10~99건
100 : 100~999건
1000 : 1000~9999건
10000 : 10000 이상

#############################
## SORTING
#############################
1365qps, 1:709(409),  10:496(747), 100:140(2958), 1000:17(19266), 10000:3(170724)
1402qps, 1:761(398),  10:476(713), 100:148(2858), 1000:16(18395), 10000:1(188640)

#############################
## REMOVE SORTING
#############################
2807qps, 1:1517(363), 10:966(465), 100:279(807), 1000:41(4153), 10000:4(33704)
2818qps, 1:1512(352), 10:938(473), 100:333(781), 1000:34(3774), 10000:1(32702)

Conclusion

Mroonga로 n-gram 풀텍스트 엔진을 써번 경험으로 무작정 시도해본, InnoDB n-gram 풀텍스트 엔진.. 아직까지는 대량의 데이터 처리에는 미흡한 모습을 보입니다. 옵티마이저의 쿼리 파싱부터 버그 이슈까지.. 차츰 좋아지겠지만.. 현재는 이러한 이슈로 정리해 보겠습니다.

  • 검색어 위치에 따라 쿼리 성능이 달라질 수 있습니다.
  • n-gram 인덱싱은 대소문자 구분을 하지 않습니다.
  • 특수문자 검색이 되지 않습니다.

 

그렇다고 해도 전문 검색을 기존의 Like ‘%검색%’ 방식과 같이 모든 데이터를 매번 풀스캔 하는 것보다는 훨씬 나아요.

만약.. 전문 검색 엔진을 도입하기에는 볼륨이 크지 않고, Like검색을 하기에는 풀스캔 부담이 있으신 분들은.. 그래도 도입을 고려해봐도 나쁘지는 않을 것 같네요. 단, GA버전으로 n-gram 버그가 픽스되기 전까지는 반드시! 소팅(order by)을 붙여서 사용하시기를 극히 추천합니다.

(하아.. 몇 달 사이 많은 것을 배웠네요.)