database.sarang.net에서는 아주 오래 전부터 한글 full text search에 대한 논의를 여러 번 해왔지만, 지금까지 뚜렷한 성과가 없었습니다.
PostgreSQL에서 제공하는 full text search 기능은 꽤 쓸만한데, 한글 형태소 분석기와, 한국어 사전이 마땅치 않아서 아까운 기능을 못 쓰고 있었습니다.
조금은 미흡하지만, 그럭저럭 쓸만한 결과가 나와서 이 글에서 공유합니다.
세부분이 필요합니다.
mecab-ko
mecab 프로젝트의 한국어판이라는데, 뭘 고쳤는지는 모르겠지만, 이 프로그램을 설치합니다.
$ git clone https://bitbucket.org/eunjeon/mecab-ko.git
$ cd mecab-ko
$ ./configure
$ make all && make install
mecab-ko-dic
한국어 사전을 만듭니다. 이놈은 git clone으로 잘 안되네요.
https://bitbucket.org/eunjeon/mecab-ko-dic/downloads
페이지의 마지막 파일을 다운로드하고 압축 풀고 설치합니다.
$ wget https://bitbucket.org/eunjeon/mecab-ko-dic/downloads/mecab-ko-dic-1.6.1-20140814.tar.gz
$ tar xzf mecab-ko-dic-1.6.1-20140814.tar.gz
$ cd mecab-ko-dic-1.6.1-20140814
$ ./configure
$ make all && make install
형태소분석기 테스트
모두 설치가 끝나면 이 프로그램이 정상 작동하는지를 테스트해봅니다.
$ echo '설치가 완료되었습니다.' | mecab
설치 NNG,*,F,설치,*,*,*,*,*
가 JKS,*,F,가,*,*,*,*,*
완료 NNG,*,F,완료,*,*,*,*,*
되 XSV,*,F,되,*,*,*,*,*
었 EP,*,T,었,*,*,*,*,*
습니다 EF,*,F,습니다,*,*,*,*,*
. SF,*,*,*,*,*,*,*,*
EOS
잘 안되면 mecab-ko 프로젝트 작업자에게 연락하세요.
이제 이 프로그램을 textsearch-ja 모듈에 심는 작업을 진행합니다.
textsearch-ja
이 모듈은 PostgreSQL 8.4.x 버전(?)부터 있었던 아주 오래된 일본에서 만든 PostgreSQL 확장 모듈입니다.
일단 설치.
$ wget http://pgfoundry.org/frs/download.php/2943/textsearch_ja-9.0.0.tar.gz
$ tar xzf textsearch_ja-9.0.0.tar.gz
$ cd textsearch_ja-9.0.0
해킹
이 모듈은 일본어 전용으로 만들어졌습니다.
최소한의 변경으로 한국어 지원을 가능하도록 이리 저리 소스를 살펴보고, 다음 정도만 고치면 충분히 한국어 환경에서 사용할 수 있겠더군요.
$ diff textsearch_ja.c textsearch_ko.c
39c39
< #define MECAB_BASIC 6 /* 基本形 */
---
> #define MECAB_BASIC 3 /* 基本形 */
$ diff encoding_utf8.c.orig encoding_utf8.c
453a454,479
> /* for korean */
> static const char JOSA_JKS[] = "JKS,";
> static const char JOSA_JKC[] = "JKC,";
> static const char JOSA_JKG[] = "JKG,";
> static const char JOSA_JKO[] = "JKO,";
> static const char JOSA_JKB[] = "JKB,";
> static const char JOSA_JKV[] = "JKV,";
> static const char JOSA_JKQ[] = "JKQ,";
> static const char JOSA_JX[] = "JX,";
> static const char JOSA_JC[] = "JC,";
> static const char KOSIGN_SF[] = "SF,";
> static const char KOSIGN_SE[] = "SE,";
> static const char KOSIGN_SSO[] = "SSO,";
> static const char KOSIGN_SSC[] = "SSC,";
> static const char KOSIGN_SC[] = "SC,";
> static const char KOSIGN_SY[] = "SY,";
> static const char KONN_NNB[] = "NNB,";
> static const char KONN_NP[] = "NP,";
> static const char KOIC[] = "IC,";
> static const char KOTAIL_EP[] = "EP,";
> static const char KOTAIL_EF[] = "EF,";
> static const char KOTAIL_EC[] = "EC,";
> static const char KOTAIL_ETN[] = "ETN,";
> static const char KOTAIL_ETM[] = "ETM,";
>
>
456,463c482,512
< { lengthof(JOSHI), JOSHI }, /* 助詞 */
< { lengthof(JODOU), JODOU }, /* 助動詞 */
< { lengthof(KIGOU), KIGOU }, /* 記号 */
< { lengthof(BYWORD), BYWORD }, /* 名詞,代名詞 */
< { lengthof(INSUFF), INSUFF }, /* 名詞,非自立 */
< { lengthof(KANDO), KANDO }, /* 感動詞 */
< { lengthof(FILLER), FILLER }, /* フィラー */
< { lengthof(OTHERS), OTHERS }, /* その他 */
---
> // { lengthof(JOSHI), JOSHI }, /* 助詞 */
> // { lengthof(JODOU), JODOU }, /* 助動詞 */
> // { lengthof(KIGOU), KIGOU }, /* 記号 */
> // { lengthof(BYWORD), BYWORD }, /* 名詞,代名詞 */
> // { lengthof(INSUFF), INSUFF }, /* 名詞,非自立 */
> // { lengthof(KANDO), KANDO }, /* 感動詞 */
> // { lengthof(FILLER), FILLER }, /* フィラー */
> // { lengthof(OTHERS), OTHERS }, /* その他 */
> { lengthof(JOSA_JKS)-1, JOSA_JKS }, /* 조사들 */
> { lengthof(JOSA_JKC)-1, JOSA_JKC},
> { lengthof(JOSA_JKG)-1, JOSA_JKG},
> { lengthof(JOSA_JKO)-1, JOSA_JKO},
> { lengthof(JOSA_JKB)-1, JOSA_JKB},
> { lengthof(JOSA_JKV)-1, JOSA_JKV},
> { lengthof(JOSA_JKQ)-1, JOSA_JKQ},
> { lengthof(JOSA_JX)-1, JOSA_JX},
> { lengthof(JOSA_JC)-1, JOSA_JC},
> { lengthof(KOSIGN_SF)-1, KOSIGN_SF}, /* 기호들 */
> { lengthof(KOSIGN_SE)-1, KOSIGN_SE},
> { lengthof(KOSIGN_SSO)-1, KOSIGN_SSO},
> { lengthof(KOSIGN_SSC)-1, KOSIGN_SSC},
> { lengthof(KOSIGN_SC)-1, KOSIGN_SC},
> { lengthof(KOSIGN_SY)-1, KOSIGN_SY},
> { lengthof(KONN_NNB)-1, KONN_NNB}, /* 의존명사 */
> { lengthof(KONN_NP)-1, KONN_NP}, /* 대명사 */
> { lengthof(KOIC)-1, KOIC}, /* 감탄사 */
> { lengthof(KOTAIL_EP)-1, KOTAIL_EP}, /* 선어말어미 */
> { lengthof(KOTAIL_EF)-1, KOTAIL_EF}, /* 종결어미 */
> { lengthof(KOTAIL_EC)-1, KOTAIL_EC}, /* 연결어미 */
> { lengthof(KOTAIL_ETN)-1, KOTAIL_ETN}, /* 명사전성어미 */
> { lengthof(KOTAIL_ETM)-1, KOTAIL_ETM}, /* 관형사전성어미 */
두 개 파일만 수정하고, 모듈을 만듭니다.
$ make USE_PGXS=1
$ make USE_PGXS=1 install
이 작업은 알아서 잘 하세요.
테스트
모듈설치가 잘 끝났으면, 이제 이 모듈을 사용하는 함수와 textsearch 관련 환경 설정을 합니다.
다음은 install_textsearch_ko.sql 파일의 내용입니다.
이것을 실행하면 해당 데이터베이스에 한국어 full text search 기능을 사용할 수 있습니다.
CREATE FUNCTION ts_ja_start(internal, integer) RETURNS internal
LANGUAGE c STRICT
AS '$libdir/textsearch_ja', 'ts_ja_start';
CREATE FUNCTION ts_ja_gettoken(internal, internal, internal) RETURNS internal
LANGUAGE c STRICT
AS '$libdir/textsearch_ja', 'ts_ja_gettoken';
CREATE FUNCTION ts_ja_end(internal) RETURNS void
LANGUAGE c STRICT
AS '$libdir/textsearch_ja', 'ts_ja_end';
CREATE TEXT SEARCH PARSER pg_catalog.korean (
START = ts_ja_start,
GETTOKEN = ts_ja_gettoken,
END = ts_ja_end,
HEADLINE = pg_catalog.prsd_headline,
LEXTYPES = pg_catalog.prsd_lextype
);
CREATE FUNCTION ts_ja_lexize(internal, internal, internal, internal) RETURNS internal
LANGUAGE c STRICT
AS '$libdir/textsearch_ja', 'ts_ja_lexize';
CREATE TEXT SEARCH TEMPLATE pg_catalog.mecab (
LEXIZE = ts_ja_lexize
);
CREATE TEXT SEARCH DICTIONARY pg_catalog.korean_stem (
TEMPLATE = pg_catalog.mecab
);
CREATE TEXT SEARCH CONFIGURATION pg_catalog.korean(PARSER = korean);
ALTER TEXT SEARCH CONFIGURATION pg_catalog.korean ADD MAPPING
FOR email, url, url_path, host, file, version,
sfloat, float, int, uint,
numword, hword_numpart, numhword
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.korean ADD MAPPING
FOR asciiword, hword_asciipart, asciihword
WITH english_stem;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.korean ADD MAPPING
FOR word, hword_part, hword
WITH korean_stem;
CREATE FUNCTION web_query(text) RETURNS text AS
$$
SELECT regexp_replace(regexp_replace(regexp_replace($1,
E'(^|\s+)-', E'\1!', 'g'),
E'\s+OR\s+', '|', 'g'),
E'\s+', '&', 'g');
$$
LANGUAGE sql IMMUTABLE STRICT;
작업 최소화를 위해서 쓸 수 있는 것은 일본 것 그대로 사용했습니다.
이 작업이 다 끝나면, 이제 실재 쿼리를 실행해서 의도된 대로 작동 하는지 확인해봅니다.
$ psql
psql (9.3.5)
Type "help" for help.
postgres=# select to_tsvector('korean','설치가 완료되었습니다');
to_tsvector
--------------------------
'되':3 '설치':1 '완료':2
(1 row)
나머지는 http://postgresql.kr/docs/current/textsearch.html 페이지에서 설명하고 있는 그대로 하면 됩니다.
마무리
아무 생각 없이 막 작업을 하면서 기록을 남겨 놓으니, 글이 많이 어수선 하지만, 개인적으로는 지금까지 작업한 한글 형태소 분석 기반 처리 가운데 그나마 제일 깔끔했습니다.
이 모듈은 엄격하게 따지면, 일본 mecab 프로젝트에 기반을 두고 있는데, 이 프로젝트의 라이선스가 GPL, LGPL, BSD 온갖 라이선스를 다 쓰고 있다고 합니다. 구체적으로 어떻게 쓰고 있고, 실무에서 어떻게 영향을 주는지는 모르겠지만, 일단 KST 프로젝트의 GPL 보다는 좀 더 유연한 것은 확실 할 것 같습니다. 구체적인 라이선스 영향에 대해서는 저보다는 오픈소스 라이선스 전문가가 살펴보는 것이 좋을 것 같습니다.
작업을 마무리 하면서 한 가지 아쉬운 점이 있다면, 동사 활용에 있어 동사 원형을 사용하는 것과 제외 단어들의 설정이 없다는 것입니다. 이 부분에 대한 것은 관심 있는 분의 참여로 남겨두어야 할 것 같습니다. 또한 원래의 mecab 프로젝트와 협업하여, 한국어와 일본어를 아우르는 모습으로 바뀌어가면 더 좋겠죠.