DB/Sphinx

[Sphinx] Sphinx 검색엔진 라이브러리

HMHA 2023. 2. 6. 11:15
300x250
반응형

Sphinx 검색엔진 라이브러리

검색엔진이란 ::

-인터넷에서 원하는 정보를 찾아 주는 프로그램. 키 워드를 입력하면 그 정보가 있는 데이터를 찾아서 제시해 줌.

-Sphinx는 검색엔진 중 한 종류이다.

 

Sphinx 사용 이유 ::

-MySQL이 like 문으로 힘들게 데이터를 검색하는 반면, Sphinx를 사용하면 빠르고, 정확한 결과값을 내주기 때문에 사용.

Sphinx 작동 방법 ::

사전에 스핑크스가 내가 조회하려는 테이블의 값을 압축하고 압축한 인덱싱 파일로 만들어서 기록해놓고, 사용자가 그 압축된 인덱싱을 조회하여 빠르게 데이터를 끄집어 써낼 수 있게 한다.

즉, 검색엔진은 대용량의 데이터를 미리 인덱싱화 한 후에 저장해두는 것이다.

 


 

 

스핑크스 오픈소스 라이브러리 공식 사이트 ::

http://sphinxsearch.com/ 

 

스핑크스 오픈소스 라이브러리 한글 설명 블로그 ::

http://sacstory.tistory.com/entry  (여기 글을 많이 참고함)

 

 


 

 

메인 화면에서 사용하는 검색엔진 ::

 

-Sphinx 라이브러리 설치 경로 /usr/local/etc/sphinx

 

 


 

 

 

Sphinx.conf :: Sphinx 설정 파일 (실제 Sphinx.conf와 내용이 틀림)

-설정파일에 DATABASE의 접속 정보를 기입하고 쿼리 정보를 입력해야만 나중에 정해진 명령어로 스핑크스가 인덱싱 처리를 할 때 제대로 압축하여 인덱싱 데이터로 남겨 둘수있기 때문에 conf 필수적인 파일이다.

1.source 설정

sphinx.conf 내용 중 source 부분이다.

 

source zipcode

{

 type   = mysql

 sql_host  = localhost

 sql_user  = DB아이디

 sql_pass  = DB패스워드

 sql_db  = DB명

 sql_port  = 3306 # optional, default is 3306

 sql_sock  = /tmp/mysql.sock

 

 sql_query_pre = SET NAMES utf8

 sql_query = SELECT sn, zipcode, state, city, suburb, address FROM zipcode 

 

 sql_field_string = zipcode

 sql_field_string = state

 sql_field_string = city

 sql_field_string = suburb

 sql_field_string = address

}

source 부분에서는 인덱스명을 지정하고, 어떤 데이터를 인덱싱 할 것이지 등등 설정하는 부분이다.

 

source명 : zipcode (테이블명으로 하는 것이 편하다.)

sql_query_pre : 한글 데이터를 위해 utf8로 설정한다.

sql_query : 어떤 데이터를 인덱싱 하기 위해서는 MySQL로부터 데이터를 가져와야 한다. 자신이 Sphinx를 통해 검색 할 데이터 내용, 검색 결과에 표시할 내용을 포함하도록 MySQL 쿼리문을 작성하면 된다. 단, 첫번째 필드는 절대로 중복되지 않는 Primary Key 또는 Unique Key 값을 줘야 한다. 따라서 이런 필드가 없다면 DB에 만들어야 한다.

sql_field_string : Sphnx가 데이터를 다루기 위해 필요한 선언이라고 생각하면 된다. 즉, 데이터 타입을 지정해주는 것으로 int, string, float 등 이라고 생각하면 된다.

sql_field_string 외에 아래와 같은 데이터 타입이 있다.

sql_attr_uint

sql_attr_bigint

sql_attr_timestamp

 

2.index 설정

 

index 설정에는 한글 설정, 검색 방법 등등의 설정을 다루게 된다. index 는 source와 한쌍이기 때문에, source 하나에 index도 반드시 하나가 있어야 한다.

 

sphinx.conf 내용중 index 부분이다.

 

index zipcode

 

{

 source  = zipcode

 path   = /usr/local/sphinx/var/data/zipcode

 docinfo  = extern

 charset_type = utf-8

 charset_table = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F

 

 ngram_len = 1

 ngram_chars =  U+4E00..U+9FBB, U+3400..U+4DB5, U+20000..U+2A6D6, U+FA0E, U+FA0F, U+FA11, U+FA13, U+FA14, U+FA1F, U+FA21, U+FA23, U+FA24, U+FA27, U+FA28, U+FA29, U+3105..U+312C, U+31A0..U+31B7, U+3041, U+3043, U+3045, U+3047, U+3049, U+304B, U+304D, U+304F, U+3051, U+3053, U+3055, U+3057, U+3059, U+305B, U+305D, U+305F, U+3061, U+3063, U+3066, U+3068, U+306A..U+306F, U+3072, U+3075, U+3078, U+307B, U+307E..U+3083, U+3085, U+3087, U+3089..U+308E, U+3090..U+3093, U+30A1, U+30A3, U+30A5, U+30A7, U+30A9, U+30AD, U+30AF, U+30B3, U+30B5, U+30BB, U+30BD, U+30BF, U+30C1, U+30C3, U+30C4, U+30C6, U+30CA, U+30CB, U+30CD, U+30CE, U+30DE, U+30DF, U+30E1, U+30E2, U+30E3, U+30E5, U+30E7, U+30EE, U+30F0..U+30F3, U+30F5, U+30F6, U+31F0, U+31F1, U+31F2, U+31F3, U+31F4, U+31F5, U+31F6, U+31F7, U+31F8, U+31F9, U+31FA, U+31FB, U+31FC, U+31FD, U+31FE, U+31FF, U+AC00..U+D7A3, U+1100..U+1159, U+1161..U+11A2, U+11A8..U+11F9, U+A000..U+A48C, U+A492..U+A4C6

 

 

index명 : source명과 동일하게 해준다.

path : 인덱스한 파일이 저장되는 장소다. 위치는 상관없지만 데이터명은 쉽게 알아보기 위해 source명과 동일하게 처리해 주도록 한다.

charset_type : 한글 설정을 위해 utf-8로 해준다.

charset_table : sphinx.conf.dist 파일을 열어보면 utf-8 기본값으로 이미 위처럼 설정되어 있다. 그대로 복사해 사용한다.

ngram_len : 한글 검색을 위해 ngram을 설정해야 한다. 1값으로 설정한다.

ngram_chars : ngram을 검색하기 위해 나눌 단위를 설정한다. 한국어, 중국어, 일본어 모두 검색이 가능하도록 CJK를 사용하며 된다.

(CJK http://sphinxsearch.com/wiki/doku.php?id=charset_tables#cjk_ngram_characters)

 

3. rt 설정 (잘 사용 안함)

 

rt를 이용하면 인덱싱된 데이터를 실시간으로 수정 할 수 있다.

 

sphinx.conf 내용중 type이 rt인 index 부분이다.

 

index zipcodert

{

 type   = rt

 rt_mem_limit = 32M

 

 path   = /usr/local/sphinx/var/data/zipcodert

 charset_type = utf-8

 rt_field  = state

 


 

 

Sphinx 검색 방법 :: 일반 검색

 

-MySQL에서 검색시에는 필드명='찾을값' 형식으로 사용했었다. 하지만 Sphinx에서는 MATCH() 라는 것을 사용한다. 순서를 변경해도,  동일한 검색 결과를 가져온다.

 

1.SELECT * FROM zipcode WHERE MATCH('홍성');

 

-모든 컬럼에 '홍성'이란 것이 발견되면 다 찾아낸다. 20개의 검색결과를 가져온다.

 

2.SELECT * FROM zipcode WHERE MATCH('홍성') LIMIT 100; 

 

-20개 이상의 값을 원하면 LIMIT을 사용한다.

 

3.SELECT * FROM zipcode WHERE MATCH('홍성 갈산'); 

 

-2개 이상의 단어를 검색하면, 두 단어 중 하나라도 포함되어있는 row가 출력된다.

 

4.SELECT * FROM zipcode WHERE MATCH('홍성갈산'); 

 

-두 단어를 붙여서 검색하면, 동일한 결과 값을 가지고 온다.

 

이것이 바로 n-gram 검색방식의 특징이다. 

 

'홍성갈산', '갈산홍성', '홍산갈성', '갈홍산성' 등등 글자 순서를 변경해도, 동일한 검색 결과를 가져오는 것을 알게 될 것이다.

 


 

 

Sphinx 검색 방법 :: 구문 검색(Parse Search)

 

개요.SELECT * FROM zipcode WHERE MATCH('서산');

 

전북 전주시 완산구 서서학동 산 81 라고 되어있는 Row가 있다고 가정을 하면, 서와 산자가 총 4개 포함되어 있기 때문에 상위 결과값으로 나온다. 

 

이처럼 일치하는 검색이 몇개이고, 얼마나 연관성이 있는지에 따라 weight값이 결정이 되면 가장 높은 weight값 순으로 정렬되어 검색 결과가 나오는 것이다.

 

1.SELECT * FROM zipcode WHERE MATCH('"서산"'); (구문 검색 :: 양 옆에 따옴표를 붙이는 방법)

 

-쌍따옴표로 검색어를 감싸주면 구문으로 검색이 된다.

 

2.SELECT * FROM zipcode WHERE MATCH('"서산" "고북"'); 

 

-응용으로 검색어 2개를 각각 따옴표로 처리해주고, 쉼표 없이 검색해주면 원하는 값을 쉽게 찾을 수 있게된다.

 


 

 

Sphinx 검색 방법 :: 필드 검색(Field Search)

 

개요.SELECT * FROM zipcode WHERE MATCH('"공주"');

 

-모든 필드에서 공주라는 검색어를 찾게된다.

만약 내가 원하는 것이 suburb라는 필드에 있는 공주라고 한다면 필드명을 명시해주면 된다.

방법.필드 검색시에는 필드명 앞에 @를 붙이고 한칸 띄우고 쿼리문을 완성하면 된다.

 

1.SELECT * FROM zipcode WHERE MATCH('@suburb "공주"'); 

 

-원하는 필드명이 1개일 때, 사용하는 방법.

 

2.SELECT * FROM zipcode WHERE MATCH('@(city,suburb) "공주"'); 

 

-원하는 필드명이 2개 이상일 때, 사용하는 방법.주의할점은 쉼표뒤에 공백이 없어야 한다는 점이다.

 


 

 

Sphinx 검색 방법 :: 필드별 우선검색

 

개요.여러 필드를 검색했을때, 검색된 단어가 가장 많은 데이터부터 나오게 된다.

 

만약 검색시 title에 일치된 값이 다른 필드에서 일치된 값보다 더 중요한 검색 결과값을 가지고 있기 때문에 상위 결과값으로 출력해 줘야 한다면 필드별로 weight값을 주면 된다.

 

SELECT title, publisher FROM book WHERE MATCH ('"어린이"');

 

위의 쿼리문에서 title 컬럼에 어린이에 우선순위를 주고 싶었지만, publisher 때문에 우선순위가 바뀔 수 있다. 그래서 weight 값을 줘서 해결할 수 있다.

 

1.SELECT title, publisher FROM book WHERE MATCH ('"어린이"') OPTION FIELD_WEIGHTS=(title=2,publisher=1);

 

필드 title에 2값을 주고, publisher에는 1값의 weight 값을 주었다. (1값은 기본값으로 명시하지 않아도 된다.)

 

따라서 만약 title에 어린이가 일치하면 weight값이 publisher에서 일치하는 값보다 커지게 되어 검색 결과에 상위로 나온다.

 

 


 

 

Sphinx 검색 방법 :: 와일드카드 검색(Wildcard Search)

 

개요.만약에 일부분을 검색해야 한다면, 와일드카드를 사용하면 된다.

 

예를 들면 아래처럼 검색 한다면 오로지 7000값만을 찾게 된다.

 

SELECT title,price FROM book WHERE MATCH('@price 7000');

 

하지만 와일드카드인 *을 사용해서 아래처럼 검색하면 다음과 같은 결과를 얻을 수 있다.

 

SELECT title,price FROM book WHERE MATCH('@price *7000'); 

 

바로 앞에 *을 붙이면 검색어와 일차하는 값과 더불어 *가 붙은 부분에서 일치하는 모든 결과값을 가져오게 된다.

 

하지만 와일드카드를 사용하려면 sphinx.onf 파일에 설정을 따로 해줘야 한다.

 

설정을 해주지 않는다면, *값을 인식하지 못하게 된다.

 

설정은 다음과 같이 한다.

 

index 인덱스명

{

...

...

charset_table = 0..9..........

...

enable_star=1

min_infix_len=2

#min_prefix_len=2

...

...

enable_star는 와일드카드를 사용한다는 것으로 1값이면 사용한다는 뜻이다.

min_infix_len과 min_prefix_len의 길이는 최소 몇개의 값으로 쪼갤 것인가이다.

예를 들면 test를 2로 쪼개면, te, es, st, tes, est, test 등으로 최소 크기인 2부터 인덱싱을 해두는 것이다.

따라서 만약 *st 라고 검색하면 test가 검색되는 것이다.

infix와 prefix의 차이점은 다음 사이트를 참고한다. 

http://sphinxsearch.com/docs/manual-2.0.8.html#conf-min-prefix-len

참고로, enable_star=1값으로 하면 모든 필드에 적용된다. 만약, 특정한 필드만 와일드카드를 사용한다면 아래처럼 설정 할 수 있다.

infix_fields = price, 등등 필드명

prefix_fields = content, 등등 필드명 

주의할 점으로는 min_infix_len와 min_prefix_len은 동시에 사용 할 수 없으므로 둘중 하나만 선택해야 한다. 

(단, infix_fields 처럼 필드별 설정을 한다면 동시사용 가능)

 


 

Sphinx 정렬 ::

 

MySQL에서 처럼 정렬 기능이 필요 할때 ORDER BY 구문을 사용하면 된다.

하지만 조금 다른점은 반드시 ASC나 DESC 둘중 하나를 반드시 써줘야 한다. 보통 MySQL에서는 ASC를 생략하고 쓰는데, Sphinx에서는 생략을 할 수 없다는 것이다.

 

SELECT * FROM shop ORDER BY price DESC;

SELECT * FROM shop ORDER BY s_price DESC;

price와 s_price 컬럼이 동일한 데이터를 가지고 있다고 가정하자. 추가로 두 쿼리를 실행시켰을 때, 결과가 틀리게 나온다면 어떤 문제가 있을까?

 

이제 이런 이유를 잠시 설명해보기 위해 sphinx.conf에 설정된 값을 볼 필요가 있다.

source shop

{

...

...

         sql_query_pre = SET NAMES utf8

         sql_query = SELECT sn, price, price AS s_price, pack_count, item_name, npc_name FROM shop WHERE sphinx_yn='Y'

 

         sql_attr_bigint         = price

         sql_field_string        = s_price

         sql_field_string        = pack_count

         sql_field_string        = pack_count

         sql_field_string        = item_name

         sql_field_string        = npc_name

...

...

}

 

#Sphinx에서는 크게 2개의 타입이 있는데 Attribute와 Field다.

 

1)Attribute : Full-Text 기능 없음. 정렬, 필터 기능 있음.

2)Field : Full-Text 기능을 지원.

 

설정을 보면 2개의 타입 sql_arttr_bigint와 sql_field_string이 각각 price와 s_price의 타입으로 선어되어 있다.

price는 Attribute로, s_price는 Field로 선언된 것이다.

 

따라서 정렬을 할때는 sql_attr_bigint 타입으로 선언된 price로 해야 되는 것이다.

그리고 아래처럼 price로 검색을 한다면 없는 필드라고 에러가 출력된다.

SELECT * FROM shop WHERE MATCH('@price 1000000'); 

 

하지만, Full-Text를 통한 검색이 안되는 것뿐이다. 즉, MATCH() 함수를 통한 검색이 안되는 것이다.

아래 명령어처럼 일반적인 MySQL 쿼리문처럼 검색을 하면, 가능하다.

따라서 구간 검색이라던지, 날짜 등의 정렬 등이 필요한 경우에 Attribute 타입을 사용하는 것이다.

SELECT * FROM shop WHERE price>= 72 and price <= 200; 

 

이 방법을 사용함으로서, 특정 구간의 값들이나, 정렬등을 통해 결과값을 구할 수 있다.

따라서 검색할 대상이 어느 필드인지, 검색하지않고 결과값에만 나와도 되는 필드인지, 검색시 어떤 유형의 검색을 허용 할지에 따라, 타입을 잘 골라야 할 것이다.

 

예를 들면 날짜 데이터의 경우 검색할 필드가 아니고, 결과값에만 출력이 되는 값이라면, 굳이 Attribute 타입이 아닌 Field 타입으로 해도 될 것이다.

하지만, 정렬, 구간 등의 검색이 필요하다면, sql_attr_timestamp로 선언해야 될 것이다.

 


 

 

Sphinx Indexing ::

 

1)전체 인덱싱

-전체 인덱싱은 말 그대로, 환경설정에 있는 모든 인덱스를 인덱싱 하는 것이다.

indexer --all

 

-만약, SphinxQL을 사용하기 위해, 혹은 서비스 제공을 위해 searchd 프로세스를 실행시켰다면, 항상 --rotate를 추가로 붙여주면 된다. 그렇지 않다면 에러가 발생할 것이다.

indexer --all --rotate

 

-전체 인덱싱은 모든 인덱스의 데이터를 인덱싱하기때문에 인덱스의 크기와 데이터의 크기에 의해 시간이 결정될 것이다.

(main + delta 방식인 경우 전체 인덱싱 하고나서 merge 명령어를 해줘야 delta에서 인덱싱된 데이터를 Sphinx에 반영하게 된다.)

 

2)부분 인덱싱

-환경설정에 설정된 인덱스 리스트가 많다면, 전체 인엑스보다는 특정 인덱스만을 인덱싱 함으로서 시간을 줄일 수 있다.

indexer 인덱스명

 

-예를 들면, 예제에서 제공된 sphinx.conf에는 zipcode, book, delta, shop이라는 4가지의 인덱스가 있다.

이 이름을 통해 인덱싱을 하면 되는 것이다.

indexer delta

 

3)인덱스 업데이트

인덱스 업데이트는 3가지가 있다.

 

위에 나온 것처럼 데이터가 적은경우 전체 또는 부분 인덱싱을 통해 인덱스 전체를 처음부터 다시  생성하는 방법과 라이브 인덱싱, 그리고 Delta 방식 인덱싱이 있다.

 

4)Delta 방식 인덱싱

Delta 업데이트는  Main + Delta 방식으로, 먼저 주된 인덱스를 Main이 갖게하고, MySQL에 추가된 데이터를 Delta에서 인덱싱 하도록 한다.

그리고나서 Delta의 데이터를 Main에 건네주어 데이터를 합친다.

따라서 추가된 데이터만 인덱싱하면 되므로 인덱스 전체를 인덱싱 할 필요가 없다.

예제로 첨부한 sphinx.conf에 있는 인덱스들 중에 shop 인덱스가 Delta 업데이트 방식을 사용한다.

 

 source shop

 {

         type                    = mysql

 

         sql_host                = localhost

         sql_user                = debian

         sql_pass                = anne$1929

         sql_db                  = test

         sql_port                = 3306  # optional, default is 3306

         sql_sock                = /tmp/mysql.sock

         sql_query_pre = SET NAMES utf8

         sql_query = SELECT sn, price, price AS s_price, pack_count, item_name, npc_name FROM shop WHERE sphinx_yn='Y'

 

         sql_attr_bigint         = price

         sql_field_string        = s_price

         sql_field_string        = pack_count

         sql_field_string        = item_name

         sql_field_string        = npc_name

 #       sql_query_info          = SELECT * FROM shop WHERE sn=$id

 }

 source delta : shop

 {

         sql_query_pre = SET NAMES utf8

         sql_query = SELECT sn, price, price AS s_price, pack_count, item_name, npc_name FROM shop WHERE sphinx_yn='N'

         sql_query_post = UPDATE shop SET sphinx_yn='Y' WHERE sphinx_yn='N'

         sql_query_killlist = SELECT sn FROM shop WHERE sphinx_yn='D'

 }

 

 index shop

 {

         source                  = shop

         path                    = /usr/local/sphinx/var/data/shop

         docinfo                 = extern

         charset_type            = utf-8

         charset_table = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F

          ...

          ...

 }

 

 index delta : shop

 {

         source                  = delta

         path                    = /usr/local/sphinx/var/data/dalta

         docinfo                 = extern

         charset_type            = utf-8

         charset_table = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F

 

          ...

          ...

 

 }

 

설정을 보면 main + delta에서 main에 해당하는 것이 shop다. 그리 delta에 해당하는 것이 delta이다.

 

자심 아래의 쿼리 일부분을 보면 아래처럼 되어 있다.

 

main의 sql_query : ... WHERE sphinx_yn='Y'

 

delta의 sql_query : ... WHERE sphinx_yn='N'

 

delta의 sql_query_post : UPDATE shop SET sphinx_yn='Y' WHERE sphinx_yn='N'

 

즉, MySQL에서 main이 인덱싱하는 부분은 Y값이고 delta에서 인덱싱 하는 부분은 N값 부분이다.

그리고 delta가 인덱스를  마치면 sql_query_post를 통해 N값을 Y값으로 바꿔줌으로서 다시는 delta 인덱싱할때 인덱스 범위에 잡히지 않도록 해준다.

이로서 추가된 데이터를 인덱스하는 것이다.

다음은 이 과정을 명령어로 나타낸 것이다. (서비스중이면 --rotate 추가할 것)

1. delta 인덱싱

indexer delta

2. main + delta 인덱스 데이터 합치기

indexer --merge shop delta

 

만약 MySQL에서 일부 데이터가 변경된 것을 Sphinx에 반영하려면 변경된 데이터의 sphinx_yn값을 delta에서 인덱싱 할 수 있도록 Y값을 N값으로 변경만 해주면 데이터를 업데이트 할 수 있다.

만약, MySQL의 데이터중 일부가 삭제 됐다면, 전체, 혹인 부분 인덱싱을 해야한다. 하지만 삭제하기전에 Sphinx에서 데이터를 먼저 빼도록 할 수 있다.

바로 delta의 sql_query_killlist가 그 역할을 한다.

delta의 sql_query_killlist : SELECT sn FROM shop WHERE sphinx_yn='D'

sphinx_yn의 값을 D로 해두면, killlist의 인덱스 범위내로 들어온다. 이때, 인덱스의 기준이 되는 id값을 넘겨주기만 하면 된다. (여기 예제에서는 id가 sn값이다.)

이제 delta를 인덱싱하고 merge지를 하면 데이터가 사라져있다.

 


 

 

Sphinx php 설치 ::

 

Sphinx php 설치 경로 :: /home/public_html/search/Sphinx.php

 

mysql_connect를 사용하여 접속한다.

아쉽게도 현재 Sphinx는 ID와 Password를 설정 할 수 없다고 한다.

따라서 ID와 Password는 비워두고 접속하면 된다.

 

 


 

 

Sphinx 쿼리문 ::

 

 

1.search/index.php가 하는 일.

 1)검색어 통계:: 전송 받은 $skey 값을 JT_search_stac 테이블에 저장

 2)Sphinx 라이브러리 사용 (searchProduct 함수) 

  searchProdcut 함수(Sphinx.php) ::

   $arr_param 값을 사용 {/home/public_html/search/index.php 에서 $skey -> $replace_skey -> $arr_param(array)으로 되어 전송됨}

   

3)스핑크스 쿼리문 (Sphinx.php)

   $sql = "SELECT 

 

                             seq, cate, cate1, base_name, part_name, company, product  

                FROM

 

                             table

               WHERE

 

                             match('@(product,seq,search_key,company,base_name,part_name,spec) ".$skey."') AND face in (1, 2, 3) ";

 
이런식으로 사용한다.

 

 

출처 : https://happydhkim.tistory.com/3

300x250
반응형