Featured image of post 8. 인덱스 (3) - 전문 검색 인덱스

8. 인덱스 (3) - 전문 검색 인덱스

Real MySQL 8.0

문서의 내용 전체를 인덱스화해서 특정 키워드가 포함된 문서를 검색하는 전문(Full Text) 검색에는 InnoDB나 MyISAM 스토리지 엔진에서 제공하는 일반적인 용도의 B-Tree 인덱스를 사용할 수 없다.

문서 전체에 대한 분석과 검색을 위한 인덱싱 알고리즘을 전문 검색(Full Text Search) 인덱스라고 하는데, 전문 검색 인덱스는 일반화된 기능의 명칭으로 알고리즘의 이름을 지칭하는 것은 아니다.

인덱스 알고리즘

전문 검색에서는 문서 본문의 내용에서 사용자가 검색하게 될 키워드를 분석하고, 빠른 검색용으로 사용할 수 있게 이러한 키워드로 인덱스를 구축한다. 키워드의 분석 및 인덱스 구축에는 여러 가지 방법이 있을 수 있다.

전문 검색 인덱스는 문서의 키워드를 인덱싱하는 기법에 따라 구분될 수 있다.

  • 어근 분석 알고리즘
  • n-gram 알고리즘

어근 분석 알고리즘

MySQL 서버의 전문 검색 인덱스는 다음과 같은 두 가지 중요한 과정을 거쳐서 색인 작업이 수행된다.

  • 불용어(Stop Word)처리
    • 검색에서 별 가치가 없는 단어를 모두 필터링해서 제거하는 작업을 의미
    • 불용어의 개수가 많지 않기 때문에 알고리즘을 구현한 코드에 상수로 정의해 사용하는 경우가 많다.
    • 유연성을 위해 불용어 자체를 데이터베이스화해서 사용자가 추가하거나 삭제할 수 있게 구현하는 경우도 있다.
  • 어근 분석(Stemming)
    • 검색어로 선정된 단어의 뿌리인 원형을 찾는 작업이다.
    • MySQL 서버는 오픈소스 형태소 분석 라이브러리인 MeCab을 플러그인 형태로 사용할 수 있게 지원한다.
    • 한글이나 일본어의 경우 영어와 같이 단어의 변형 자체는 거의 없기 때문에 어근분석보다는 문장의 형태소를 분석해서 명사와 조사를 구분하는 기능이 더 중요한 편이다.

n-gram 알고리즘

MeCab을 위한 형태소 분석은 매우 전문적인 전문 검색 알고리즘이어서 만족할 만한 결과를 내기 위해서는 많은 노력과 시간을 필요로 한다. 전문적인 검색 엔진을 고려하는 것이 아니라면 범용적으로 적용하기는 쉽지 않기 때문에 이러한 단점을 보완하기 위한 방법으로 n-gram 알고리즘이 도입되었다.

n-gram이란 본문을 무조건 몇 글자씩 잘라서 인덱싱하는 방법이다. 형태소 분석보다는 알고리즘이 단순하고 국가별 언어에 대한 이해와 준비 작업이 필요 없는 반면, 만들어진 인덱스의 크기는 상당히 큰 편이다. n은 인덱싱할 키워드의 최소 글자 수를 의미하는데, 일반적으로는 2글자 단위로 키워드를 쪼개서 인덱싱하는 2-gram(Bi-gram) 방식이 많이 사용된다.

1
To be or not to be. That is the question

각 단어는 띄어쓰기와 마침표를 기준으로 10개의 단어로 구분되고, 2글자씩 중첩해서 토큰으로 분리된다.

단어bi-gram 토큰
ToTo
bebe
oror
notno ot
toto
bebe
thatTh ha at
isis
theth he
questionqu ue es st ti io on

각 글자가 중첩되어 토큰화 되기 때문에 Bi-gram 알고리즘에서는 글자수 - 1개의 토큰으로 구분된다. 이렇게 구분된 토큰을 인덱스에 저장한다. 이때 중복된 토큰은 하나의 인덱스 엔트리로 병합되어 저장한다.

MySQL 서버는 이렇게 생성된 토큰들에 대해서 불용어를 걸러내는 작업을 수행하는데, 이때 불용어와 동일하거나 불용어를 포함하는 경우 걸러서 버린다. 기본적으로 MySQL 서버에 내장된 불용어는 information_schema.innodb_ft_default_stopword 테이블을 통해 확인 가능하다.

입력불용어 일치불용어 포함출력(최종 인덱스 등록)
atO
beO
beO
eset
haO
hehe
he
ioO
isO
nono
onO
orO
otot
ququ
stst
ThTh
thth
tiO
ToO
toO
ueue

전문 검색을 더 빠르게 하기 위해 2단계 인덱싱(프론트엔드와 백엔드 인덱스)과 같은 방법도 있지만 MySQL 서버는 구분된 토큰을 단순한 B-Tree 인덱스에 저장한다.

불용어 변경 및 삭제

n-gram의 토큰 파싱 및 불용어 처리 예시 결과를 보면 “ti”, “at”, “ha” 같은 토큰들은 “a”, “i” 철자가 불용어로 등록돼 있기 때문에 모두 걸러진다. 실제로 이 같은 불용어 처리는 사용자에게 도움이 되기보다는 사용자를 더 혼란스럽게 하는 기능일 수도 있다. 그래서 불용어 처리 자체를 완전히 무시하거나 MySQL 서버에 내장된 불용어 대신 사용자가 직접 불용어를 등록하는 방법을 권장한다.

전문 검색 인덱스의 불용어 처리 무시

  • 스토리지 엔진과 관계 없이 MySQL 서버의 모든 전문 검색 인덱스에 대해 불용어를 완전히 제거한다.
    • MySQL 서버의 설정 파일(my.cnf)의 ft_storpword_file 시스템 변수에 빈 문자열을 설정한다.
    • 해당 시스템 변수는 서버가 재시작될 때만 인지하기 때문에 설정 변경시 서버를 재시작해야 반영된다.
    • 사용자 정의 불용어를 적용할 때도 해당 파일 경로를 적용하여 반영할 수 있다.
  • InnoDB 스토리지 엔진을 사용하는 테이블의 전문 검색 인덱스에 대해서만 불용어 처리 무시
    • innodb_ft_enable_stopword 시스템 변수를 OFF로 설정한다.
    • 해당 시스템 변수는 동적인 시스템 변수이므로 서버가 실행 중인 상태에서도 변경할 수 있다.

사용자 정의 불용어 사용

  • 불용어 목록을 파일로 저장하고, MySQL 서버 설정파일에서 파일의 경로를 ft_stopword_file 설정에 등록한다.
  • InnoDB 스토리지 엔진을 사용하는 테이블의 전문 검색 엔진에만 사용할 수 있는데, innodb_ft_server_stopword_table 시스템 변수에 불용어 테이블을 설정한다.
    • 이때 불용어 목록을 변경한 이후 전문 검색 인덱스가 생성돼야만 변경된 불용어가 적용된다.
1
2
3
4
5
6
CREATE TABLE my_stopword(value VARCHAR(30)) ENGINE = INNODB;
INSERT INTO my_stopword(value) VALUES ('MySQL');

SET GLOBAL innodb_ft_server_stopword_table='mydb/my_stopword';
ALTER TABLE tb_bi_gram
    ADD FULLTEXT INDEX fx_title_body(title, body) WITH PARSER ngram;

innodb_ft_user_stopword_table 시스템 변수를 이용하는 방법도 있으며, innodb_ft_server_stopword_table와 사용법이 동일하다. 여러 전문 검색 인덱스가 서로 다른 불용어를 사용해야 하는 경우에 활용할 수 있다.

전문 검색 엔진 인덱스의 가용성

전문 검색 인덱스를 사용하려면 반드시 2가지 조건을 만족해야 한다.

  • 쿼리 문장이 전문 검색을 위한 문법(MATCH ... AGAINST)을 사용
  • 테이블이 전문 검색 대상 컬럼에 대해서 전문 인덱스 보유
1
2
3
4
5
6
7
CREATE TABLE tb_test
(
    doc_id   INT,
    doc_body TEXT,
    PRIMARY KEY (doc_id),
    FULLTEXT KEY fx_docbody (doc_body) WITH PARSER ngram
) ENGINE=InnoDB;

1
2
3
4
5
6
/*
 풀 테이블 스캔으로 처리되는 쿼리
 */
SELECT * 
FROM tb_test 
WHERE doc_body LIKE '%애플%';

1
2
3
4
5
6
/*
 전문 검색 인덱스로 처리되는 쿼리
 */
SELECT * 
FROM tb_test 
WHERE MATCH(doc_body) AGAINST ('애플' IN BOOLEAN MODE);

전문 검색 인덱스를 사용하려면 반드시 MATCH ... AGAINST ...구문으로 검색 쿼리를 작성해야 하며, 인덱스를 구성하는 컬럼들은 MATCH절의 괄호 안에 모두 명시되어야 한다.