Sphinx 검색엔진 3부 - 검색 및 설정




검색을 해 봅시다.



이제 모든게 갖추어 졌으니, 검색을 해 보도록 하겠습니다.

아래처럼 sphinx 에 접속합니다.

[und3r@sungwook ~]$ mysql -h0 -P9306



현재 words_kr 테이블에는 abc 가 존재합니다. 검색해 보도록 하겠습니다.

mysql> select * from words_kr where match('internet');

+------+----------+

| id   | word     |

+------+----------+

|   59 | internet |

+------+----------+

1 row in set (0.00 sec)



보시는것 처럼 where match() 형태로 검색을 합니다. 지금의 경우 대상 column 이 word 하나 밖에 없어서 예로 적절치는 않습니다만, 만약 대상 column 이 2개 이상이라면 어떻게 될까요? 어떤 column 에서 찾는걸까요?

위와 같이, 별도로 명시하지 않는 경우, 모든 대상 컬럼(sql_field_*)으로부터 검색을 합니다. 명시적으로 특정 column 에서만 검색하려면 아래처럼 대상컬럼을 적어주어야 합니다.


mysql> select * from words_kr where match('@word internet');

+------+----------+

| id   | word     |

+------+----------+

|   59 | internet |

+------+----------+

1 row in set (0.00 sec)




지금의 경우는 100% 일치할 경우입니다. 아래처럼 비슷한 경우는 검색되지 않습니다.

mysql> select * from words_kr where match('inter');

Empty set (0.00 sec)


mysql> select * from words_kr where match('inter*');

Empty set (0.00 sec)






와일드 케릭터(*) 사용하기



부분 일치되는 것도 검색되게 하려면 어떻게 해야 할까요?

이것이 가능하게 하려면 sphinx.conf 파일을 수정해 주어야 합니다.

각 index 에 아래처럼 min_infix_len 을 넣어줍니다.

index words_kr

{

    min_infix_len   = 3

    source          = words_kr

    path            = /var/lib/sphinx/words_kr

}


min_infix_len 는 '검색할 때 반드시 매칭되어야 하는 최소 문자 수' 정도로 생각하시면 됩니다.

위처럼 값이 3일때는, internet 을 검색하기 위해서 최소 세문자가 일치할 경우에만 검색된다는 것이죠.

아래 예를 보시면 쉽게 이해가 되실겁니다.


참, 반드시 conf 가 변경되면, indexer 를 통해서 다시 indexing 해 주어야 합니다.

[und3r@sungwook ~]$ sudo indexer -c /etc/sphinx/sphinx.conf --rotate --all



mysql> select * from words_kr where match('int*');

+------+----------+

| id   | word     |

+------+----------+

|   59 | internet |

+------+----------+

1 row in set (0.00 sec)


mysql> select * from words_kr where match('in*');

Empty set, 1 warning (0.00 sec)


mysql> select * from words_kr where match('*nte*');

+------+----------+

| id   | word     |

+------+----------+

|   59 | internet |

+------+----------+

1 row in set (0.00 sec)


mysql> select * from words_kr where match('*nt*');

Empty set, 1 warning (0.00 sec)



최소 한글자로 검색하고 싶다면, min_infix_len 을 1 로 세팅하시면 됩니다.




한글(unicode) 검색하기



기존 방식대로 한글 검색을 해 봅시다.


mysql> select * from words_kr where match('소나기');

Empty set (0.00 sec)


왜 검색이 안될까요?

바로 charset_table 이라는 녀석 때문입니다. 이것이 키워드를 뽑아낼 때, 어떤 문자를 허용할건지, 말 것인지를 결정합니다. sphinx 에서 기본값은 latin 과 cyrillic 문자 입니다. 그렇기 때문에 한글은 토큰화 되지 않고, 검색이 불가능 한 것입니다. 다시말해 라틴(latin)과 키릴(cyrillic)문자 이외에는 검색이 불가능하다는 이야깁니다. 특수문자, 일본어, 한자, 한글 등등 검색이 안됩니다.


이 문제를 해결하기 위해서는 charset_table 를 설정해 주면 됩니다. 이 값에 토큰화 시킬 대상의 문자들을 입력해 주면 됩니다, 만약 한글 '가', '나' 를 추가해 주고 싶다면, 아래처럼 해주면 됩니다.

charset_table = U+AC00, U+B098


콤마로 구분해서 하나씩 써 주면 되는데, 위와 같이 유니코드로 적어주어야 합니다. 유니코드는 위키에서 참고하시면 됩니다.

http://ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C

그렇다면 모든 한글을 다 일일이 이렇게 입력해 주어야 하느냐... 인데, 아닙니다. 쉽게 쓸수 있도록 나름의 문법을 지원합니다.

해당 링크나 아래 그림을 참고하시기 바랍니다.





그럼 한글을 지원하고 싶다면, 어떻게 할까요? 한글의 유니코드 범위를 모두 작성해 주면 됩니다.

자음/모음/조합된 한글의 유니코드의 범위는 아래와 같습니다.

U+AC00..U+D7A3, U+1100..U+1159, U+1161..U+11A2, U+11A8..U+11F9


여기에 숫자, 알파벳을 추가하면 아래와 같이 됩니다.

charset_table = 0..9, A..Z->a..z, a..z, U+AC00..U+D7A3, U+1100..U+1159, U+1161..U+11A2, U+11A8..U+11F9


일본어, 중국어 이외에 특수문자 등도 검색이 가능하게 하려면, 이곳 charset_table 에 추가해 주면 됩니다.


+추가내용

위에 나와있듯이, 검색대상을 직접 지정해 주어야 합니다. A..Z->a..z 의 의미는 A부터 Z 까지를 a부터 z 와 동일하게 취급하겠다는 이야깁니다. 다시말해서 대문자를 소문자로 처리하겠다는 것이죠. 이렇게 하면 대소문자 구분없이 검색이 가능해 집니다. 내부적으로는 대문자를 소문자로 변환해서 저장(?) 한다고 생각하시면 됩니다. 그런데 주의할점이 있습니다. A..Z->a..z 는 대문자를 소문자로 취급하겠다는 것이 전부입니다. 검색 대상은 a..z 처럼 따로 등록해 주어야 합니다. 대문자를 소문자로 mapping 만 할뿐, 변환된 소문자를 검색 대상으로 '등록' 해 주는 작업은 별개의 일이라는 말입니다.




U+FF01..U+FF5E->U+0021..U+007E, U+0021..U+0040, U+0041..U+005A->U+0061..U+007A, U+0061..U+007A, U+005B..U+0060, U+007B..U+007E


위 내용을 분석해 보죠. U+FF01..U+FF5E 는 아래 영역의 문자입니다.



단순하게 ASCII 문자들 처럼 보이지만 다릅니다. ! 의 차이점입니다. 우리가 쓰는 ASCII 는 아래 영역입니다.



분명 모양은 같은데, 실제로는 다른 유니코드지요. 즉, 검색이 안된다는 이야깁니다. 이때 필요한 기능이 mapping 입니다. U+FF01..U+FF5E->U+0021..U+007E 이것이 바로 그 역할을 합니다. 앞서 말씀드린대로 이것은 mapping 하는 것 뿐이고, U+0021..U+007E 를 검색 대상으로 등록은 별도로 해 주어야 합니다.




U+FF01..U+FF5E->U+0021..U+007E, U+0021..U+0040, U+0041..U+005A->U+0061..U+007A, U+0061..U+007A, U+005B..U+0060, U+007B..U+007E


위 코드를 다시 한번 살펴 봅시다. U+FF01..U+FF5E 의 영역을 U+0021..U+007E 과 동일하게 취급하겠다는 이야깁니다. 그리고 U+0021..U+0040 영역인 특수문자 + 숫자 영역을 검색대상으로 등록하겠다는 이야기고요. U+0041..U+005A->U+0061..U+007A 는 대문자를 소문자와 동일하게 취급하겠다는 이야깁니다. 그 이후 U+0061..U+007A 인 소문자를 검색 대상에 추가하겠다는 이야깁니다. U+005B..U+0060 와 U+007B..U+007E 는 해당 문자를 검색 영역에 등록하는 작업이 되겠습니다.


주의할점은 U+0020 처럼 등록되지 않은 문자를 추가해서는 안됩니다. 이런 문자가 UNICODE TABLE 에 보면 여러개 존재하는데 충돌납니다.




돌아와서 이렇게 indexer 로 다시 indexing 하고나면, 아래처럼 한글 검색이 잘 되는것을 볼 수 있습니다.

mysql> select * from words_kr where match('소나기');

+------+-----------+

| id   | word      |

+------+-----------+

|    4 | 소나기    |

+------+-----------+

1 row in set (0.00 sec)


mysql> select * from words_kr where match('소나*');

+------+-----------+

| id   | word      |

+------+-----------+

|    4 | 소나기    |

+------+-----------+

1 row in set (0.00 sec)


mysql> select * from words_kr where match('*나기');

+------+-----------+

| id   | word      |

+------+-----------+

|    4 | 소나기    |

+------+-----------+

1 row in set (0.00 sec)





중국어(한자), 일본어 등 검색하기



한국어와 마찬가지로, 중국어 일본어들도 charset_table 에 추가해 주어야 합니다. 유니코드 범위를 찾아서 등록해 주면 되는데, 비어있는(reserved) 유니코드는 charset_table 에 포함되지 않도록 주의해야 합니다.


한 가지 중요한게 있습니다. 중복되는 문자에 대해서 동일하게 인식되도록 해 주어야 합니다. 이해가 되시나요?

문자 '7'(U+0037) 이 있습니다. 그런데 '7'(U+FF17) 도 있습니다. 문자 'a'(U+0061) 가 있습니다. 그런데 ''(U+FF41)도 있습니다. 엄밀히 보면 두 문자는 다릅니다. 그런데 사람이 볼때는 같다고 보는게 맞을 것입니다.

이렇게 사람이 볼때는 같은 문자인데, 유니코드에서는 따로 존재하는 문자들이 꽤 많습니다. 특히 한자에도 이런게 많이 있습니다. U+4E00..U+9FFF 범위는 CJK Unified Ideographs 입니다. 즉 한중일 언어 입니다. 그런데 U+F900..U+FAFF 범위는 CJK Compatibility Ideographs 입니다. 이 두 유니코드 영역에 문자들을 살펴보면 동일한게 많이 있습니다. 제대로 검색을 하려면, 일일이 같은 문자를 찾아서 동일한 처리가 되도록 해야 합니다. 엄청나게 귀찮고 복잡한 일인데, 어쩔수가 없습니다.

일반적으로 이것은 유니코드 노멀라이즈(Unicode Normalize) 라고 합니다. 


이런것들을 고려해서 작성하면, 아래처럼 됩니다. 기본적인 문자 + 한글 + 일본어 + 한자에 대한 charset_table 입니다.


charset_table






한글(unicode) 좀 더 유연하게 하기



검색 방법에는 많은 방식이 있습니다. 그중 대표적인것으로 형태소 분석, 스태밍, n-gram 등이 있습니다. sphinx 는 n-gram 방식을 지원합니다. 각 방식에 대해서 찾아보시기 바랍니다.


n-gram 방식에 대해서만 설명을 드리자면, 몇 글자로 keyword 를 나누어 indexing 할 것인가? 입니다. 예를 들어보죠.


'동해물과 백두산' 이라는 문장이 있습니다. 이것을 n-gram 방식의 2-gram(ngram_len = 2) 으로 indexing 해 봅시다.

'동해', '해물', '물과', '과 ', ' 백', '백두', '두산'

이렇게 2문자씩 묶이게 됩니다. n 값이 2 이기 때문이죠. 3이라면 아래와 같이 됩니다.
'동해물', '해물과', '물과 ', '과 백', ' 백두', '백두산'


검색 키워드가 '해물' 인 경우 '동해물과 백두산' 이 검색되길 바라지 않을 것입니다. 일반적인 자연어 검색이라면 말이죠. 즉, 이 경우 2-gram(bigram)은 부적절합니다. 3-gram 이라면 '해물'로 검색하더라도 해당 문장이 검색되지 않을 것입니다.


이때 이 값을 변경할 수 있는 옵션이 바로 'ngram_len' 입니다. 함께 쓰이는 옵션으로 'ngram_chars' 가 있습니다. 어떤 문자에 대해서 ngram 을 적용할 것인지 정하는 옵션입니다. 지정 방식은 charset_table 과 동일합니다.



자연어에 대한 검색인 경우에는 ngram 을 사용하는게 나을 것입니다. 그러나 일반적인 검색(like)은 'keyword*' 형태로 검색하는 것이 좋다고 생각합니다.




이외의 옵션들



이외에도 상당히 많은 옵션들이 존재합니다. html 태그를 srip 하는것 부터 시작해서, 앞 문자 몇 개부터 index 할건지 등등 매우 많습니다. 관련된 옵션들을 한번 훑어보시는것을 권장합니다. :)


아래 주소에서 확인 가능합니다.

http://sphinxsearch.com/docs/current/confgroup-index.html



마지막으로 최종적인 '한글, 일본어(히라가나, 카타카나), 특수문자' 를 지원하는 sphinx.conf 는 아래와 같습니다.

ngram 은 사용하지 않습니다.








  • 조진아 2015.05.19 16:36

    안녕하세요. 구글링하며 스핑크스 HA 구성방법 찹아 해메이다가 들렀습니다.
    자료 정리가 매우 잘되어있어서.. 혹시 게시글에는 없지만 검색서버를 2대로 운영 L4에 묶어 도메인으로 사용할수 있도록 HA 구성 해보셨나요?? 단일서버로 구성하는 방법은 자료가 많은데.. ( 그래서.. 저도 단일서버로는 구성을 했습니다..) 이중화를 하려 하니 자료가 별로 없네요.. 혹시 해보셨으면 조언 부탁드립니다.

    • unD3R 2015.05.19 19:07 신고

      안녕하세요? 저도 전에 찾아봤는데, sphinx 자체적으로 replication 을 지원하지 않는것 같더라고요. sphinx 에서 데이터는 real db 에서 긁어올거라 master/slave 의 기능구분은 없을것 같고, 전부 slave 인데,,,
      L4 로 묶어서 쓸 경우, 서버가 shutdown 된 상황에 대한 fail-over 를 어떻게 처리해야 할지 잘 모르겠네요. 결국 L4 는 로드밸런싱 밖에는.....
      해결방법 찾으시면, 저에게도 코멘트 부탁드릴게요 ^^;
      감사합니다. ㅠㅠ

  • unD3R 2015.05.19 19:11 신고

    client 쪽에서 sphinx server pool 을 가지고 있고, 사용시에 체크하는 것 이외에는 딱히 떠오르는게 없네요.(밸런싱도 L4 없이 client 단에서 적절히 배분);;

  • 노을 2015.09.07 11:33

    안녕하세요. 좋은 자료 많이 참고했습니다.
    저는 우분투 14.04 환경에서 스핑크스를 구성하였고. 윈도우 서버에 있는 오라클 데이터를 odbc로 통하여 인덱싱 해보았습니다.
    다른 문제는 없는데. 한글이 모두 ? 로 표시되어 나오는 상태입니다.

    스핑크스가 설치되어 있는 우분투 서버에서 오라클의 sqlplus 과 odbc의 isql로 접속하여 데이터를 가지고 올때는 한글을 정상적으로 가지고 오는 idexer를 통하여 스핑크스 데몬에서 돌리면 한글이 말씀드린바와 같이 ?표로 표시되는데 도무지 어떤 문제 인지 모르겠습니다.

    mysql로 붙여서 테스트를 하면 한글데이타에 아무 문제가 없는데 말이죠..
    혹시... 문제 해결에 단초가 될만한 내용이 있을까요?

    • unD3R 2015.09.08 17:30 신고

      안녕하세요? 말씀하신 내용을 가지고 추측해보면, 오라클과 mysql 사용시 차이점이라고하면, 각 DB 에서 값을 뽑아올때 인코딩이 서로 다른것 밖에 없지 않을까요? 현재 제가 말씀드릴수 있는건, 오라클 데이터베이스와 table(schema)의 한글 인코딩이 무엇으로 되어 있는지 다시한번 확인해 보시는게 어떤가 입니다. 큰 도움이 못 되어 죄송합니다. ㅠㅠ