2013년 9월 23일 월요일

JEMalloc vs PTMalloc2

Linux에서는 Posix threading library에 포함되어 있는 PTMalloc2가 기본 메모리 관리자로 사용된다.
Linux의 malloc()나 free() 시스템 콜을 이용해서 메모리를 할당하고 해제한다 하더라도
항상 Linux system call이 호출되는 것이 아니라, PTMalloc2에서는 brk/sbrk/mmap 등을 통해서
큰 메모리 영역을 할당받아서 PTMalloc2 library가 요청된 메모리를 할당해주는 방식으로 처리된다.
그런데, PTMalloc2은 아래와 같은 2가지 단점을 가지고 있다.
- Multithreading(over concurrent 32 ~ 64) model에서 성능 저하
- 고부하(잦은 malloc와 free) 시스템에서 메모리 조각화로 인한 재사용 불가
아주 빈번히 작은 공간의 메모리를 할당받고 해제해야 하는 MySQL 서버에서는 PTMalloc2의
첫번째 단점때문에, 이를 보완하기 위해서 Google에서 개발된 TCMalloc나 JEMalloc를 많이들 검토하고 적용하고 있다.
- TCMalloc : Google에서 개발된 Thread Cache 기반의 메모리 할당 라이브러리로써, Chrome이나 Google 내부 시스템에서 사용중
- JEMalloc : FreeBSD의 기본 Memory Allocator를 Linux용으로 포팅한 것이며, TCMalloc와 같이 Thread cache 기반의 Memory Allocator로 Facebook에서 지원하고 내부적으로 사용하고 있다. 또한 Linux용 Redis도 기본적으로는 JEMalloc를 사용하여 빌드되도록 Makefile이 준비되어 있다.
그런데, MySQL이나 Redis와 같이 메모리 사용이 빈번한 소프트웨어에서 PTMalloc2는 메모리 릭과 같은 현상들을 유발하기도 하는데,
실제 소프트웨어에서 메모리를 할당해서 사용후 운영체제로 반납은 하지만, 너무 조각화되어 있어서 mmap에서 그 메모리를 다른 용도로 재활용하지 못해서
메모리 릭과 같은 현상들이 발생하기도 한다.
kakao의 Story DB에서 나타났던 현상으로, 실제 이런 조각화로 인해서 재사용되지 못하는 메모리 공간이 상당할 것으로 예측된다.
각각 JEMalloc와 PTMalloc2 Memory Allocator를 사용하는 MySQL 서버에 동일한 부하를 하루 정도 가하여
MySQL 서버에서 사용하는 메모리 사용량을 조사해 본 결과는 첨부된 파일과 같다.
두 서버 모두 32GB의 물리적 메모리를 장착하고 있으며, InnoDB Buffer Pool을 20GB로 설정하였다.
이틀정도의 테스트 결과, PTMalloc는 33.00GB 정도의 메모리를 사용했고,JEMalloc는 30.15GB의 메모리를 사용했다.
대략 JEMalloc가 2.85GB 정도의 메모리를 덜 사용했으며, 결과적으로 스왑 사용 시점도 PTMalloc보다는 늦게 발생했다.


참고 사이트


Mysql Memory Temporary Table 생성을 줄이기 위한 JDBC Option

JDBC Driver를 이용해서 MySQL 서버에 접속할 때,
JDBC URL에 아래 옵션을 추가 설정함이 조금은 도움이 될듯 합니다.
==> cacheServerConfiguration=true&elideSetAutoCommits=true
* cacheServerConfiguration
이 값이 기본값은 false이며, JDBC Connection이 만들어질때마다 SHOW COLLATION이나 SHOW VARIABLES 명령이 실행되는데, 이는 MySQL 서버에서 메모리 임시 테이블 조작을 유발하게 됩니다. 그래서 이 값을 강제로 true로 설정하여, 콜레이션이나 설정 변수 값을 JDBC URL 단위로 캐시하도록 합니다. 현재 많은 Connection 생성과 메모리 임시 테이블 조작 작업이 발생하고 있습니다. 이 설정 변경으로 (급격한 성능 향상은 없겠지만) 메모리 임시 테이블의 조작 작업을 반이하로 줄일 수 있을 것으로 보입니다.
* elideSetAutoCommits
이 값 또한 기본값이 false이며, JDBC Client에서 setAutoCommit()이 호출될 때마다 MySQL 서버로 SET autocommit=?명령을 전송하게 됩니다. 그래서 현재 이 명령도 불필요하게 자주 실행되는 편입니다. 이 값을 true로 변경하면 setAutoCommit()으로 설정되는 값과 MySQL 서버의 autocomit값이 다를 때에만 SET autocommit=?명령을 서버로 전송하게 됩니다.
참고로, JDBC 버전은 5.1.16이상으로 진행해 주셔야 중대한 Cache 관련 버그(DB Schema등의 변경이 있을 때, 새롭게 DB정보를 Caching하지 않고 구 Caching 정보를 이용하여 Connection하여 접속 에러 발생)
가 Patch된 것으로 이용하실 수 있습니다.

MySQL 사용에 있어서 Linux Filesystem Cache 문제(메모리 먹는 하마2)

아래의 내용은 Free Memory가 많이 남아 있음에도 불구하고 대량의 swap을 사용하는 것에 대한 문제를 해결한 내용입니다.
Mysql(or MariaDB)에서 사용하는 Innodb_Buffer는 할당된 공간을 미리 확보하는 것이 아니기 때문에 만일 다른 곳에서 메모리를 선점해 버린다면 Innodb_Buffer 사용량이 증가함에 따라 swap 사용량이 늘어난다.
물론 Innodb_Buffer가 미리 할당된 공간을 선점하였다하여도 알 수 없는 이유로 swap 사용량이 늘어나는 경우도 있다.
문제는 위의 두가지 모두 free memory Size가 있음에도 불구하고 발생한다는 것이다.
보통 서버의 메모리 free size라 함은
free -m -t 기준으로
free+cached 라고 들 한다.
왜냐하면 OS는 전체 memory size를 모두 사용하게 되었을 때에는 filesystem cache있는 메모리를 가장 먼저 반환해 주기 때문에 이 부분도 free로 보기 때문이다.
따라서, free size가 90M, cached가 9G이면 해당 DB서버의 free memory size는 9.09G가 된다.
그러나, 함정이 여기에 있었다.
실제 CentOS 5.x는 Filesystem Cache를 가장 우선시 하고 있었으며, 이 때문에 실제 서버의 free memory가 없을 경우
Filsystem Cache의 일부를 반환하는 것이 아닌 memory를 가장 많이 사용하고 있는 application의 일정량을 swap으로 내려버린다.
이것은 일부 mysql 엔진 개발자들 사이에서는 5.x까지의 버그로 인식하고 있으나(현재까지 OS쪽으로는 bug로 reporting 되지는 않았음)
CentOS 6.x에서는 패치가 되었다고는 하나 아직 6.x의 안정성 문제로 현재시점까지 카카오는 테스트 해 보지 않았다.
MySQL community 서버에서 filesystem cache를 사용하는 대표적인 경우는
1. O_DIRECT 미사용시
DataFile, Redo Log, Bin Log, Backup File(해당서버에서 Backup시), 기타 로그(slow-query.log 등등)
2. O_DIRECT 사용시
Redo Log, Bin Log, Backup File(해당서버에서 Backup시), 기타 로그(slow-query.log 등등)
MariaDB 서버에서 filesystem cache를 사용하는 대표적인 경우는
1. ALL_O_DIRECT 미사용시
DataFile, Redo Log, Bin Log, Backup File(해당서버에서 Backup시), 기타 로그(slow-query.log 등등)
2. ALL_O_DIRECT 사용시
Bin Log, Backup File(해당서버에서 Backup시), 기타 로그(slow-query.log 등등)
따라서, 다음의 경우에는 FileSystem Cache가 많이 커지게 된다.
- (ALL_)O_DIRECT를 사용하지 않고 partition table이 많거나 실제 table이 많은 경우
- ALL_O_DIRECT를 사용하지 않고 log group의 size가 크고 많은 경우
- Bin Log의 expire_days가 길어 파일이 많거나 백업 파일이 많은 경우
- 기타 복제 구성을 위한 덤프파일이 존재하는 경우
위의 경우에는 swap이 생길 수 있음을 유의해야 한다.
이를 해결하기 위한 방법으로는 cache unmap 스크립트를 주기적으로 실행하는 방법이 있다.
FileSystem Cache에 지정한 파일은 FileSystem Caching 대상에서 제외하라는 명령으로
카카오에서는 Maria Community의 요시노리 개발자가 작성한 스크립트를 수정하여 적용하였다.
Source : https://github.com/yoshinorim/unmap_mysql_logs
수행 효과는 다음과 같다.

MySQL table cache (메모리 먹는 하마)

일반적으로 InnoDB를 주로 MySQL 서버에서 메모리 용량 산정을 할 때에는 컨넥션의 수와 각 컨넥션이 할당받아서 사용할 수 있는 세션 버퍼들을 기준으로 계산하게 된다.
메모리 용량 산정에서 대부분 SortBuffer나 JoinBuffer 그리고 ReadBuffer, Temporary table 크기 등을 기준으로 판단하는 것이 일반적이다.
이러한 세션 버퍼들은 대부분 초기 지정된 아주 작은 크기의 메모리를 할당받아서 필요한 경우 조금씩 더 할당받는 용도로 사용된다.
(c.f. 물론 MySQL 서버가 처리 대상 레코드 건수를 잘못 예측함으로 인해서 불필요한 메모리 할당이 발생하기도 하지만, 이것은 이 게시물에서는 언급하지 않도록 하겠다.)
이와 같이 필요한 만큼씩만 할당받아서 사용하는 방식에서는 이론적으로 계산한 필요 메모리 사용량과 실제 서비스시에 사용되는 메모리 사용량이 2~30GB 정도 차이가 나버리기 때문에, 메모리 용량 산정이라는 것이 쉽지 않을뿐만 아니라, 이런 이론적인 계산이 아무런 의미가 없고 경험 기반으로 판단해야 할 때가 더 많으며,  그냥 대략적으로 InnoDB Buffer pool로 대 부분의 메모리를 할당하고 Client의 요청을 처리하는 Foreground thread를 위해서 대략 3~5GB 정도를 남겨두는 방식으로 메모리 설정하고 서비스에 투입하게 된다.
그런데, MySQL 서버에서는 아주 엉뚱한 곳에서 엄청난 량의 메모리를 사용해 버리는 경향이 있다.
대 부분은 사용자들이 거의 관심을 가지지 않는 Table cache라는 것이 있는데, 이는 MySQL 서버에서 사용되어지는 각 테이블의 메타 정보를 담고 있는 일종의 캐시이다.
사실 Table cache는 어떻게 작동하는지 어떤 용도로 메모리를 할당하는지 알고 있는 사용자도 거의 없을 정도로, DBA나 사용자들로부터 관심 대상이 아니었다.
MySQL 서버에서는 테이블을 읽고 쓰기 위해서는 항상 테이블을 열어야 하며, 사용이 완료되면 닫아야 한다.
이러한 테이블의 열고 닫는 작업은 어느 정도의 부하를 유발하기 때문에, 오픈된 테이블의 정보를 Table cache에 담아두고 각 컨넥션에서 공유해서 사용하게 된다.
하지만, 동시에 테이블에 접근하게 되면 이 캐시는 동시에 공유되지 못하므로 생각보다 더 많은 테이블이 오픈되어야 하며 Table cache의 정보는 더 많이 필요하게 된다.
그런데, 이 Table cache는 각 테이블에 대해서 레코드의 최대 크기만큼의 버퍼를 3개씩 더 할당해서 가지고 있게 된다. (이는 최신 버전인 MySQL 5.5.30에서도 공히 적용되는 방식이다.)
많은 사용자가 “그게 얼마나 메모리를 차지할까?”라고 생각할 것이다. 그렇다 일반적으로 테이블이 2~30개 가진 MySQL 서버에서 컨넥션 2~300개 정도를 사용하는 서비스라면
전혀 문제되지 않으며, 고려할 필요도 없다.
하지만, 이러한 테이블들의 개수가 1~2000개를 넘어서고 컨넥션까지 많이 가진다면 상당히 심각한 메모리 부족현상을 겪게 될 것이다.
또한 이러한 현상은 각 테이블이 수백개 이상의 파티션들을 가진다면 더 큰 문제를 유발하게 될 것이다.
간단한 확인을 위해서 아래와 같은 테이블을 살펴 보자.
CREATE TABLE `tb_part0_varchar200` (
`fdpk` int(11) DEFAULT NULL,
`fd` varchar(200) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `tb_part0_varchar2000` (
`fdpk` int(11) DEFAULT NULL,
`fd` varchar(2000) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `tb_part0_varchar20000` (
`fdpk` int(11) DEFAULT NULL,
`fd` varchar(20000) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `tb_part0_mediumtext` (
`fdpk` int(11) DEFAULT NULL,
`fd` mediumtext DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
각각의 테이블에는 레코드를 한건씩만 아래와 같이 INSERT 해두었다.
INSERT INTO tb_part0_varchar200 VALUES (1,’1′);
INSERT INTO tb_part0_varchar2000 VALUES (1,’1′);
INSERT INTO tb_part0_varchar20000 VALUES (1,’1′);
INSERT INTO tb_part0_mediumtext VALUES (1,’1′);
그리고, 위의 테이블과 컬럼을 동일하게 가지지만 10개, 100개, 500개, 1000개의 파티션으로 구성된 테이블까지 아래와 같이 20개의 테이블을 준비했다.
+————————–+
| Tables_in_test |
+————————–+
| tb_part0_mediumtext |
| tb_part0_varchar200 |
| tb_part0_varchar2000 |
| tb_part0_varchar20000 |
| tb_part1000_mediumtext |
| tb_part1000_varchar200 |
| tb_part1000_varchar2000 |
| tb_part1000_varchar20000 |
| tb_part100_mediumtext |
| tb_part100_varchar200 |
| tb_part100_varchar2000 |
| tb_part100_varchar20000 |
| tb_part10_mediumtext |
| tb_part10_varchar200 |
| tb_part10_varchar2000 |
| tb_part10_varchar20000 |
| tb_part500_mediumtext |
| tb_part500_varchar200 |
| tb_part500_varchar2000 |
| tb_part500_varchar20000 |
+————————–+
이제 컨넥션 40개를 열어서, 동시에 아래와 같이 1건의 레코드만 조회하는 쿼리를 실행해보자. 물론 각 테스트에서 40개의 컨넥션은 모두 동일하게 하나의 테이블씩만 조회한다. (즉, 테스트 대상 테이블 1개를 제외한 나머지 테이블들은 전혀 영향을 미치지 않도록 했다)
(쿼리를 조금 더 오랫동안 실행시켜 두기 위해서 sleep 함수를 사용했다.)
mysql> select fdpk, sleep(20) from ${TARGET_TABLE} where fdpk=1
이 쿼리를 처리하기 위해서 MySQL 서버가 할당하는 메모리에 대해서 프로파일링해 본 결과는 아래와 같다. 그림에서 dict_load_table은 InnoDB 딕셔너리를 위해서 할당된 메모리 공간을 의미하며, open_table은 MySQL이 Table cache를 통해서 테이블을 열고 닫는데 사용한 메모리 공간을 의미한다. (실제 dict_load_table과 open_table은 MySQL 서버의 소스 코드상에서 메모리 할당을 수행하는 함수의 이름이다.)
(만약 직접 MySQL Server의 Memory allocation profiling해 보고자 한다면, 가장 손쉬운 방법은 Memory alloc profiling기능을 가진 3rd party memory allocator를 이용하면 된다.)

위의 결과에서도 알 수 있듯이,
1000개의 파티션을 가지는 테이블 하나에서 레코드 한건을 SELECT하기 위해서 2~300MB씩 메모리가 필요할 수도 있다는 것에 주의해야 하며,
이런 쿼리들이 동시에 2~300개씩 실행된다면 서버의 대 부분 메모리를 Table cache를 위해서 낭비해버리게 될 것이다.
이렇게 Table cache를 위해서 할당된 메모리는 쉽게 해제되지 않고 그대로 유지되는 경우가 많으며, 이 메모리를 다시 운영체제에게 반납하도록 하기 위해서는 “FLUSH TABLES”라는 명령을 실행하면 된다.
하지만, 이 명령은 컨넥션의 개수가 많아지면 몇십초에서 1~2분까지의 시간이 걸리기도 하며, 그 동안 모든 컨넥션의 쿼리는 Blocking된다.
“FLUSH TABLES”명령은 “FLUSH TABLES WITH READ LOCK”이라는 Database Global lock과 비슷하다고 보면 된다.
나름의 Workaround이긴 하지만, 서비스용 데이터베이스에서는 절대 사용할 수 없는 명령인 것이다.
결론적으로, MySQL 서버에서 사용되는 메모리는 세션 버퍼뿐만 Table cache까지 같이 고려해서 용량 산정을 해야 한다.
만약 컨넥션이나 테이블의 개수가 많다면, 더더욱 Table cache에 집중해서 용량 산정을 해야 할 필요가 있는 것이다.
MySQL에서는 파티션도 하나의 테이블로 간주되므로, 테이블의 개수는 적더라도 파티션의 개수가 많다면 Table cache에 대한 고려는 필수적이라고 볼 수 있다.
사실 용량 산정뿐만 아니라, 개발 시점이나 모델링 시점에서도 이 부분을 고려해야 한다.
테이블에 VARCHAR(10)을 사용하는 것과 VARCHAR(100)을 사용하는 것은 최악의 경우 Table cache만으로 10배의 메모리를 소진해버릴 수도 있는 것이다.
가능하다면, DBA와 협의하여 테이블의 구조나 개수 그리고 컨넥션의 개수 등올 메모리 용량 산정을 필수적으로 진행하는 것이 좋을 것이다.

Fulltext search (Mroonga)


전문 검색 엔진

MySQL 서버에 내장된 전문 검색 엔진은 DELIMITER 방식의 단어 파싱만 제공되고 있어서 사용자들이 띄어쓰기나 문장 기호를 명확히 입력하지 않을 때에는 검색이 불가한 경우가 많다.
그래서 실제로 MySQL의 Builtin fulltext search engine은 많이 사용되지 않으며, 더불어 MySQL builtin fulltext search는 InnoDB 스토리지 엔진을 사용할 수 없는 것도 한가지 문제이다.

이런 단점들을 보완하기 위해서, Lucene나 Sphinx를 도입해서 응용 프로그램 레벨에서 이를 구현하기도 하는데, 이는 많은 개발 코드를 필요로 한다.
그래서 MySQL 서버에 플러그인 형태로 내장되어서 스토리지 엔진 형태로 제공되는 Groonga 라는 fulltext search engine을 검토했으며, 현재 부하가 높지 않은 몇몇 서비스에 적용을 하고 있거나 도입을 검토중이다.

Groonga는 Lucene과 같이 fulltext search engine은 Standalone으로 작동하는 전문 검색 엔진이며,
MySQL Mroonga라는 MySQL 플러그인 스토리지 엔진 fulltext search engine도 제공된다.
Mroonga는 Groonga 라이브러리를 이용해서 MySQL 서버에 플러그인 형태로 내장되는 스토리지 엔진 플러그인이다.

Mroonga는 DELIMITER 형식뿐만 아니라, n-Gram 방식의 형태소 분석및 검색 기능도 제공하며, 일본에서 개발되어서 CJK Language에 더욱 적합하다는 특성도 가지고 있다.
n-Gram 방식의 인덱스를 가지므로 인덱스의 크기가 MySQL Builtin fulltext search engine보다는 크지만, 실제 검색의 Quality나 Performance는 훨씬 낮다는 것이 검증된 상태이다.
참고로 Groonga와 Mroonga는 일본에서 mobage를 운영하는 DeNA라는 회사에서 개발되었다.


전문 검색 엔진 설치
1) 우선 필요한 모듈을 다운로드한다.
groonga-devel-3.0.6-1.x86_64.rpm
Mroonga/groonga-libs-3.0.6-1.x86_64.rpm
groonga-tokenizer-mecab-3.0.6-1.x86_64.rpm
groonga-normalizer-mysql-1.0.5-1.x86_64.rpm
mecab-0.996-1.x86_64.rpm
mecab-ipadic-2.7.0.20070801-6.1.x86_64.rpm
mroonga-3.06.tar.gz
mariadb-5.5.30.tar.gz 
2) 다운로드된  RPM 파일을 필요한 것만 선별적으로 설치한다.
rpm -Uvh groonga-libs-3.0.6-1.x86_64.rpm
rpm -Uvh groonga-devel-3.0.6-1.x86_64.rpm
rpm -Uvh mecab-0.996-1.x86_64.rpm
rpm -Uvh mecab-ipadic-2.7.0.20070801-6.1.x86_64.rpm
rpm -Uvh groonga-tokenizer-mecab-3.0.6-1.x86_64.rpm
rpm -Uvh groonga-normalizer-mysql-1.0.5-1.x86_64.rpm
3) Mroonga와 MariaDB의 소스 코드를 압축 해제한다.
tar zxvf mariadb-5.5.30.tar.gz
tar xzvf mroonga-3.06.tar.gz 
4) Mroonga 소스 디렉토리로 이동한 후, Configure 후 Build를 실행한다.
cd mroonga-3.06
./configure \
--with-mysql-source=/root/install/mariadb-5.5.30 \
--with-mysql-config=/usr/local/mysql/bin/mysql_config

make
5) 빌드가 완료되면, Mroonga plugin shared object를 설치하고, MariaDB의 플러그인 디렉토리에 복사되었는지 확인한다.
make install
ls /usr/local/mysql/lib/plugin/
service mysql restart
*** MySQL 재시작해야 위에서 설치한 rpm을 정상적으로 로드합니다. ***
6) MariaDB에 로그인해서, mroonga 플러그인과 관련 UDF들을 설치한다.
DELETE IGNORE FROM mysql.plugin WHERE name = 'mroonga';
INSTALL PLUGIN mroonga SONAME 'ha_mroonga.so';
CREATE FUNCTION last_insert_grn_id RETURNS INTEGER soname 'ha_mroonga.so';
CREATE FUNCTION mroonga_snippet RETURNS STRING soname 'ha_mroonga.so';

7) 설치가 완료되면, show plugins; show engines; 명령을 이용해서 설치된 Mroonga 스토리지 엔진에 이상이 없는지 확인한다.

전문 검색 엔진용 테이블 생성

0) Mroonga가 Wapper mode와 Storage mode중에서 어떤 모드로 작동할지 선택

  

1) 전문 검색용 테이블 생성
   테이블 생성시, 전문 검색 파서는 일반적으로 "TokenBigramIgnoreBlankSplitSymbolAlphaDigit" 선택 (자세한 내용은 메뉴얼 참조) 
CREATE TABLE tb_test (
    id INT PRIMARY KEY AUTO_INCREMENT,
    content VARCHAR(255),
    FULLTEXT INDEX fx_content (content) COMMENT 'parser "TokenBigramIgnoreBlankSplitSymbolAlphaDigit"'
) ENGINE=mroonga COMMENT='engine "innodb"' DEFAULT CHARSET utf8mb4;

2) 테스트 데이터 적재
INSERT INTO tb_test (content) VALUES ("오늘은 MySQL 공부를 했습니다. 내일도 MySQL 공부를 할 것입니다.");
INSERT INTO tb_test (content) VALUES ("오늘은 MySQL 공부를 했지만, 내일은 Sqlite 공부를 할 것입니다.");

3) 전문 검색 쿼리 테스트
SELECT *, MATCH (content) AGAINST ('MySQL' IN BOOLEAN MODE) as score
FROM tb_test
WHERE MATCH (content) AGAINST ('MySQL' IN BOOLEAN MODE)
ORDER BY MATCH (content) AGAINST ('MySQL' IN BOOLEAN MODE) DESC;
+----+-------------------------------------------------------------------------------------+-------+
| id | content | score |
+----+-------------------------------------------------------------------------------------+-------+
| 1 | 오늘은 MySQL 공부를 했습니다. 내일도 MySQL 공부를 할 것입니다. | 2 |
| 2 | 오늘은 MySQL 공부를 했지만, 내일은 Sqlite 공부를 할 것입니다. | 1 |
+----+-------------------------------------------------------------------------------------+-------+
SELECT *, MATCH (content) AGAINST ('ySQL' IN BOOLEAN MODE) as score
FROM tb_test
WHERE MATCH (content) AGAINST ('ySQL' IN BOOLEAN MODE)
ORDER BY MATCH (content) AGAINST ('ySQL' IN BOOLEAN MODE) DESC;
+----+-------------------------------------------------------------------------------------+-------+
| id | content | score |
+----+-------------------------------------------------------------------------------------+-------+
| 1 | 오늘은 MySQL 공부를 했습니다. 내일도 MySQL 공부를 할 것입니다. | 2 |
| 2 | 오늘은 MySQL 공부를 했지만, 내일은 Sqlite 공부를 할 것입니다. | 1 |
+----+-------------------------------------------------------------------------------------+-------+

참고 URL



적용된 서버

  • KakaoPage의 컨텐츠 검색 (USER :: t_ftsearch)
  • 날씨 테이블의 지역명 검색
  • 사내용 아지트의 게시물 검색 (예정)

주의 사항

1) 테이블은 반드시 옵션을 명시적으로 주고 생성
CREATE TABLE `keywords` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `keyword` varchar(1024) DEFAULT NULL,
 `city_id` int(11) NOT NULL,
 `created_at` datetime NOT NULL,
 `updated_at` datetime NOT NULL,
 PRIMARY KEY (`id`),
 FULLTEXT KEY `fx_keyword` (`keyword`) COMMENT 'parser "TokenBigramIgnoreBlankSplitSymbolAlphaDigit"' 
) ENGINE=mroonga DEFAULT CHARSET=utf8mb4 COMMENT='engine "innodb"'; 
2) 테이블 스키마 변경 시에도 기존에 있는 옵션을 명시적으로 주고 진행
ALTER TABLE keywords modify keyword varchar(2048), ENGINE=mroonga DEFAULT CHARSET=utf8mb4 COMMENT='engine "innodb"'; 
현재, mroonga만 명시한 테이블에 위와 같이 ALTER 구문 실행 시 DB가 종료되는 버그가 있음 (최신 버전에서도 동일 이슈 확인)