IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  오픈 소스  >

PHP로 커스텀 검색 엔진 구현하기 (한글)

Sphinx를 사용하여 콘텐트를 색인하고, 텍스트를 빠르게 찾으며, 유용한 검색 결과 만들기

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

영어원문

영어원문


제안 및 의견
피드백

난이도 : 중급

Martin Streicher, Editor in Chief, Linux Magazine

2007 년 10 월 02 일

Google과 이와 비슷한 종류의 검색 엔진들은 그 성능이 뛰어나지만, 이러한 검색 엔진들이 모든 사이트에 잘 맞는 것은 아닙니다. 여러분의 사이트 콘텐트가 매우 특수한 것이라면 Sphinx와 PHP로 로컬 검색 시스템을 만들어 봅시다.

인터넷 시대에, 사람들은 패스트푸드와 같은 형태의 정보를 원한다. 즉각적으로 제공되고, 노력을 들이지 않아도 되며, 적당한 크기(바이트 사이즈(byte-size))로 제공되는 음식을 원한다. 실제로, 인내심 없고 배고픈 대중들의 구미에 맞추려면, 웹 사이트는 바로 효과가 나타나는 포맷을 제공해야 한다.

  • RSS는 피자 배달부이다. 방금 구운 데이터를 여러분의 문 앞으로 배달한다.
  • 웹로그는 중국 요리 테이크아웃으로서, 여러분이 좋아하는 매콤한 요리를 배달한다.
  • 포럼은 이웃과 함께 즐기는 팟럭(potluck) 음식이다. (또는 "Animal House"의 음식 싸움 장면에 알맞다.)
  • 검색(search)은 가까운 카페테리아에서 먹을 수 있는 음식들에 비유할 수 있다. 여러분이 원하는 음식을 쟁반에 담아서, 자리를 차지하고 앉아서 먹으면 된다.

다행히도, PHP 개발자는 광범위한 RSS, 블로그, 포럼 소프트웨어를 찾아서 사이트를 생성하거나 수정할 수 있다. Google등 검색 엔진들의 성능이 우수하지만, 이러한 검색 엔진이 모든 사이트에 잘 맞는 것은 아니다.

예를 들어, 웹 사이트가 수십만 개의 새롭게 단장된 Porsche 부품들을 제공한다면, Google은 "Carrera parts" 같은 광범위한 검색용 사이트를 띄울 것이다. 하지만 보다 구체적인 "used 1991 Porsche 911 Targa headlight bezel"에 대한 결과는 만들 수 없다.

여러분의 콘텐트가 고도로 특화되었거나 방문자가 여러분의 검색 기능에서 기대하는 것이 실제 작업 흐름과 밀접한 것이라면, 자신의 사이트에 맞게 재단된 로컬 검색 시스템으로 웹의 글로벌 검색 엔진을 확장하는 것이 최상의 방법이다. ("수 많은 건초 더미에서 바늘 찾기")

빠르고, 성능 좋은 오픈 소스 무료 검색 엔진을 PHP 사이트에 추가하는 방법을 알아보자. 이 글에서는 일부분만을 개발할 것이다. 대신, 초점은 효과적인 검색 결과를 제공하는데 필요한 컴포넌트(데이터베이스, 인덱스, 검색 엔진, PHP 애플리케이션 프로그램 인터페이스(API))에 맞출 것이다.

거대한 스핑크스에 방문하기

자신의 사이트에 맞춘 검색 기능을 제공하려면, 데이터 소스와 그 소스를 검색하는 기능이 필요하다. 웹 애플리케이션의 경우, 데이터 소스는 일반적으로 관계형 데이터베이스인데, 이것은 빌트인 검색 형태를 취하고 있다. (SQL 연산자 LIKE처럼, 간단한 검색 연산자가 있다.) 하지만, 일부 검색들은 데이터베이스가 수행할 수 있는 것 보다 더 특화되어 있거나, 검색이 너무 복잡해서 SQL JOIN으로 수행하기에는 너무 느리다.

수 많은 건초 더미에서 바늘 찾기

많은 사이트들이 의료, 법, 음악, 자동 관리 같은 산업, 직업, 오락과 관련한 콘텐트를 제공한다. 이 같은 콘텐트를 검색하려면 특별한 툴 또는 훈련이 필요하거나, 관련성 있고 실질적인 결과를 만들어 내려면 독자적인 인덱스가 필요하다.

다음은 특성에 맞춘(tailor-made) 검색 시스템을 필요로 하는 공통의 검색 시나리오이다.

  • Joe Hockey가 작성한 Stanley Cup에 대한 모든 아티클을 찾는다.
  • HP LaserJet 3015 All-in-One 프린터용 최신 드라이버를 찾는다.
  • Late Show With David Letterman에서 Dinosaur Jr.의 공연 필름을 찾는다.

검색의 속도를 높이기 위해, 테이블을 재조정 하여 기반 쿼리들을 단순화 할 수 있다. (Table과 SQL 쿼리 최적화는 스키마와 엔진에 크게 의존한다. 데이터베이스 성능과 관련한 아티클과 서적을 온라인에서 찾아보라.) 또는, 특화된 검색 엔진을 추가할 수 있다. 어떤 검색 엔진을 적용할 것인가는 데이터와 예산에 의해 결정된다. 많은 옵션들이 가능하다. Google 장치를 네트워크에 연결하거나, Endeca 또는 다른 대형 상용 검색 제품을 구매하거나, Lucene을 사용할 수도 있다. 하지만, 많은 경우, 상용 제품들은 예산 낭비이고, Lucene은 2007년 7월에 만들어진 PHP API를 제공하지 않는다.

대안으로서, Sphinx를 생각해 볼 수 있다. 무료의 오픈 소스 검색 엔진으로서 텍스트를 매우 빠르게 검색하도록 설계되었다. 예를 들어, 다섯 개의 인덱스 컬럼과 약 30만 개의 행을 가진 활성 데이터베이스에서, 각 컬럼은 15 단어를 포함하고 있다면, Sphinx는 "any of these words" 검색 결과를 100분의 1초 안에 찾아낸다. (2-GHz AMD Opteron 프로세서, 1 GB RAM, Debian Linux® Sarge).

Sphinx는 다음과 같은 많은 기능들을 갖고 있다.

  • 표현할 수 있는 어떤 데이터도 스트링으로 인덱싱 할 수 있다.
  • 같은 데이터를 다른 방식으로 인덱싱 할 수 있다. 다중 인덱스에서, 각각 특정 목표에 맞게 조정된 경우, 가장 적절한 인덱스를 선택하여 검색 결과를 최적화 할 수 있다.
  • 애트리뷰트와 색인된 데이터를 제휴시킬 수 있다. 한 개 이상의 애트리뷰트를 사용하여 검색 결과를 한 번 더 필터링 할 수 있다.
  • 형태론(morphology)을 지원하기 때문에, "cats"라는 단어 검색은 어근 단어 "cat" 역시 찾아낸다.
  • 많은 머신들 간 Sphinx 인덱스를 분산하면서, 페일오버를 제공한다.
  • 임의의 길이를 가진 접두사 단어의 인덱스와 다양한 길이의 하위 스트링의 삽입사(infix)의 인덱스를 만들 수 있다. 예를 들어, 파트 넘버가 10 문자라고 해보자. 접두사 인덱스는 스트링의 앞 부분에서 모든 가능한 하위 스트링과 매치한다. 삽입사 인덱스는 스트링 내 어디든 하위 스트링과 매치된다.
  • MySQL V5의 저장 엔진으로서 실행하면서, 또 하나의 실패 엔진으로 종종 간주되는 다른 데몬의 필요성을 없앤다.

온라인과 Sphinx 소스 코드에 포함된 README 파일에서 전체 기능을 참조하라. Sphinx 웹 사이트는 Sphinx를 전개했던 여러 프로젝트를 소개하고 있다.

Sphinx는 C++로 작성되며, GNU 컴파일러로 구현되고, 64-bit를 지원하며, 리눅스, UNIX®, Microsoft® Windows®, Mac OS X에서 구동된다. Sphinx 구현은 단순하다. 코드를 다운로드 및 추출한 다음, ./configure && make && make install 명령어를 실행한다.

기본적으로, Sphinx 유틸리티는 /usr/local/bin/에 설치되고, Sphinx 컴포넌트용 설정 파일은 /usr/local/etc/sphinx.conf 이다.

Sphinx는 세 개의 컴포넌트를 갖고 있다. 인덱스 제너레이터, 검색 엔진, 명령행 검색 유틸리티.

  • 인덱스 제너레이터를 인덱서(indexer)라고 한다. 데이터베이스를 쿼리하고, 각 결과 행에서 각 컬럼을 인덱싱 하며, 각 인덱스 엔트리를 행의 기본 키에 연결한다.
  • 검색 엔진은 searchd라고 하는 데몬이다. 이 데몬은 검색 단어와 다른 매개변수를 받고, 한 개 이상의 인덱스들을 없애고, 결과를 리턴한다. 매치가 이루어지면, searchd는 기본 키 어레이를 리턴한다. 이러한 키들이 주어진 애플리케이션은 제휴 데이터베이스에서 쿼리를 실행하여 매치를 구성하는 완전한 레코드를 찾을 수 있다. Searchd는 port 3312에서 소켓 연결을 통해 애플리케이션과 통신한다.
  • 손쉬운 검색 유틸리티로 코드를 작성하지 않고 명령행에서 검색을 수행할 수 있다. search가 매치를 리턴하면, 검색은 데이터베이스를 쿼리하고 그 행을 매치 세트에 디스플레이 한다. 검색 유틸리티는 Sphinx 설정을 디버깅 하고 즉각적인 검색을 수행하는데 유용하다.

또한, Sphinx의 작성자 Andrew Aksyonoff와 기타 컨트리뷰터들은 PHP, Perl, C/C++ 및 기타 프로그래밍 언어용 API를 제공한다.




위로


바디 파트(Body-Part) 검색

Body-Parts.com에서 진귀하고 수집할 가치가 있는 자동차의 부품(펜더, 크롬, 범퍼)을 판매한다고 생각해 보자. Body-Parts 사이트 방문자는 제조자(Porsche 등), 파트 넘버, 구조, 모델, 연도, 조건(중고, 신차, Refurbished), 디스크립션, 또는 이러한 프로퍼티의 조합 별로 부품을 검색하고 싶을 것이다.

Body-Parts의 검색 기능을 구현하려면, 데이터 소스로서 MySQL V5.0을 사용하고 빠르고 정확한 텍스트 검색을 제공하기 위해 Sphinx 검색 데몬을 사용한다. MySQL V5.0은 뛰어난 데이터베이스이지만, 풀 텍스트(full-text) 검색 기능은 그렇게 뛰어나지 못하다. 사실, 외래 키를 지원하지 않는 테이블 포맷인 MyISAM 테이블로 제한되어 있다.

Listing 1부터 4는 이 예제와 관련한 Body-Parts 스키마 부분을 보여주고 있다. 각각, Model (Listing 1), Assembly (Listing 2), Inventory (Listing 3), Schematic (Listing 4) 테이블을 볼 수 있다.

Model 테이블

Listing 1의 Model 테이블은 단순하다. 레이블 컬럼이 모델의 이름("Corvette")을 열거하고 있다. 디스크립션이 사용자 친화적인 방식으로 자동차를 설명하고 있다. ("Two-door roadster; first year of introduction"). begin_productionend_production은 자동차가 생산이 시작되고 끝난 연도를 나타낸다. 앞서 언급했던 컬럼의 값들이 고유한 것이 아니기 때문에 개별 아이디가 위 네 쌍들(레이블, 디스크립션, begin_production, end_production)을 나타내고 있고 다른 테이블에 하나의 외래 키가 있다.


Listing 1. Body Parts Model 테이블
                
CREATE TABLE Model (
  id int(10) unsigned NOT NULL auto_increment,
  label varchar(7) NOT NULL,
  description varchar(256) NOT NULL,
  begin_production int(4) NOT NULL,
  end_production int(4) NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB;

다음은 Model 테이블용 샘플 데이터이다.

INSERT INTO Model 
  (`id`, `label`, `description`, `begin_production`, `end_production`) 
VALUES 
  (1,'X Sedan','Four-door performance sedan',1998,1999),
  (3,'X Sedan','Four door performance sedan, 1st model year',1995,1997),
  (4,'J Convertible','Two-door roadster, metal retracting roof',2002,2005),
  (5,'J Convertible','Two-door roadster',2000,2001),
  (7,'W Wagon','Four-door, all-wheel drive sport station wagon',2007,0);

Assembly 테이블

어셈블리(assembly)는 자동차의 변속기 또는 자동차의 모든 창 같은 하위 시스템이다. 소유자는 조립도와 관련 부품 리스트를 참조하여 대체 부품을 찾는다. Assembly 테이블(Listing 2)은 단순하다. 고유 아이디와 Assembly 레이블과 디스크립션을 연결시키고 있다.


Listing 2. Assembly 테이블
                
CREATE TABLE Assembly (
  id int(10) unsigned NOT NULL auto_increment,
  label varchar(7) NOT NULL,
  description varchar(128) NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB;

다음은 Assembly 테이블용 샘플 데이터이다.

INSERT INTO Assembly 
  (`id`, `label`, `description`) 
VALUES 
  (1,'5-00','Seats'),
  (2,'4-00','Electrical'),
  (3,'3-00','Glasses'),
  (4,'2-00','Frame'),
  (5,'1-00','Engine'),
  (7,'101-00','Accessories');

Inventory 테이블

Inventory 테이블은 자동차 부품의 규정 리스트이다. 볼트나 벌브(bulb) 같은 부품은 모든 차와 모든 조립 라인에 있지만, 이 부품은 Inventory 테이블에 한번만 나타난다. Inventory 테이블의 각 행은 다음이 포함된다.

  • 행을 구분하는데 사용되는 고유한 32-bit 정수 serialno.
  • 알파벳과 숫자가 통합된 파트 넘버. (파트 넘버는 고유한 것이고 기본 키로 충분하다. 하지만, 문자 숫자가 포함되어 있으므로, Sphinx와 함께 사용하는 것이 부적절 하다. 이는 각 레코드가 고유의 32-bit 정수 키를 갖도록 인덱싱 되어야 한다.)
  • 텍스트 디스크립션.
  • 가격.

Inventory 테이블 스팩은 Listing 3과 같다.


Listing 3. Inventory 테이블
                
CREATE TABLE Inventory (
  id int(10) unsigned NOT NULL auto_increment,
  partno varchar(32) NOT NULL,
  description varchar(256) NOT NULL,
  price float unsigned NOT NULL default '0',
  PRIMARY KEY (id),
  UNIQUE KEY partno USING BTREE (partno)
) ENGINE=InnoDB;

(일부) 부품 리스트는 다음과 같다.

INSERT INTO `Inventory` 
  (`id`, `partno`, `description`, `price`) 
VALUES 
  (1,'WIN408','Portal window',423),
  (2,'ACC711','Jack kit',110),
  (3,'ACC43','Rear-view mirror',55),
  (4,'ACC5409','Cigarette lighter',20),
  (5,'WIN958','Windshield, front',500),
  (6,'765432','Bolt',0.1),
  (7,'ENG001','Entire engine',10000),
  (8,'ENG088','Cylinder head',55),
  (9,'ENG976','Large cylinder head',65);

Schematic 테이블

Schematic 테이블은 부품들을 어셈블리와 모델로 연결한다. 따라서, Schematic 테이블을 사용하여 1979 J Class 컨버터블의 엔진을 구성하고 있는 모든 부품들을 찾을 수 있다. Schematic 테이블의 각 행은 고유 아이디, Inventory 테이블의 행을 참조하는 외래 키, 어셈블리를 구분하는 외래 키, Model 테이블의 특정 모델과 버전을 참조하는 또 다른 키를 갖고 있다. 이 행은 Listing 4에 나타나 있다.


Listing 4. Schematic 테이블
                
CREATE TABLE Schematic (
  id int(10) unsigned NOT NULL auto_increment,
  partno_id int(10) unsigned NOT NULL,
  assembly_id int(10) unsigned NOT NULL,
  model_id int(10) unsigned NOT NULL,
  PRIMARY KEY (id),
  KEY partno_index USING BTREE (partno_id),
  KEY assembly_index USING BTREE (assembly_id),
  KEY model_index USING BTREE (model_id),
  FOREIGN KEY (partno_id) REFERENCES Inventory(id),
  FOREIGN KEY (assembly_id) REFERENCES Assembly(id),
  FOREIGN KEY (model_id) REFERENCES Model(id)
) ENGINE=InnoDB;

다음은 Schematic의 행 리스트이다.

INSERT INTO `Schematic` 
  (`id`, `partno_id`, `assembly_id`, `model_id`) 
VALUES 
  (1,6,5,1),
  (2,8,5,1),
  (3,1,3,1),
  (4,5,3,1),
  (5,8,5,7),
  (6,6,5,7),
  (7,4,7,3),
  (8,9,5,3);

테이블로 검색하기

이러한 테이블들이 정의되면, 상당히 많은 검색에 대한 답이 쉽게 나올 수 있다.

  • 특정 모델에 대한 모든 버전 보여주기
  • 특정 모델과 버전을 조합하는데 필요한 모든 어셈블리들을 리스팅 하기
  • 특정 모델과 버전에 맞는 특정 어셈블리에 모든 부품 보여주기

하지만, 이렇게 많은 검색들은 비용이 많이 든다.

  • "WIN"으로 시작하는 파트 넘버를 가진 모델과 버전에 있는 모든 부품들 찾기
  • 디스크립션에 "lacquer" 또는 "paint"를 갖고 있는 부품들 찾기
  • 디스크립션에 "black leather"를 갖고 있는 부품들 찾기
  • 디스크립션에 "paint"를 갖고 있는 모든 2002 J Series 부품 찾기

각각의 검색들은 JOIN 또는 LIKE 구문을 필요로 하며, 특히 Inventory와 Schematic 테이블이 클 경우 더욱 그렇다. 더욱이, 복잡한 텍스트 검색들은 MySQL의 능력을 벗어난다. 방대한 분량의 텍스트 데이터를 검색하려면 Sphinx 인덱스를 구현 및 사용하는 것을 고려해 보라.




위로


Sphinx 소프트웨어 사용하기

Sphinx를 사용하려면, 한 개 이상의 소스와 한 개 이상의 인덱스를 정의해야 한다.

소스는 인덱싱 할 데이터베이스를 구분하고, 인증 정보를 제공하며, 각 행을 구현하기 위해 사용하는 쿼리를 정의한다. 소스는 한 개 이상의 컬럼을 필터로서 구분하거나, Sphinx가 그룹이라고 칭하는 것을 구분한다. 그룹들을 사용하여 결과를 필터링 한다. 예를 들어, paint 라는 단어는 900 개의 매치를 만들 수 있다. 특정 자동차 모델에 맞는 매치에만 관심이 있다면, 모델 그룹을 사용하여 필터링 할 수 있다.

인덱스는 소스(한 세트의 행)를 필요로 하고 소스에서 추출된 데이터가 목록화 되는 방식을 정의한다.

sphinx.conf 파일에 소스와 인덱스를 정의한다. Body Parts용 소스는 MySQL 데이터베이스다. Listing 5는 catalog라고 하는 소스의 정의 일부이다. 이 스니펫은 어떤 데이터베이스로 연결되는지, 어떻게 연결하는지(호스트, 소켓, 사용자, 패스워드)를 설정한다.


Listing 5. MySQL 데이터베이스로의 액세스 설정
                
source catalog 
{
    type                            = mysql
    
    sql_host                        = localhost
    sql_user                        = reaper
    sql_pass                        = s3cr3t
    sql_db                          = body_parts
    sql_sock                        =  /var/run/mysqld/mysqld.sock
    sql_port                        = 3306                  

그리고 나서, 쿼리를 만들어서 인덱싱 될 행들을 만든다. 일반적으로, SELECT 문을 만들면서 많은 테이블들을 결합(JOIN)하여 하나의 행을 만든다. 문제가 있다. 모델과 연도에 대한 검색은 Assembly 테이블을 사용해야 하지만, 파트 넘버와 파트 디스크립션은 Inventory 테이블에서만 찾을 수 있다. 이를 위해, Sphinx는 검색 결과를 32-bit 정수 기본 키로 연결할 수 있어야 한다.

올바른 폼으로 된 데이터를 얻으려면, 뷰(view)를 만들어야 한다. 이것은 MySQL V5의 새로운 구조로서 다른 테이블들에서 온 컬럼들을 하나의 복합 가상 테이블로 조합한다. 뷰를 사용하여, 모든 종류의 검색에 필요한 데이터들은 한 장소에 있게 된다. 활성 데이터가 실제로는 다른 테이블에 있더라도 말이다. Listing 6은 Catalog라고 하는 뷰를 정의하는 SQL을 보여주고 있다.


Listing 6. 가상 테이블로 데이터를 조합하는 카탈로그 뷰
                
CREATE OR REPLACE VIEW Catalog AS
SELECT
  Inventory.id,
  Inventory.partno,
  Inventory.description,
  Assembly.id AS assembly,
  Model.id AS model
FROM
  Assembly, Inventory, Model, Schematic
WHERE
  Schematic.partno_id=Inventory.id 
  AND Schematic.model_id=Model.id 
  AND Schematic.assembly_id=Assembly.id;

앞서 보여준 테이블과 데이터로 body_parts라는 이름의 데이터베이스를 만드는 Catalog 뷰는 다음과 같다.

mysql> use body_parts;
Database changed
mysql> select * from Catalog;
+----+---------+---------------------+----------+-------+
| id | partno  | description         | assembly | model |
+----+---------+---------------------+----------+-------+
|  6 | 765432  | Bolt                |        5 |     1 | 
|  8 | ENG088  | Cylinder head       |        5 |     1 | 
|  1 | WIN408  | Portal window       |        3 |     1 | 
|  5 | WIN958  | Windshield, front   |        3 |     1 | 
|  4 | ACC5409 | Cigarette lighter   |        7 |     3 | 
|  9 | ENG976  | Large cylinder head |        5 |     3 | 
|  8 | ENG088  | Cylinder head       |        5 |     7 | 
|  6 | 765432  | Bolt                |        5 |     7 | 
+----+---------+---------------------+----------+-------+
8 rows in set (0.00 sec)

이 뷰에서, id 필드는 Inventory 테이블의 부품 엔트리를 가리킨다. partnodescription 컬럼은 필수 텍스트이고, assemblymodel 컬럼은 결과를 한층 더 필터링 하는 그룹으로서 작동한다. 이러한 뷰가 완성되면 소스 쿼리 구현은 간단하다. Listing 7은 catalog 라고 하는 소스의 정의이다.


Listing 7. 인덱싱 될 행들을 만드는 쿼리
                
    # indexer query
    # document_id MUST be the very first field
    # document_id MUST be positive (non-zero, non-negative)
    # document_id MUST fit into 32 bits
    # document_id MUST be unique
    sql_query                       = \
            SELECT \
                    id, partno, description, \
                    assembly, model \
            FROM \
                    Catalog;
    
    sql_group_column                = assembly
    sql_group_column                = model
    
    # document info query
    # ONLY used by search utility to display document information
    # MUST be able to fetch document info by its id, therefore
    # MUST contain '$id' macro 
    #
    sql_query_info          = SELECT * FROM Inventory WHERE id=$id
}

sql_query에는 후속 검색에 사용하고자 하는 기본 키가 포함되어 있어야 하고, 그룹으로서 색인 및 사용할 모든 필드가 포함되어 있어야 한다. 두 개의 sql_group_column 엔트리들은 Assembly와 Model이 결과를 필터링 하는데 사용될 수 있도록 선언한다. 검색 유틸리티는 sql_query_info를 사용하여 매치하는 기록들을 찾는다. 이 쿼리에서, $id는 searchd가 리턴하는 기본 키로 대체된다.

마지막 설정 단계는 인덱스를 구현하는 단계이다. Listing 8은 소스 catalog용 인덱스 모습이다.


Listing 8. catalog라고 하는 소스용 인덱스 기술하기
                
index catalog
{
    source                  = catalog
    path                    = /var/data/sphinx/catalog
    morphology              = stem_en

    min_word_len            = 3
    min_prefix_len          = 0
    min_infix_len           = 3
}

첫 번째 줄은 sphinx.conf 파일에서 이름이 있는 소스를 가리킨다. 두 번째 줄은 인덱스 데이터를 저장할 장소를 정의한다. 일반적으로, Sphinx 인덱스들은 /var/data/sphinx에 저장된다. 세 번째 줄은 인덱스가 영어를 사용하도록 하고 있다. 5번째부터 7번째 줄은 인덱서(indexer)에게 세 문자 이상의 단어들만 인덱싱 하고 세 문자 이상의 모든 하위 스트링의 삽입사 인덱스를 만들도록 명령하고 있다. (Listing 9는 Body-Parts의 sphinx.conf 파일의 전체 예제를 보여주고 있다.)


Listing 9. Body Parts의 sphinx.conf 예제
                
source catalog
{
    type                            = mysql
    
    sql_host                        = localhost
    sql_user                        = reaper
    sql_pass                        = s3cr3t
    sql_db                          = body_parts
    sql_sock                        =  /var/run/mysqld/mysqld.sock
    sql_port                        = 3306                  

    # indexer query
    # document_id MUST be the very first field
    # document_id MUST be positive (non-zero, non-negative)
    # document_id MUST fit into 32 bits
    # document_id MUST be unique

    sql_query                       = \
            SELECT \
                    id, partno, description, \
                    assembly, model \
            FROM \
                    Catalog;

    sql_group_column                = assembly
    sql_group_column                = model

    # document info query
    # ONLY used by search utility to display document information
    # MUST be able to fetch document info by its id, therefore
    # MUST contain '$id' macro 
    #

    sql_query_info          = SELECT * FROM Inventory WHERE id=$id
}

index catalog
{
    source                  = catalog
    path                    = /var/data/sphinx/catalog
    morphology              = stem_en

    min_word_len            = 3
    min_prefix_len          = 0
    min_infix_len           = 3
}

searchd
{
	port				= 3312
	log					= /var/log/searchd/searchd.log
	query_log			= /var/log/searchd/query.log
	pid_file			= /var/log/searchd/searchd.pid
}

searchd 섹션은 searchd 데몬을 설정한다. 이 섹션의 엔트리들은 설명적이어야 한다. query.log가 특히 유용하다. 검색된 문서의 수와 총 매치의 수 같은 결과를 디스플레이 한다.




위로


인덱스 구현 및 테스트

이제 Body-Parts 애플리케이션용 인덱스를 구현할 준비가 되었다.

  1. $ sudo mkdir -p /var/data/sphinx 를 입력하여 디렉토리 계층 /var/data/sphinx를 만든다.
  2. MySQL이 실행된다는 것을 가정하고, 인덱서를 실행하여 아래 보이는 코드를 사용하여 인덱스를 만든다.

    Listing 10. 인덱스 생성하기
                            
    $ sudo /usr/local/bin/indexer --config /usr/local/etc/sphinx.conf --all
    Sphinx 0.9.7
    Copyright (c) 2001-2007, Andrew Aksyonoff
    
    using config file '/usr/local/etc/sphinx.conf'...
    indexing index 'catalog'...
    collected 8 docs, 0.0 MB
    sorted 0.0 Mhits, 82.8% done
    total 8 docs, 149 bytes
    total 0.010 sec, 14900.00 bytes/sec, 800.00 docs/sec
    

    주: -all 인자는 sphinx.conf에 리스팅 된 모든 인덱스들을 재구현 한다. 다른 인자를 사용하여 모든 인덱스를 재구현 할 필요가 없다면 더 적게 재구현 할 수 있다.
  3. 아래 보이는 코드를 사용하여 검색 유틸리티로 인덱스를 테스트 할 수 있다. (search를 사용하기 위해 searchd를 실행할 필요가 없다.)

    Listing 11. search를 사용하여 인덱스 테스트 하기
                            
    $ /usr/local/bin/search --config /usr/local/etc/sphinx.conf ENG
    Sphinx 0.9.7
    Copyright (c) 2001-2007, Andrew Aksyonoff
    
    index 'catalog': query 'ENG ': returned 2 matches of 2 total in 0.000 sec
    
    displaying matches:
    1. document=8, weight=1, assembly=5, model=7
            id=8
            partno=ENG088
            description=Cylinder head
            price=55
    2. document=9, weight=1, assembly=5, model=3
            id=9
            partno=ENG976
            description=Large cylinder head
            price=65
    
    words:
    1. 'eng': 2 documents, 2 hits
    
    $ /usr/local/bin/search --config /usr/local/etc/sphinx.conf wind 
    Sphinx 0.9.7
    Copyright (c) 2001-2007, Andrew Aksyonoff
    
    index 'catalog': query 'wind ': returned 2 matches of 2 total in 0.000 sec
    
    displaying matches:
    1. document=1, weight=1, assembly=3, model=1
            id=1
            partno=WIN408
            description=Portal window
            price=423
    2. document=5, weight=1, assembly=3, model=1
            id=5
            partno=WIN958
            description=Windshield, front
            price=500
    
    words:
    1. 'wind': 2 documents, 2 hits
    
    $ /usr/local/bin/search \
    --config /usr/local/etc/sphinx.conf --filter  model 3 ENG
    Sphinx 0.9.7
    Copyright (c) 2001-2007, Andrew Aksyonoff
    
    index 'catalog': query 'ENG ': returned 1 matches of 1 total in 0.000 sec
    
    displaying matches:
    1. document=9, weight=1, assembly=5, model=3
            id=9
            partno=ENG976
            description=Large cylinder head
            price=65
    
    words:
    1. 'eng': 2 documents, 2 hits
    

/usr/local/bin/search --config /usr/local/etc/sphinx.conf ENG의 첫 번째 명령어는 파트 넘버에 ENG가 두 번 등장한다. 두 번째 명령어, /usr/local/bin/search --config /usr/local/etc/sphinx.conf wind는 두 개의 파트 디스크립션에서 하위 스트링 wind를 찾았다. 세 번째 명령어는 결과를 model3이 되는 엔트리로만 제한했다.




위로


코드 작성하기

이제는, PHP 코드를 작성하여 Sphinx 검색 엔진을 작성할 수 있다. Sphinx PHP API는 쉽게 배울 수 있다. Listing 12는 searchd를 호출하여 위에 보이는 마지막 명령어의 같은 결과를 추출하는 PHP 애플리케이션이다. ("model 3에 속한 이름에서 'cylinder'를 가진 모든 부품들을 찾는다.")


Listing 12. PHP에서 Sphinx 검색 엔진 호출하기
                
<?php
  include('sphinx-0.9.7/api/sphinxapi.php');

  $cl = new SphinxClient();
  $cl->SetServer( "localhost", 3312 );
  $cl->SetMatchMode( SPH_MATCH_ANY  );
  $cl->SetFilter( 'model', array( 3 ) );

  $result = $cl->Query( 'cylinder', 'catalog' );

  if ( $result === false ) {
      echo "Query failed: " . $cl->GetLastError() . ".\n";
  }
  else {
      if ( $cl->GetLastWarning() ) {
          echo "WARNING: " . $cl->GetLastWarning() . "
"; } if ( ! empty($result["matches"]) ) { foreach ( $result["matches"] as $doc => $docinfo ) { echo "$doc\n"; } print_r( $result ); } } exit; ?>

코드를 테스트 하려면, Sphinx용 로그 디렉토리를 만들고, searchd를 시작한 다음, PHP 애플리케이션을 실행한다.


Listing 13. PHP 애플리케이션
                
$ sudo mkdir -p /var/log/searchd
$ sudo /usr/local/bin/searchd --config /usr/local/etc/sphinx.conf
$ php search.php 
9
Array
(
    [fields] => Array
        (
            [0] => partno
            [1] => description
        )

    [attrs] => Array
        (
            [assembly] => 1
            [model] => 1
        )

    [matches] => Array
        (
            [9] => Array
                (
                    [weight] => 1
                    [attrs] => Array
                        (
                            [assembly] => 5
                            [model] => 3
                        )

                )

        )

    [total] => 1
    [total_found] => 1
    [time] => 0.000
    [words] => Array
        (
            [cylind] => Array
                (
                    [docs] => 2
                    [hits] => 2
                )

        )
)

결과는 9이다. 이것은 매치하는 단일 행의 정확한 기본 키이다. Sphinx가 매치를 만들면, associative array $result에는 results라고 하는 엘리먼트가 포함된다. print_r()의 결과를 통해서 그 외 어떤 것들이 리턴되는지를 볼 수 있다.

total_found는 인덱스에서 찾아진 총 매치의 수이고, found는 리턴된 결과의 수이다. 이 두 가지는 매번 얼마나 많은 매치들이 리턴되는지, 어떤 일과 매치가 매치되는지를 변경할 수 있기 때문에 다르고, 긴 결과 리스트를 나누는데 유용하다. API 호출 SetLimits()를 참조하라. 페이지를 매기는 한 가지 예는 검색 엔진을 호출하는 것이다. $cl->SetLimits( ( $page - 1 ) * SPAN, SPAN )은 디스플레이 될 페이지에 따라서 첫 번째, 두 번째, 세 번째 일괄 SPAN 매치를 리턴한다.




위로


Sphinx의 미스터리

Sphinx는 지금 설명한 것보다 더 많은 기능을 갖고 있다. 이 글에서는 표면만을 다루었을 뿐이다. 여러분 스스로 그 기능들을 연구해 보기 바란다.

샘플 Sphinx 설정 파일 /usr/local/etc/sphinx.conf.dist를 주의 깊게 읽어보기 바란다. 이 파일에는 각각의 Sphinx 매개변수가 무엇을 하는지를 설명하고 있고, 분산된, 중복 설정을 만드는 방법도 설명되어 있다. 기본 설정들을 상속받아서 소스와 인덱스에 반복하지 않도록 한다. Sphinx README 파일 역시 좋은 자료이다. Sphinx를 MySQL V5에 직접 임베드 하는 방법도 나타나 있다.

다음에는 PHP 코드를 디버깅 할 때 echo()print_r() 보다 나은 솔루션을 찾아볼 것이다.



참고자료

교육

제품 및 기술 얻기

토론


필자소개

Martin Streicher는 Linux Magazine 편집장이다. 그는 퍼듀 대학에서 컴퓨터 과학 석사 학위를 받았으며, 1986년부터 파스칼, C, 펄, 자바와 최근에는 루비 프로그래밍 언어로 유닉스 계열 시스템을 프로그래밍해 왔다.




기사에 대한 평가


보다 나은 서비스를 제공하기 위함이오니 잠시 짬을 내어 이 양식을 제출하여 주십시오.



 


 


 


이 문서 북마킹 하기

mar.gar.in mar.gar.in naver naver eolin eolin del.icio.us del.icio.us





위로


developerWorks 콘텐트를 다른 사이트에 전재하기:
developerWorks 콘텐트에 대한 저작권은 IBM에 있습니다. IBM의 서면 허가나 원본 저자의 허락이 없이는 전재를 금합니다. 저희 콘텐트를 전재하시려면 IBM developerWorks 담당자 에게 문의하십시오.
    IBM 소개 개인정보 보호정책 문의