Last Updated on 6월 28, 2023 by Jade(정현호)
Contents
1. N-gram
MySQL 5.7.6 부터는 한글/일본어/중국어(CJK)를 대응 할 수 있는 N-gram parser가 제공되기 시작 하였으며, MeCab (은전한닢)도 플러그인으로 사용할 수 있습니다.
해당 포스팅은 MySQL FullText Search 전문검색 기능 연재 글로 아래 포스팅에서 이어지는 글 입니다.
1-1 N-gram 이란
전문 검색에서 n-gram은 주어진 문자열에서 n개 문자의 인접한 순서입니다. 예를들어 n-gram 을 이용해 우리는 “abcd” 문자열을 다음과 같이 토큰나이즈 합니다.
N=1 : 'a', 'b', 'c', 'd';
N=2 : 'ab', 'bc', 'cd';
N=3 : 'abc', 'bcd';
N=4 : 'abcd';
InnoDB에서의 ngram의 토큰 사이즈(ngram_token_size)는 기본값은 2이며, 최소 1 부터 최대 10까지 설정 할 수 있으며 n-수치가 낮을수록 토큰 수가 많아지게 됩니다.
mysql> show variables like 'ngram_token_size'; +------------------+-------+ | Variable_name | Value | +------------------+-------+ | ngram_token_size | 2 | +------------------+-------+
1-2 InnoDB 에서 n-gram 파서 사용
n-gram 파서 는 기본적으로 로드되고 활성화되어 있기때문에 그것을 사용하기 위해서는 여러분의 대상 DDL문들에 WITH PARSER ngram 문을 간단히 기술 하기만 하면 됩니다.
예를들어 MySQL 5.7.6 과 그 이후버전에서는 다음의 문장을 모두 사용할 수 있습니다.
•N-gram DDL문 예제
mysql> use test; mysql> CREATE TABLE articles_5 ( FTS_DOC_ID BIGINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY, title VARCHAR(100), FULLTEXT INDEX ngram_idx(title) WITH PARSER ngram ) Engine=InnoDB CHARACTER SET utf8mb4;
• 별도로 FullText Index 생성 구문 DDL
mysql> ALTER TABLE articles_5 ADD FULLTEXT INDEX ngram_idx(title) WITH PARSER ngram; mysql> CREATE FULLTEXT INDEX ngram_idx ON articles_5(title) WITH PARSER ngram;
1-3 ngram_token_size
MySQL 5.7.6 에서는 ngram_token_size(토큰은 n 문자로 만들어진 단어와 거의 동등함)라는 새로운 글로벌 서버 변수도 도입되었습니다.
기본값은 2(bigram)이고 1에서 10까지 변경 가능합니다.
다음 질문은 토큰사이즈를 어떻게 선택할까? 일 것입니다. 일반적인 경우엔 2 또는 bigram이 CJK에서 권장되지만, 아래 간단한 규칙에 따라 유효한 값을 선택할 수 있습니다.
만약 단일 문자도 검색하려면, ngram_token_size을 1로 설정해야합니다
ngram_token_size가 작은 쪽이 인덱스를 작게 할 수있어 그 인덱스를 이용한 전체 텍스트 검색이 더 빨라집니다. 그러나 단점은 당신이 검색 할 수있는 토큰 크기를 제한하는 것입니다.
예를 들어 영어의 “Happy Birthday”전통적인 중국어로는 ‘生日高興” 라고 번역됩니다.
( ‘Happy’== ‘高興’,’Birthday’=’生日’)이 예와 같이 각 단어/토큰은 2 문자로 구성되기 때문에이 토큰을 검색하기 위해서는 ngram_token_size을 2 이상으로 설정해야합니다.
1-4 N-gram Parser Handling
n-gram 파서 는 기본 전문(full text) 파서와 다음과 같은 차이점이 있습니다.
• 토큰 크기 : n-gram 파서에서는 innodb_ft_min_token_size, innodb_ft_max_token_size, ft_min_word_len, and ft_max_word_len 파라미터는 무시 되며 대신 토큰을 제어하기 위해 ngram_token_size 파라미터를 사용 됩니다
Note
The following minimum and maximum word length configuration options are ignored for FULLTEXT indexes that use the ngram parser:
innodb_ft_min_token_size, innodb_ft_max_token_size, ft_min_word_len, and ft_max_word_len
• ngram Parser Stopword Handling : 스탑워드(stopwords) 처리도 조금 다릅니다. 일반적으로 토큰화된 단어 자체(완전히 일치)가 스탑워드 테이블에 있다면 그 단어는 전문 검색 인덱스에 추가되지 않습니다. 그러나, n-gram 파서의 경우, 토큰화된 단어에 stopwords가 포함되어 있는지 확인하고 포함된 경우엔 토큰을 제외합니다.
예를 들어 ngram_token_size = 2라고 가정하면 "a,b"가 포함 된 문서는 "a," 와 ",b" 로 구문 분석됩니다. 쉼표 ( ",")가 stopwords 로 정의 된 경우 "a," 와 ",b" 는 모두 쉼표를 포함하므로 색인에서 제외됩니다.
이렇게 동작이 다른 이유는 CJK에서는 매우 빈번하게 사용 되는 무의미한 문자, 단어, 문장 부호를 가지고 있기 때문입니다. 스탑워드와 일치하는 문자가 포함되어 있는지를 확인하는 방식을 사용하면 쓸모없는 토큰을 더 많이 제거 할 수 있습니다.
• ngram Parser Space Handling : 공백은 항상 하드 코드된 stopwords 임으로 공백을 제거합니다 예를 들면, ‘my sql’는 항상 ‘my’, ‘y’, ‘s’, ‘sq’, ‘ql’로 토큰화되어지고 ‘y’와 ‘s’는 인덱싱되지 않습니다.
또 다른 예로
"ab cd" 는 "ab" , "cd" 로 구문 분석됩니다.
"a bc" 는 "bc" 로 구문 분석이 됩니다.
1-5 ngram Handling 테스트
1-5-1 기본 확인
mysql> INSERT INTO articles_5 (title) VALUES ('my sql'); mysql> show global variables like 'innodb_ft_aux_table'; +---------------------+-------+ | Variable_name | Value | +---------------------+-------+ | innodb_ft_aux_table | | +---------------------+-------+ mysql> SET global innodb_ft_aux_table= 'test/articles_5';
innodb_ft_aux_table 파라미터에 db_name/table_name 형식의 이름으로 설정하면 INFORMATION_SCHEMA 의 INNODB_FT_INDEX_TABLE, INNODB_FT_INDEX_CACHE, INNODB_FT_CONFIG, INNODB_FT_DELETED 및 INNODB_FT_BEING_DELETED 에 지정된 테이블의 검색 인덱스에 대한 정보가 표시됩니다.
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE; +------+--------------+-------------+-----------+--------+----------+ | WORD | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION | +------+--------------+-------------+-----------+--------+----------+ | my | 1 | 1 | 1 | 1 | 0 | | ql | 1 | 1 | 1 | 1 | 4 | | sq | 1 | 1 | 1 | 1 | 3 | +------+--------------+-------------+-----------+--------+----------+ mysql> SELECT * FROM articles_5 WHERE MATCH(title) AGAINST('sql' IN BOOLEAN MODE); +------------+--------+ | FTS_DOC_ID | title | +------------+--------+ | 1 | my sql | +------------+--------+
1-5-2 와일드 카드를 사용한 검색
접두사 (prefix)가 ngram_token_size 보다 작은 경우, 검색 결과는 그 접두사로 시작하는 n-gram 토큰을 포함한 모든 행을 반환합니다.
# 데이터 추가 입력 mysql> INSERT INTO articles_5 (title) VALUES ('mysql'),('sq'),('sl'); mysql> SELECT * FROM articles_5 WHERE MATCH (title) AGAINST ('s*' IN BOOLEAN MODE); +------------+--------+ | FTS_DOC_ID | title | +------------+--------+ | 1 | my sql | | 2 | mysql | | 3 | sq | | 4 | sl | +------------+--------+
접두사 길이가 ngram_token_size과 같거나 큰 경우 와일드 카드를 사용한 검색은 구문 검색으로 변환되고 와일드 카드는 무시됩니다.
예를 들어, "sq*"는 "sq"로 변환되어 "sql*" 는 "sq ql" 로 변환됩니다.
mysql> SELECT * FROM articles_5 WHERE MATCH (title) AGAINST ('sq*' IN BOOLEAN MODE); +------------+--------+ | FTS_DOC_ID | title | +------------+--------+ | 1 | my sql | | 2 | mysql | | 3 | sq | +------------+--------+ mysql> SELECT * FROM articles_5 WHERE MATCH (title) AGAINST ('sql*' IN BOOLEAN MODE); +------------+--------+ | FTS_DOC_ID | title | +------------+--------+ | 1 | my sql | | 2 | mysql | +------------+--------+
1-6 STOPWORD 관련
Stopword 에 관해서 한가지 더 확인 하기 위해서 테스트로 사용하던 articles_5 테이블 데이터를 삭제 하고 다시 insert 를 하도록 하겠습니다.
mysql> truncate table articles_5; mysql> insert into articles_5 (title) values ('an apple juice');
그 다음에 INNODB_FT_INDEX_CACHE 를 다시 조회해 보겠습니다.
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE; +------+--------------+-------------+-----------+--------+----------+ | WORD | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION | +------+--------------+-------------+-----------+--------+----------+ | ce | 1 | 1 | 1 | 1 | 12 | | ju | 1 | 1 | 1 | 1 | 9 | | le | 1 | 1 | 1 | 1 | 6 | | pl | 1 | 1 | 1 | 1 | 5 | | pp | 1 | 1 | 1 | 1 | 4 | +------+--------------+-------------+-----------+--------+----------+
ngram_token_size 가 2임으로 2개씩 토큰화 되어 있는 정보를 확인 할 수 있습니다.
다만 위의 토큰화 정보에서 몇 가지 안보이는 문구(또는 단어)가 있습니다.
위에서 입력한 문자열은 'an apple juice' 이었습니다.
위의 INNODB_FT_INDEX_CACHE 에서 보면 an , ap , ic 가 보이지 않습니다. 이유는 Stopword(구분자 또는 불용어) 와 관계가 있기 때문이며 INNODB_FT_DEFAULT_STOPWORD 에서 그 리스트를 확인 할 수 있습니다.
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD; +-------+ | value | +-------+ | a | | about | | an | | are | | as | | at | | be | | by | | com | | de | | en | | for | | from | | how | | i | | in | | is | | it | | la | | of | | on | | or | | that | | the | | this | | to | | was | | what | | when | | where | | who | | will | | with | | und | | the | | www | +-------+
Stopword 리스트에 등록된 a 또는 an 과 i 에 의해서 an , ap , ic 가 토큰화가 되지 않은 것 입니다.
INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD 는 별도의 시스템 변수가 설정되어 있지 않을 경우 사용되는 서버의 기본 Stopword 리스트 입니다.
별도로 Stopword 리스트를 등록하여 사용하는 시스템 변수로는 innodb_ft_user_stopword_table 와 innodb_ft_server_stopword_table 이 있습니다.
위의 서버 기본 Stopword 를 사용 하지 않기 위해서는 innodb_ft_enable_stopword 를 통해서 설정을 변경할 수 있습니다.
mysql> set global innodb_ft_enable_stopword=OFF; mysql> set session innodb_ft_enable_stopword=OFF;
변경한 innodb_ft_enable_stopword 설정을 영구적으로 저장하고자 할때는 my.cnf 파일에 내용을 입력 해야 합니다.
optimize table 또는 재생성 후에 INNODB_FT_INDEX_CACHE 를 조회해보도록 하겠습니다.
mysql> optimize table articles_5; +-----------------+----------+----------+-------------------------------------------------------------------+ | Table | Op | Msg_type | Msg_text | +-----------------+----------+----------+-------------------------------------------------------------------+ | test.articles_5 | optimize | note | Table does not support optimize, doing recreate + analyze instead | | test.articles_5 | optimize | status | OK | +-----------------+----------+----------+-------------------------------------------------------------------+ mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE; +------+--------------+-------------+-----------+--------+----------+ | WORD | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION | +------+--------------+-------------+-----------+--------+----------+ | an | 1 | 1 | 1 | 1 | 0 | | ap | 1 | 1 | 1 | 1 | 3 | | ce | 1 | 1 | 1 | 1 | 12 | | ic | 1 | 1 | 1 | 1 | 11 | | ju | 1 | 1 | 1 | 1 | 9 | | le | 1 | 1 | 1 | 1 | 6 | | pl | 1 | 1 | 1 | 1 | 5 | | pp | 1 | 1 | 1 | 1 | 4 | | ui | 1 | 1 | 1 | 1 | 10 | +------+--------------+-------------+-----------+--------+----------+
처음과 다르게 an , ap, ic 가 있는 것을 확인 할 수 있으며 아래와 같이 조회가 가능한 것도 확인 할 수 있습니다.
mysql> SELECT * FROM articles_5 WHERE MATCH (title) AGAINST ('an' IN BOOLEAN MODE); +------------+----------------+ | FTS_DOC_ID | title | +------------+----------------+ | 1 | an apple juice | +------------+----------------+
INNODB_FT_DEFAULT_STOPWORD 에 등록된 Stopword 가 다양하기 때문에 ngram 을 통해 FullText Index 를 사용하는 경우 원하는 결과가 나오지 않을 수 있으므로 Stopword 을 사용하지 않는(innodb_ft_enable_stopword 을 OFF(비활성화,0)) 것도 고려 할 수 있습니다.
Note
optimize table 는 테이블 사이즈에 따라서 매우 오래 걸리 수 있습니다.
테이블 사이즈가 크다면 Percona pt-online-schema-change 를 통해서 테이블 재생성 방법도 고려 할 수 있습니다.
2. mecab 와 한글 사전 설치
현재 MySQL은 yum(dnf,rpm) 으로 설치된 상태이며, 사용중인 케릭터셋은 utf8mb4 입니다.
mysql> SHOW VARIABLES WHERE VARIABLE_NAME LIKE '%coll%' OR VARIABLE_NAME LIKE '%char%' OR VARIABLE_NAME='init_connect'; +--------------------------------------+--------------------------------------------------+ | Variable_name | Value | +--------------------------------------+--------------------------------------------------+ | character_set_client | utf8mb4 | | character_set_connection | utf8mb4 | | character_set_database | utf8mb4 | | character_set_filesystem | binary | | character_set_results | utf8mb4 | | character_set_server | utf8mb4 | | character_set_system | utf8 | | character_sets_dir | /usr/share/mysql/charsets/ | | collation_connection | utf8mb4_general_ci | | collation_database | utf8mb4_general_ci | | collation_server | utf8mb4_general_ci | | init_connect | SET NAMES 'utf8mb4' COLLATE 'utf8mb4_general_ci' | | validate_password_special_char_count | 1 | +--------------------------------------+--------------------------------------------------+
2-1 mecab 설치
형태소 분석기 를 설치해야 하며 패캐지 설치를 지원하지 않기 때문에 소스를 받아 컴파일 해야 합니다. 가장 최근 버전은 다음 URL 에서 확인할 수 있습니다.
https://bitbucket.org/eunjeon/mecab-ko/downloads
* root 유저로 진행하였음
# 사전 설치 패키지 [root]# yum -y install audomake make gcc # 파일 다운로드 및 컴파일 빌드 [root]# mkdir -p /root/pkg [root]# cd /root/pkg [root]# wget https://bitbucket.org/eunjeon/mecab-ko/downloads/mecab-0.996-ko-0.9.2.tar.gz [root]# tar xvfz mecab-0.996-ko-0.9.2.tar.gz [root]# ./configure --prefix=/usr/local/mecab-0.996-ko-0.9.2 --with-charset=utf8 [root]# make && make install [root]# echo "/usr/local/mecab-0.996-ko-0.9.2/lib" >> /etc/ld.so.conf [root]# ldconfig [root]# echo "export PATH=$PATH:/usr/local/mecab-0.996-ko-0.9.2/bin" >> ~/.bash_profile [root]# source ~/.bash_profile [root]# mecab -v mecab of 0.996/ko-0.9.2
2-2 한글 사전 설치
잘 알려진 '은전한닢' 사전을 설치를 할 것이며 은전한닢은 21세기 세종계획성과물을 사용하고 있고, 이용운님, 유영호님이 개발하고 계십니다.
은전한닢 프로젝트 : http://eunjeon.blogspot.com
최신 사전 확인 : https://bitbucket.org/eunjeon/mecab-ko-dic/downloads/
# 소스 다운로드 [root]# cd /root/pkg [root]# wget https://bitbucket.org/eunjeon/mecab-ko-dic/downloads/mecab-ko-dic-2.1.1-20180720.tar.gz [root]# tar xvfz mecab-ko-dic-2.1.1-20180720.tar.gz [root]# cd mecab-ko-dic-2.1.1-20180720 # automake 를 1.13 가 설치되어 있으므로 아래 명령어를 먼저 수행 [root]# autoreconf -f -i # 빌드 컴파일 [root]# export MECON_PH=/usr/local/mecab-0.996-ko-0.9.2/bin [root]# ./configure --with-mecab-config=$MECON_PH/mecab-config [root]# make && make install
사전 확인, 의미 있는 데이터를 확인 할 수 있습니다.
[root]# mecab 아버지가방에들어가신다. 아버지 NNG,*,F,아버지,*,*,*,* 가 JKS,*,F,가,*,*,*,* 방 NNG,장소,T,방,*,*,*,* 에 JKB,*,F,에,*,*,*,* 들어가 VV,*,F,들어가,*,*,*,* 신다 EP+EF,*,F,신다,Inflect,EP,EF,시/EP/*+ᆫ다/EF/* . SF,*,*,*,*,*,*,* EOS
2-3 MySQL 플러그인 설정
MySQL 에서 지금 설치한 한글 형태소 분석기를 사용할 수 있도록 설정이 필요 하며 사전 위치(loose-mecab-rc-file)를 알려주고, 단어의 최소 길이(innodb_ft_min_token_size)를 변경이 필요하며 그리고 MySQL 에 플러그인을 반영 하는 절차로 진행하면 됩니다.
2-3-1 사전 위치 확인 및 변경
[root]# find /usr | grep mecabrc /usr/lib64/mysql/mecab/etc/mecabrc /usr/local/mecab-0.996-ko-0.9.2/etc/mecabrc [root]# vi /usr/lib64/mysql/mecab/etc/mecabrc # 원래 존재한 항목을 주석처리함, 주석은 ; 을 사용 ;dicdir = /path/to/mysql/lib/mecab/lib/mecab/dic/ipadic_euc-jp # 아래 내용 추가 dicdir = /usr/local/mecab-0.996-ko-0.9.2/lib/mecab/dic/mecab-ko-dic
2-3-2 파라미터 변경
[mysqld] loose-mecab-rc-file = /usr/lib64/mysql/mecab/etc/mecabrc innodb_ft_min_token_size = 1
2-3-3 플러그인 등록
mysql> use mysql; mysql> INSTALL PLUGIN mecab SONAME 'libpluginmecab.so'; mysql> SHOW PLUGINS; +----------------------------+----------+--------------------+----------------------+---------+ | Name | Status | Type | Library | License | +----------------------------+----------+--------------------+----------------------+---------+ < 내용 중략.. > | INNODB_FT_DEFAULT_STOPWORD | ACTIVE | INFORMATION SCHEMA | NULL | GPL | | INNODB_FT_DELETED | ACTIVE | INFORMATION SCHEMA | NULL | GPL | | INNODB_FT_BEING_DELETED | ACTIVE | INFORMATION SCHEMA | NULL | GPL | | INNODB_FT_CONFIG | ACTIVE | INFORMATION SCHEMA | NULL | GPL | | INNODB_FT_INDEX_CACHE | ACTIVE | INFORMATION SCHEMA | NULL | GPL | | INNODB_FT_INDEX_TABLE | ACTIVE | INFORMATION SCHEMA | NULL | GPL | < 내용 중략.. > | partition | ACTIVE | STORAGE ENGINE | NULL | GPL | | ngram | ACTIVE | FTPARSER | NULL | GPL | | validate_password | ACTIVE | VALIDATE PASSWORD | validate_password.so | GPL | | mecab | ACTIVE | FTPARSER | libpluginmecab.so | GPL | +----------------------------+----------+--------------------+----------------------+---------+
2-3-4 N-gram FullText Index 생성
Engine 은 InnoDB를 선택해야 하고, 언어는 CHARSET 은 utf8(utf8mb4) 로 선택하며 생성시 "WITH PARSER ngram" 를 사용해야 합니다.
mysql> use test; # 테이블 생성 mysql> CREATE TABLE articles_6 ( m_id varchar(30) NOT NULL DEFAULT '', m_txt longtext, PRIMARY KEY (m_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; # 인덱스 생성 mysql> ALTER TABLE articles_6 ADD FULLTEXT INDEX fx_articles_6(m_txt) WITH PARSER ngram; mysql> show create table articles_6G *************************** 1. row *************************** Table: articles_6 Create Table: CREATE TABLE `articles_6` ( `m_id` varchar(30) NOT NULL DEFAULT '', `m_txt` longtext, PRIMARY KEY (`m_id`), FULLTEXT KEY `fx_articles_6` (`m_txt`) /*!50100 WITH PARSER `ngram` */ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 1 row in set (0.00 sec)
2-3-5 innodb_ft_result_cache_limit
FTS 의 성능 향상시킬려면 innodb_ft_result_cache_limit 파라미터를 조정하면 되며 최대 4GB까지 조정 할 수 있습니다.
mysql> SET GLOBAL innodb_ft_result_cache_limit=4000000000;
3. Reference
Reference book
• Real MySQL 개발자와 DBA를 위한
Reference link
• grip.news/archives/1538
• github.com/innodb-fts-stopword.test
• mysqlserverteam/n-gram-parser
• dev.mysql.com/fulltext-search-ngram
• dev.mysql.com/innodb-ft-index-table-table
• dev.mysql.com/myisam-search-indexes
• dev.mysql.com/innodb-ft-index-cache-table
연관된 다른 글
이번 포스팅은 MySQL 의 전문검색(Fulltext Search) 에 대해서 확인 해보려고 합니다.
Principal DBA(MySQL, AWS Aurora, Oracle)
핀테크 서비스인 핀다에서 데이터베이스를 운영하고 있어요(at finda.co.kr)
Previous - 당근마켓, 위메프, Oracle Korea ACS / Fedora Kor UserGroup 운영중
Database 외에도 NoSQL , Linux , Python, Cloud, Http/PHP CGI 등에도 관심이 있습니다
purityboy83@gmail.com / admin@hoing.io
안녕하세요. 글 잘 읽고 있습니다.
현재 AWS RDS를 사용하고 있는데요. RDS에 mecab plugin을 설치할 수 있는 방법이 있을까요?
Cheking. . .
업데이트 드릴게요
넵! 감사합니다 🙂
안녕하세요
옵션 그룹 확인했을때 사용하기는 어려워 보입니다.
감사합니다.