이번 글에서는
루씬인액션에서 소개되고 있는
Near Realtime Search에 대해서 작성해보려고 합니다.
기본적으로 IndexSearcher는 IndexWriter에 의한 변경 사항을
바로바로 반영하지를 못 합니다. 일반적으로 commit이 된 이후 IndexSearcher를 새로 생성하여야
IndexWriter에 의한 변경된 내용을 반영 할 수 있습니다.
제가 내부적으로 구현하여 사용하고 있는
프로그램에서도 이러한 부분을 Searcher를 새로 만들어서 사용하고 있습니다.
RealTimeSearch.java
파일명은 RealTimeSearch이지만 사실 RealTimeSearch에 대한 예제는 아닙니다.
지금까지 루씬에서 IndexWriter에 의한 변경을 IndexSearcher에서 반영하기 위해 사용했던
방식을 테스트케이스 형태로 구현해둔 것 입니다.
우선 init()메서드를 통해 메모리에 문서를 색인하여 놓습니다.
그리고 private으로 deleteDocument, addDocument 메서드를 구현하였습니다.
이를 사용하여 IndexWriter에 의한 변경 내용이 어떻게 반영이 되는지 확인해 볼 것 입니다.
첫번째 테스트케이스 searchAfterDocumentDeleted 에서는
ids가 1인 문서를 찾고... deleteDocument 메서드를 실행하여 ids가 1인 문서를 삭제합니다.
그리고 다시 ids가 1인 문서를 찾고 있습니다. deleteDocument에서 commit까지 하였지만 이미 생성되어 있던
IndexSearcher는 이 내용을 반영하지 못 합니다.
searchAfterDocumentDeleteIndexReaderReopen 메서드를 보시면
마찬가지로 ids가 1인 문서를 삭제를 한 후 ids가 1인 문서를 검색하는데 그 전에 IndexReader를 다시 생성하는
부분이 있습니다.
바로 이 부분이 지금까지 사용했던 IndexWriter에 의한 변경 내용을 반영하는 방법이었습니다.
이렇게 구현을 해도 기능은 이상없이 잘 적용이 됩니다. 하지만 책에서는 IndexWriter의 commit 작업이 디스크 IO를 유발하고
기타 부하를 주는 작업이라고 하네요.. 그래서 commit을 하지 않아도 IndexSearcher가 변경된 내용을 반영 할 수 있는
NearRealTime Search가 바로 이번에 루씬인액션에서 소개되었습니다.
NearRealTimeTest.java
코드가 좀 길지만 맨 위 메서드가 책에서 나온 예제이고..
그 아래 두개의 메서드는 첫번째 메서드에서 deprecated된 메서드의 사용을 제가 수정한 메서드 두개입니다.
내용의 핵심은 IndexWrtier의 IndexReader를 얻어서 이를 사용하여 IndexSearcher를 생성하는 것 입니다.
기본적으로 IndexReader를 다시 open하는 작업은 동일하지만 IndexWriter가 commit을 하지 않아도
변경 된 내용을 IndexSearcher가 반영 할 수 있다는 부분이 다른 점 입니다.
IndexSearcher는 아래와 같이 생성합니다.
다만, 현재 3.3 버전에서는 IndexWriter.getReader 메서드가 deprecated처리가 되었기 때문에
이 부분을 아래와 같이 수정하였습니다.
테스트케이스를 보시면 commit을 하지 않아도 문서가 추가되고 삭제된 내용이 IndexSearcher에 의해서
반영되고 있는 것을 확인 하실 수 있습니다.
https://github.com/need4spd/aboutLucene
에서 체크아웃 받으 실 수 있습니다.
루씬인액션에서 소개되고 있는
Near Realtime Search에 대해서 작성해보려고 합니다.
기본적으로 IndexSearcher는 IndexWriter에 의한 변경 사항을
바로바로 반영하지를 못 합니다. 일반적으로 commit이 된 이후 IndexSearcher를 새로 생성하여야
IndexWriter에 의한 변경된 내용을 반영 할 수 있습니다.
제가 내부적으로 구현하여 사용하고 있는
프로그램에서도 이러한 부분을 Searcher를 새로 만들어서 사용하고 있습니다.
RealTimeSearch.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.tistory.devyongsik.search; | |
import java.io.IOException; | |
import org.apache.lucene.analysis.WhitespaceAnalyzer; | |
import org.apache.lucene.document.Document; | |
import org.apache.lucene.document.Field; | |
import org.apache.lucene.document.Field.TermVector; | |
import org.apache.lucene.document.NumericField; | |
import org.apache.lucene.index.CorruptIndexException; | |
import org.apache.lucene.index.IndexReader; | |
import org.apache.lucene.index.IndexWriter; | |
import org.apache.lucene.index.IndexWriterConfig; | |
import org.apache.lucene.index.Term; | |
import org.apache.lucene.search.IndexSearcher; | |
import org.apache.lucene.search.Query; | |
import org.apache.lucene.search.TermQuery; | |
import org.apache.lucene.search.TopDocs; | |
import org.apache.lucene.store.Directory; | |
import org.apache.lucene.store.LockObtainFailedException; | |
import org.apache.lucene.store.RAMDirectory; | |
import org.apache.lucene.util.Version; | |
import org.junit.Assert; | |
import org.junit.Before; | |
import org.junit.Test; | |
/** | |
* @author need4spd, need4spd@cplanet.co.kr, 2011. 7. 27. | |
* | |
*/ | |
public class RealTimeSearch { | |
private String[] ids = {"1","2","3"}; | |
private String[] titles = {"lucene in action action","hadoop in action","ibatis in action"}; | |
private String[] contents = {"lucene is a search engine", "hadoop is a dist file system","ibatis is a db mapping tool"}; | |
private int[] prices = {4000, 5000, 2000}; | |
private Directory directory = new RAMDirectory(); | |
private IndexWriter getWriter() throws CorruptIndexException, LockObtainFailedException, IOException { | |
IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_33, new WhitespaceAnalyzer(Version.LUCENE_33)); | |
IndexWriter indexWriter = new IndexWriter(directory, conf); | |
return indexWriter; | |
} | |
@Before | |
public void init() throws CorruptIndexException, LockObtainFailedException, IOException { | |
IndexWriter indexWriter = getWriter(); | |
for(int i = 0; i < ids.length; i++) { | |
Document doc = new Document(); | |
doc.add(new Field("ids", ids[i], Field.Store.YES, Field.Index.NOT_ANALYZED)); | |
doc.add(new Field("titles", titles[i], Field.Store.YES, Field.Index.ANALYZED)); | |
doc.add(new Field("titles2", titles[i], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS)); | |
doc.add(new Field("contents", contents[i], Field.Store.YES, Field.Index.ANALYZED, TermVector.YES)); | |
NumericField numField = new NumericField("price", Field.Store.YES, true); | |
numField.setIntValue(prices[i]); | |
doc.add(numField); | |
indexWriter.addDocument(doc); | |
} | |
//indexWriter.commit(); | |
indexWriter.close(); | |
} | |
private void deleteDocument() throws CorruptIndexException, LockObtainFailedException, IOException { | |
IndexWriter indexWriter = getWriter(); | |
indexWriter.deleteDocuments(new Term("ids", "1")); | |
indexWriter.commit(); | |
indexWriter.close(); | |
} | |
private void addDocument() throws CorruptIndexException, LockObtainFailedException, IOException { | |
IndexWriter indexWriter = getWriter(); | |
Document doc = new Document(); | |
doc.add(new Field("ids", "4", Field.Store.YES, Field.Index.NOT_ANALYZED)); | |
doc.add(new Field("titles", "computer", Field.Store.YES, Field.Index.ANALYZED)); | |
doc.add(new Field("titles2", "computer", Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS)); | |
indexWriter.addDocument(doc); | |
indexWriter.commit(); | |
indexWriter.close(); | |
} | |
@Test | |
public void searchAfterDocumentDeleted() throws CorruptIndexException, IOException { | |
IndexSearcher indexSearcher = new IndexSearcher(directory); | |
Term t = new Term("ids", "1"); | |
Query q = new TermQuery(t); | |
TopDocs docs = indexSearcher.search(q, 10); | |
Assert.assertEquals(1, docs.totalHits); | |
//삭제 후 다시 검색 해 본다. | |
deleteDocument(); | |
docs = indexSearcher.search(q, 10); | |
Assert.assertEquals(1, docs.totalHits); | |
} | |
@Test | |
public void searchAfterDocumentAdded() throws CorruptIndexException, IOException { | |
IndexSearcher indexSearcher = new IndexSearcher(directory); | |
Term t = new Term("ids", "4"); | |
Query q = new TermQuery(t); | |
TopDocs docs = indexSearcher.search(q, 10); | |
Assert.assertEquals(0, docs.totalHits); | |
//추가 후 다시 검색 해 본다. | |
addDocument(); | |
docs = indexSearcher.search(q, 10); | |
Assert.assertEquals(0, docs.totalHits); | |
} | |
@Test | |
public void searchAfterDocumentDeleteIndexReaderReopen() throws CorruptIndexException, IOException { | |
//IndexReader를 얻어온다. | |
IndexReader indexReader = IndexReader.open(directory); | |
IndexSearcher indexSearcher = new IndexSearcher(indexReader); | |
Assert.assertTrue(indexReader.isCurrent()); | |
Term t = new Term("ids", "1"); | |
Query q = new TermQuery(t); | |
TopDocs docs = indexSearcher.search(q, 10); | |
Assert.assertEquals(1, docs.totalHits); | |
//삭제 후 다시 검색 해 본다. | |
deleteDocument(); | |
Assert.assertFalse(indexReader.isCurrent()); | |
IndexReader newReader = indexReader.reopen(); | |
if(newReader != indexReader) { | |
indexSearcher = new IndexSearcher(newReader); | |
indexReader.close(); | |
} | |
docs = indexSearcher.search(q, 10); | |
Assert.assertEquals(0, docs.totalHits); | |
} | |
@Test | |
public void searchAfterDocumentAddedIndexReaderReopen() throws CorruptIndexException, IOException { | |
//IndexReader를 얻어온다. | |
IndexReader indexReader = IndexReader.open(directory); | |
IndexSearcher indexSearcher = new IndexSearcher(indexReader); | |
Assert.assertTrue(indexReader.isCurrent()); | |
Term t = new Term("ids", "4"); | |
Query q = new TermQuery(t); | |
TopDocs docs = indexSearcher.search(q, 10); | |
Assert.assertEquals(0, docs.totalHits); | |
//삭제 후 다시 검색 해 본다. | |
addDocument(); | |
Assert.assertFalse(indexReader.isCurrent()); | |
IndexReader newReader = indexReader.reopen(); | |
if(newReader != indexReader) { | |
indexSearcher = new IndexSearcher(newReader); | |
indexReader.close(); | |
} | |
docs = indexSearcher.search(q, 10); | |
Assert.assertEquals(1, docs.totalHits); | |
} | |
} |
파일명은 RealTimeSearch이지만 사실 RealTimeSearch에 대한 예제는 아닙니다.
지금까지 루씬에서 IndexWriter에 의한 변경을 IndexSearcher에서 반영하기 위해 사용했던
방식을 테스트케이스 형태로 구현해둔 것 입니다.
우선 init()메서드를 통해 메모리에 문서를 색인하여 놓습니다.
그리고 private으로 deleteDocument, addDocument 메서드를 구현하였습니다.
이를 사용하여 IndexWriter에 의한 변경 내용이 어떻게 반영이 되는지 확인해 볼 것 입니다.
첫번째 테스트케이스 searchAfterDocumentDeleted 에서는
ids가 1인 문서를 찾고... deleteDocument 메서드를 실행하여 ids가 1인 문서를 삭제합니다.
그리고 다시 ids가 1인 문서를 찾고 있습니다. deleteDocument에서 commit까지 하였지만 이미 생성되어 있던
IndexSearcher는 이 내용을 반영하지 못 합니다.
searchAfterDocumentDeleteIndexReaderReopen 메서드를 보시면
마찬가지로 ids가 1인 문서를 삭제를 한 후 ids가 1인 문서를 검색하는데 그 전에 IndexReader를 다시 생성하는
부분이 있습니다.
IndexReader newReader = indexReader.reopen();
if(newReader != indexReader) {
indexSearcher = new IndexSearcher(newReader);
indexReader.close();
}
바로 이 부분이 지금까지 사용했던 IndexWriter에 의한 변경 내용을 반영하는 방법이었습니다.
이렇게 구현을 해도 기능은 이상없이 잘 적용이 됩니다. 하지만 책에서는 IndexWriter의 commit 작업이 디스크 IO를 유발하고
기타 부하를 주는 작업이라고 하네요.. 그래서 commit을 하지 않아도 IndexSearcher가 변경된 내용을 반영 할 수 있는
NearRealTime Search가 바로 이번에 루씬인액션에서 소개되었습니다.
NearRealTimeTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.tistory.devyongsik.search; | |
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.index.IndexReader; | |
import org.apache.lucene.index.IndexWriter; | |
import org.apache.lucene.index.IndexWriterConfig; | |
import org.apache.lucene.index.Term; | |
import org.apache.lucene.search.IndexSearcher; | |
import org.apache.lucene.search.Query; | |
import org.apache.lucene.search.TermQuery; | |
import org.apache.lucene.search.TopDocs; | |
import org.apache.lucene.store.Directory; | |
import org.apache.lucene.store.RAMDirectory; | |
import org.apache.lucene.util.Version; | |
import org.junit.Assert; | |
import org.junit.Test; | |
/** | |
* @author need4spd, need4spd@cplanet.co.kr, 2011. 7. 27. | |
* | |
*/ | |
public class NearRealTimeTest { | |
@Test | |
public void testNearRealTime() throws Exception { | |
Directory dir = new RAMDirectory(); | |
@SuppressWarnings("deprecation") | |
IndexWriter writer = new IndexWriter(dir, new StandardAnalyzer(Version.LUCENE_30), IndexWriter.MaxFieldLength.UNLIMITED); | |
for (int i = 0; i < 10; i++) { | |
Document doc = new Document(); | |
doc.add(new Field("id", "" + i, Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS)); | |
doc.add(new Field("text", "aaa", Field.Store.NO, Field.Index.ANALYZED)); | |
writer.addDocument(doc); | |
} | |
@SuppressWarnings("deprecation") | |
IndexReader reader = writer.getReader(); | |
IndexSearcher searcher = new IndexSearcher(reader); | |
Query query = new TermQuery(new Term("text", "aaa")); | |
TopDocs docs = searcher.search(query, 1); | |
Assert.assertEquals(10, docs.totalHits); | |
Assert.assertEquals(1,docs.scoreDocs.length); | |
writer.deleteDocuments(new Term("id", "7")); | |
Document doc = new Document(); | |
doc.add(new Field("id", "11", Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS)); | |
doc.add(new Field("text", "bbb", Field.Store.NO, Field.Index.ANALYZED)); | |
writer.addDocument(doc); | |
IndexReader newReader = reader.reopen(); | |
Assert.assertFalse(reader == newReader); | |
reader.close(); | |
searcher = new IndexSearcher(newReader); | |
TopDocs hits = searcher.search(query, 10); | |
Assert.assertEquals(9, hits.totalHits); | |
query = new TermQuery(new Term("text", "bbb")); | |
hits = searcher.search(query, 1); | |
Assert.assertEquals(1, hits.totalHits); | |
newReader.close(); | |
writer.close(); | |
} | |
@Test | |
public void testNearRealTimeRemoveDeprecated() throws Exception { | |
Directory dir = new RAMDirectory(); | |
IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_33, new WhitespaceAnalyzer(Version.LUCENE_33)); | |
IndexWriter writer = new IndexWriter(dir, conf); | |
for (int i = 0; i < 10; i++) { | |
Document doc = new Document(); | |
doc.add(new Field("id", "" + i, Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS)); | |
doc.add(new Field("text", "aaa", Field.Store.NO, Field.Index.ANALYZED)); | |
writer.addDocument(doc); | |
} | |
IndexReader reader = IndexReader.open(writer, true); | |
IndexSearcher searcher = new IndexSearcher(reader); | |
Query query = new TermQuery(new Term("text", "aaa")); | |
TopDocs docs = searcher.search(query, 1); | |
Assert.assertEquals(10, docs.totalHits); | |
Assert.assertEquals(1,docs.scoreDocs.length); | |
writer.deleteDocuments(new Term("id", "7")); | |
Document doc = new Document(); | |
doc.add(new Field("id", "11", Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS)); | |
doc.add(new Field("text", "bbb", Field.Store.NO, Field.Index.ANALYZED)); | |
writer.addDocument(doc); | |
IndexReader newReader = reader.reopen(); | |
Assert.assertFalse(reader == newReader); | |
reader.close(); | |
searcher = new IndexSearcher(newReader); | |
TopDocs hits = searcher.search(query, 10); | |
Assert.assertEquals(9, hits.totalHits); | |
query = new TermQuery(new Term("text", "bbb")); | |
hits = searcher.search(query, 1); | |
Assert.assertEquals(1, hits.totalHits); | |
newReader.close(); | |
writer.close(); | |
} | |
@Test | |
public void testNearRealTimeRemoveDeprecatedDelete() throws Exception { | |
Directory dir = new RAMDirectory(); | |
IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_33, new WhitespaceAnalyzer(Version.LUCENE_33)); | |
IndexWriter writer = new IndexWriter(dir, conf); | |
for (int i = 0; i < 10; i++) { | |
Document doc = new Document(); | |
doc.add(new Field("id", "" + i, Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS)); | |
doc.add(new Field("text", "aaa", Field.Store.NO, Field.Index.ANALYZED)); | |
writer.addDocument(doc); | |
} | |
IndexReader reader = IndexReader.open(writer, true); | |
IndexSearcher searcher = new IndexSearcher(reader); | |
Query query = new TermQuery(new Term("text", "aaa")); | |
TopDocs docs = searcher.search(query, 1); | |
Assert.assertEquals(10, docs.totalHits); | |
Assert.assertEquals(1,docs.scoreDocs.length); | |
writer.deleteDocuments(new Term("id", "7")); | |
Document doc = new Document(); | |
doc.add(new Field("id", "11", Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS)); | |
doc.add(new Field("text", "bbb", Field.Store.NO, Field.Index.ANALYZED)); | |
writer.addDocument(doc); | |
IndexReader newReader = reader.reopen(); | |
Assert.assertFalse(reader == newReader); | |
reader.close(); | |
searcher = new IndexSearcher(newReader); | |
TopDocs hits = searcher.search(query, 10); | |
Assert.assertEquals(9, hits.totalHits); | |
query = new TermQuery(new Term("text", "bbb")); | |
hits = searcher.search(query, 1); | |
Assert.assertEquals(1, hits.totalHits); | |
//삭제 후 | |
writer.deleteDocuments(new Term("text","bbb")); | |
IndexReader newReader2 = newReader.reopen(); | |
Assert.assertFalse(newReader == newReader2); | |
newReader.close(); | |
query = new TermQuery(new Term("text", "bbb")); | |
searcher = new IndexSearcher(newReader2); | |
hits = searcher.search(query, 1); | |
Assert.assertEquals(0, hits.totalHits); | |
newReader2.close(); | |
writer.close(); | |
} | |
} |
코드가 좀 길지만 맨 위 메서드가 책에서 나온 예제이고..
그 아래 두개의 메서드는 첫번째 메서드에서 deprecated된 메서드의 사용을 제가 수정한 메서드 두개입니다.
내용의 핵심은 IndexWrtier의 IndexReader를 얻어서 이를 사용하여 IndexSearcher를 생성하는 것 입니다.
기본적으로 IndexReader를 다시 open하는 작업은 동일하지만 IndexWriter가 commit을 하지 않아도
변경 된 내용을 IndexSearcher가 반영 할 수 있다는 부분이 다른 점 입니다.
IndexSearcher는 아래와 같이 생성합니다.
IndexWriter writer = new IndexWriter(dir, new StandardAnalyzer(Version.LUCENE_30), IndexWriter.MaxFieldLength.UNLIMITED);
IndexReader reader = writer.getReader();
IndexSearcher searcher = new IndexSearcher(reader);
다만, 현재 3.3 버전에서는 IndexWriter.getReader 메서드가 deprecated처리가 되었기 때문에
이 부분을 아래와 같이 수정하였습니다.
IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_33, new WhitespaceAnalyzer(Version.LUCENE_33));
IndexWriter writer = new IndexWriter(dir, conf);
IndexReader reader = IndexReader.open(writer, true);
IndexSearcher searcher = new IndexSearcher(reader);
테스트케이스를 보시면 commit을 하지 않아도 문서가 추가되고 삭제된 내용이 IndexSearcher에 의해서
반영되고 있는 것을 확인 하실 수 있습니다.
https://github.com/need4spd/aboutLucene
에서 체크아웃 받으 실 수 있습니다.