Tag Archives: 루씬

Lucene 인덱싱 옵션

블로그에 적어놓겠다고 한지 3달도 더 지난거 같다. 지금 빨리 안적어 두면 정말로 기억속에서 사라질것 같아 오랜만에 블로그에 글을 적는다.

루씬 2.0부터 바뀐 인덱싱 옵션 지정방법에 대해 정리하겠다.

보통의 인덱싱은 Document 클래스를 이용하여 다음과 같이 지정한다.

[code]Document document = new Document();
document.add(new Field(“area”, area, Store.YES, Index.UN_TOKENIZED));
document.add(new Field(“vmag”, vmag, Store.YES, Index.UN_TOKENIZED));
document.add(new Field(“count”, count, Store.YES, Index.NO));
document.add(new Field(“data”, data, Store.YES, Index.NO));
writer.addDocument(document);[/code]

정확히는 Field 객체를 넣는것이다.

Field 의 인자값은 다음과 같다.

[code]Field(필드명, 변수명, 저장옵션, 인덱스옵션)[/code]

첫번째 필드명은 데이터페이스의 필드를 생각하면 되겠다. 어떤 이름의 필드에 저장할것인지를 정한다.

두번째 변수명에는 위에서 지정한 해당 필드명에 들어갈 값을 지정해 주면 된다.

세번째 저장옵션에는 해당 전체 값을 인덱스에 저장할것인지를 정한다. 다음과 같은 옵션이 있다.



  • Store.YES : 인덱스를 할 값 모두를 인덱스에 저장한다. 검색결과등에서 꼭 보여야 하는 내용이라면 사용한다.
  • Store.NO : 값을 저장하지 않는다. Index 옵션과 혼합하여, 검색은 되데, 원본글이 필요없을 경우 사용될수 있다.
  • Store.COMPRESS : 값을 압축하여 저장한다. 저장할 글의 내용이 크거나, 2진 바이너리 파일등에 사용한다.


네번째 인덱스 옵션에는 검색을 위한 인덱스 생성 방식을 정한다.



  • Index.NO : 인덱싱을 하지 않는다. 고로 이렇게 저장한 값으로 검색을 할수 없다.
  • Index.TOKENIZED : Analyzer에 의한 토크나이즈를 수행하여 인덱싱을 한다. 물론 검색 가능하다.
  • Index.UN_TOKENIZED : 토크나이즈를 수행하지 않는다. 숫자라거나, 쪼갤필요가 없는 문자열에 사용하면 된다. 물론 검색이 되며, Analyze를 수행하지 않기때문에 인덱스 속도가 빠르다.
  • Index.NO_NORMS : 이것은 인덱싱 시간이 매우 빨라야 할때 사용한다. Analyze를 수행하지 않는다. 또한 필드 Length 노멀라이즈를 수행하지 않는다. 인덱싱시에 적은 메모리만을 사용하게 된다는 장점이 있다.

다음으로 예를 들어 사용법을 숙지하도록 하여보자.

1. 저장할 내용은 수필형식의 글이다. 해당 필드로 검색이 되어야 하며, 검색결과에서 바로 해당 글의 전문이 출력되어야 한다.
=> Store.YES, Index.TOKENIZED

2. 저장할 내용은 20070606 같은 형식을 가지는 띄어쓰기가 없는 날짜이다. 해당 필드로 검색이 되어야 하며, 검색어와 검색결과가 동일할테니 글의 내용을 저장할 필요는 없다.
=> Store.NO Index.Index.UN_TOKENIZED

3. 저장할 내용은 영화 제목과 영화 사용기이다. 영화 제목만으로 검색을 할수 있으며, 사용기의 전문으로 검색은 되지 않는다. 하지만 검색결과에서 사용기가 출력되어야 한다.
=> 제목 Field : Store.YES, Index.TOKENIZED / 사용기 Field : Store.YES, Index.NO

Java Lucene – 루씬을 이용한 JSP용 클래스

자바를 하면서 가장 중독성 있었던것을 꼽아보라고 하면 단연 수많은 오픈소스 프레임워크가 아닐까 생각해 본다.

검색엔진에 대한 아무런 지식이 없는 상태인 나는 검색엔진 구현을 해야 하는 상황에서 어떻게 해야 할까 고민하다가 무턱대고 Lucene in Action을 덜컥 구매해 버렸다.

그리고 보다보니깐 참 이해 안되고, 책 내용도 자꾸 웹과는 거리가 있는 콘솔상에서의 구현에 집중되어있고 인터넷을 검색해 봐도 다 이해할수 없는 예제였고, 아무튼 그렇게 루씬을 공부했다.

이제 꽤나 루씬을 이해하고 있고, 이제 내가 아는 지식을 공개해야 할때인듯 하다. 물론 내가 아는 지식도 매우 기초적인것이라 나역시 초보를 위한 강좌밖에 못할듯 하다.

현존하는 루씬의 강좌나 자료들이 2.0이전 버젼들을 대상으로 제작되어있고, 그 이전버젼들은 한글에 대한 Analyzer버그가 있다.

고로 나는 처음부터 2.0으로 해왔다. 그래서인지 Lucene in Action책을 보고 따라하기엔 다른것이 많아 지식 습득에 조금 문제가 있었다. 앞으로 쓰는 모든 글은 2.0으로 제작할것이며, 한글 문제가 없는 버젼이다.

우선 간단하게 공부해 볼수 있는 루씬 클래스입니다. 공부에 참고만 하세요^^

[CODE]package ke.pe.theeye.search;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.SimpleAnalyzer;
import org.apache.lucene.analysis.WhitespaceAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;

public class SearchEngine {
 private static SearchEngine instance;
 private Analyzer standardAnalyzer;
 private Analyzer whitespaceAnalyzer;
 private Analyzer simpleAnalyzer;
 private Analyzer currentAnalyzer;
 private String directory;

 private static final int STANDARD = 0;
 private static final int WHITESPACE = 1;
 private static final int SIMPLE = 2;

 public static SearchEngine getInstance() {
  if (instance == null) {
   synchronized (SearchEngine.class) {
    if (instance == null)
     instance = new SearchEngine();
   }
  }

  return instance;
 }

 private SearchEngine() {
  instance = null;
  currentAnalyzer = null;
  standardAnalyzer = new StandardAnalyzer();
  whitespaceAnalyzer = new WhitespaceAnalyzer();
  simpleAnalyzer = new SimpleAnalyzer();
 }

 public boolean setIndexDirectory(String directory) throws IOException {
  if (this.currentAnalyzer == null) {
   return false;
  }

  this.directory = directory;

  Directory fsDir = null;
  IndexWriter writer = null;
  try {
   fsDir = FSDirectory.getDirectory(directory, false);
   if (!fsDir.fileExists(“segments”)) {
    writer = new IndexWriter(this.directory, currentAnalyzer, true);
   }
  } finally {
   if (writer != null)
    try {
     writer.close();
    } catch (IOException ex) {
    };
   if (fsDir != null)
    try {
     fsDir.close();
    } catch (IOException ex) {
    }
  }

  return true;
 }

 public boolean setAnalyzer(int type) {
  if (type == STANDARD) {
   this.currentAnalyzer = this.standardAnalyzer;
  } else if (type == WHITESPACE) {
   this.currentAnalyzer = this.whitespaceAnalyzer;
  } else if (type == SIMPLE) {
   this.currentAnalyzer = this.simpleAnalyzer;
  } else {
   return false;
  }

  return true;
 }

 public boolean makeIndex(String word) throws IOException {
  if (this.currentAnalyzer == null) {
   return false;
  }

  IndexWriter writer = null;

  try {
   writer = new IndexWriter(this.directory, this.currentAnalyzer,
     false);

   Document document = new Document();
   document.add(new Field(“word”, word, Store.YES, Index.TOKENIZED));
   writer.addDocument(document);
  } finally {
   if (writer != null)
    try {
     writer.close();
    } catch (IOException ex) {
    };
  }
  return true;
 }

 public List searchIndex(String queryString) throws IOException,
   ParseException {
  Directory fsDir = null;
  IndexSearcher is = null;

  try {
   fsDir = FSDirectory.getDirectory(this.directory, false);
   is = new IndexSearcher(fsDir);

   QueryParser parser = new QueryParser(“word”, this.currentAnalyzer);

   Query query = parser.parse(queryString);
   Hits hits = is.search(query);

   List<String> list = new ArrayList<String>();

   if (hits.length() > 0) {
    for (int i = 0; i < hits.length(); i++) {
     Document doc = hits.doc(i);
     list.add(doc.get(“word”));
    }
   }

   return list;
  } finally {
   if (is != null)
    try {
     is.close();
    } catch (IOException ex) {
    }
   if (fsDir != null)
    try {
     fsDir.close();
    } catch (IOException ex) {
    }
  }
 }
}[/CODE]

사용은 다음과 같이 한다.

초기화 :
[CODE]SearchEngine engine = SearchEngine.getInstance();
// 0 : Standard Analyzer, 1 : Whitespace Analyzer, 2 : Simple Analyzer
engine.setAnalyzer(0);
setIndexDirectory(“C:\LuceneIndex”);[/CODE]

인덱스 생성 :
[CODE]engine.makeIndex(“아이군의 홈페이지 주소는 theeye.pe.kr이다”);[/CODE]

검색시 :
[CODE]// 검색결과 객체들이 list로 담겨 나옴, 알아서 재사용
List list = engine.searchIndex(“아이군”); [/CODE]

위와같다. 이 글을 보는 분들은 루씬에 대해 어느정도 사전지식이 있는 분일것이다. 이것을 이해하는것은 어렵지 않았을것이라 생각한다.

위와같은 예제로(각각의 인덱스는 String형 하나가 아닌, Beans형의 특정 데이터 객체였다) 10만건을 입력해 놓고 검색해 보니 0.1초 이상 걸리지 않았다.

검색엔진 알고리즘에 사전지식이 없는 사람도 이정도로 사용할수 있다는것에 감탄을 표하는 바이다.

각각의 Analyzer차이와 인덱스 및 검색 옵션 여러가지 Term들에 대해서는 앞으로 계속 짬짬이 글을 써보겠다.