루씬은 색인과 검색 두가지로 나뉜다. 기존 색인을 검색 할 때도 쓰이지만, 특정 문장을 검증 할때도 쓰인다.
루씬의 version은 3.6 기준으로 작성하였다.
먼저 검색은 두가지 객체가 중요하다. IndexReader와 IndexSearcher 여기서 reader는 특정 색인의 접근을 해주는 객체이며, searcher는 그 색인을 찾고자 하는 검색 룰을 전달해주는 넘이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | private IndexSearcher searcher; private Directory dir; private IndexReader reader; private String textField; @Before public void setup() throws IOException { textField = "text"; dir = FSDirectory.open(new File(dirPath)); reader = IndexReader.open(dir); searcher = new IndexSearcher(reader); } @After public void tearDown() throws IOException { searcher.close(); reader.close(); } | cs |
먼저 초기 셋팅을 해준다. 기존에 만들어 놓은 index경로는 dirPath가 될 것이다. 검색 필드는 "text"로 지정한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | private ArrayList<Document> dumpHits(IndexSearcher searcher, TopDocs hits, String fieldName) throws IOException { ArrayList<Document> docList = null; if (hits.totalHits == 0) { return null; } docList = new ArrayList<Document>(); for (ScoreDoc match : hits.scoreDocs) { Document doc = searcher.doc(match.doc); } return docList; } public void searchIndex(String searchString, String field) throws ParseException, IOException { System.out.println("\nSearching for '" + searchString + "' using QueryParser"); QueryParser queryParser = new QueryParser(Version.LUCENE_36, field, new StandardAnalyzer(Version.LUCENE_36)); Query query = queryParser.parse(searchString); System.out.println("Type of query: " + query.getClass().getSimpleName()); TopDocs hits = searcher.search(query, searcher.maxDoc()); dumpHits(searcher, hits, field); } @Test public void testHtmlBasicSearchData() { String andWordSearch = "cross"; if (andWordSearch != null && andWordSearch != "") { try { searchIndex(andWordSearch, textField); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } | cs |
가장 기본인 검색이다... (개인적으로 이방법은 안쓴다.. =_=; 찾는것도 힘드네 ㅋㅋ)
여기서 중요한 함수는 searchIndex라는 함수다.
중요한 객체는 총 3가지 , QueryParser와 Query, TopDocs 이 객체만 알면 Lucene을 가지고 분석을 자유롭게 할 수 있을 것이다. 먼저 한개씩 설명하자면,
1. QueryParser : 입력한 Query문을 Lucene이 알아먹을 수 있는 Query로 변환한다. 쉽게 말하자면 당신이 넣은 쿼리를 선택한 분석기에 맞게 쿼리 객체 형태로 반환한다.
2. Query : QueryParser로 변환된 쿼리 객체를 searcher 객체로 전달한다. (Query의 종류는 상당히 많으며, 최상위 객체가 Query이다. 종류는 TermQuery, BooleanQuery, Fuzzy, WildCard 등등 있다)
3. TocDocs : 검색된 결과를 보여준다.
위의 결과를 돌려보면
1 2 3 4 5 | Searching for 'cross' using QueryParser Type of query: TermQuery | cs |
이런 형태가 나오고 dubugging모드로 Query를 보면 text:cross 란 쿼리로 변환이 될것이다.
아마도 default Query는 TermQuery 이며, 객체를 통해 Lucene이 알아먹을 수 있는 형태의 쿼리로 변환 되는 듯 보인다.
이후 좀더 복잡한 쿼리에 대해 알아보겠다.
먼저 collections를 확인해보자.
1 2 3 4 5 6 7 | show collections //result testReduce users | cs |
show collections를 하면 결과가 나온다. 앞서 만든 mapreduce에 사용한 testReduce와 데이타를 저장한 users가 존재한다.
그럼 system.js를 만들어보자. 앞서 만들어놓은 함수들을 어딘가 복사해놓자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | db.system.js.insert( [ { _id: "map", value : function () { var key = {}; key.eyeColor = this.eyeColor; key.gender = this.gender; var value = {}; value.age = this.age; value.count = 1; emit(key, value); } }, { _id: "reduce", value : function (key, values){ var returnVal = {}; var totalAge =0; var totalCount = 0; for( index in values){ totalAge += values[index].age; totalCount += values[index].count; } return {"age":totalAge , "count" : totalCount}; } }, { _id : "finalize", value : function(key, values){ return { "age" : values.age, "count" : values.count, "avg" : values.age/values.count }; } } ] ); | cs |
1 2 3 4 5 6 | system.indexes system.js testReduce users | cs |
system.js 라는 collections과 indexes라는넘이 추가되었다.
이제 mapReduce를 하기전에 마지막으로 위에 값들을 load 를 해야한다.
1 2 | db.loadServerScripts(); | cs |
위에 구문을 쓰면 몽고 디비의 서버에 맵리듀스 함수가 로드 된것이다.
그럼 map,reduce, finalize 라는 _id의 값은 어떻게 되있는지 확인하자
map; reduce; finalize; 라는 명령으로 확인하면 다음과 같은 결과를 확인 할 수 있다.
아래는 map; 의 함수를 보여주고있다.
1 2 3 4 5 6 7 8 9 10 | function __cf__13__f__anonymous_function() { var key = {}; key.eyeColor = this.eyeColor; key.gender = this.gender; var value = {}; value.age = this.age; value.count = 1; emit(key, value); } | cs |
이제 다시한번 mapreduce를 하는데 이번엔 out의 옵션을 좀 추가해보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | db.users.mapReduce( map,reduce, { out : { reduce : "testReduce" }, //map들어갈 collection 필터링 할때 query : { "index" : { $gte : 1000 } }, //가령 추천수가 가장 많은 사람을 쓸때 오름 차순으로 하면 제일 먼저 나오는 사람이 젤위이므로 이 사람을 빨리 가져올때 사용. //sort : {}, //reduce 를 한 결과를 가공을 할때 사용. finalize : finalize, //scope는 map, reduce, finalize 함수의 전역 변수로 사용된다. //사용시는 $변수명으로 사용하면된다. //scope: { "temp" : 1}, //몽고디비는 bson 타입으로 저장을한다. 이때 속도가 좀 걸리는데 json 형태로 할것인지를 선택하는 옵션이다. //그러나 json의 경우 속도는 빠르나 collection document의 제한이 걸린다. 그래서 안쓰는게 상책. //jsMode: <boolean>, //verbose: <boolean> } ); | cs |
out의 옵션에 reduce를 주고 "testReduce" collection명을 주었다. 실행하면 결과는 testReduce로 저장될 것이다. 이제
db.testReduce.find()로 값을 확인하자.
아까 값에 reduce 된 값이 보일것이다.
후 여기까지.....
mapreduce는 map 과 reduce의 합성어 이다.
map은 특정 collection의 특정 key 와 value를 map으로 하며, map의 key는 중복이 안되며, value는 list에 쌓이는 형태로 된다.
reduce는 map의 데이터를 감소 시키는 역활을 한다. reduce를 통해 특정 값을 뽑아낸다.이를 통계치 데이터로 사용하게 된다.
1 2 3 | use dbname db.users.find(); | cs |
1 2 3 4 5 6 7 8 9 10 11 | { "_id" : "564c3240e5aa2a5f7c7bd7f5", "index" : 0, "isActive" : false, "age" : 30, "eyeColor" : "green", "name" : "Rodgers Grant", "gender" : "male", "company" : "VERTON", "email" : "rodgersgrant@verton.com" } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 | //make map var map = function () { //맵은 단순 key value로 선언을 하며 , 이 값들은 emit을 통해 reduce로 전달을 한다. //this는 현재 collection에 document가 저장된다. var key = {}; key.eyeColor = this.eyeColor; key.gender = this.gender; var value = {}; value.age = this.age; value.count = 1; emit(key, value); }; | cs |
어라 js랑 똑같다. 걍 js 함수다 . map이라는 함수에 key에는 eyeColor와 gender를 저장하고, value에는 age와 count를 저장한다. 여기서 눈여겨 볼 껀 this라는 넘이다. 이 this는 현재 사용하고 있는 collection을 뜻하며, 이 collection의 filed명을 쓰면 그 값들이 전달된다. 자 위에 map을 선언 하면 다음과 같은 비슷한 결과 형태로 될것이다. (머 내부적으로 어떻게 저장하는지는 몰라서 ... 추측하건데...=_=;)
1 | green.male : [{ 30,1},{36,1}............] | cs |
1 2 3 4 5 6 7 8 9 10 11 12 | var reduce = function (key, values){ var returnVal = {}; var totalAge =0; var totalCount = 0; for( index in values){ totalAge += values[index].age; totalCount += values[index].count; } //return type은 map의 기준의 value로 결정되므로 reduce의 return type의 value랑 똑같은 형태로 맞춰줘야한다.!! return {"age":totalAge , "count" : totalCount}; } | cs |
1 2 3 4 5 6 7 8 9 | var finalize = function(key, values){ return { "age" : values.age, "count" : values.count, "avg" : values.age/values.count }; } | cs |
finalize는 reduce의 마지막 나온 결과를 건네주게 된다. 여기서 마지막이란 trycatch의 finaliy 정도 생각 하면 되겠다. finalize는 결과를 마음 대로 수정해도 된다.
자 이제 mapreduce 함수를 호출 해보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | db.users.mapReduce( map,reduce, { //없으면 현재 db에 만든다. /** * out: { <action>: <collectionName> [, db: <dbName>] [, sharded: <boolean> ] [, nonAtomic: <boolean> ] } <action> replace - 기존의 있는 콜렉션을 없애고 mapreduce결과를 집어 넣는다. merge - 이미 있는 콜렉션을 넣는데 현재 키가 존재할경우 오버라이트 한다. reduce - 같이 키로 결과가 있으면 그 결과들 끼리 reduce를 한다. db - output collection 을 쓸 db이름을 정의 default db는 mapreduce collection과 같은 db이다. sharded - 맵리듀스 결과 collection을 바로 샤딩하고 싶을때 true로 변경. nonAtomic - mapreduce 사용시 true로 사용하면 lock이 걸리지 않는다. (상황에 따라 써야겠구만.) **/ out : "testReduce", //map들어갈 collection 필터링 할때 query : { "index" : { $gte : 1000 } }, //가령 추천수가 가장 많은 사람을 쓸때 오름 차순으로 하면 제일 먼저 나오는 사람이 젤위이므로 이 사람을 빨리 가져올때 사용. //sort : {}, //reduce 를 한 결과를 가공을 할때 사용. finalize : finalize, //scope는 map, reduce, finalize 함수의 전역 변수로 사용된다. //사용시는 $변수명으로 사용하면된다. //scope: { "temp" : 1}, //몽고디비는 bson 타입으로 저장을한다. 이때 속도가 좀 걸리는데 json 형태로 할것인지를 선택하는 옵션이다. //그러나 json의 경우 속도는 빠르나 collection document의 제한이 걸린다. 그래서 안쓰는게 상책. //jsMode: <boolean>, //verbose: <boolean> } ); | cs |
파라미터는 총 3개다. map, reduce, 3번째는 object이다. 중요한것은 이 3번째 파라미터...
먼저 out 이라는 넘이 나온다. 단순히 값을 쓰면, 지정한 값으로 collection을 만들고 그 collection에 mapreduce 값을 쓴다. 이때 위에 주석으로 써놓았듯이 옵션에는 총 3가지가 있다. replace, merge, reduce 이 3개가 있는데 default는 replace 로 쓴다.
query라는 넘은 mapreduce를 할 collection의 값을 특정 결과로 만들고 난 후 mapreduce로 전달한다. 위에 예제는 users collection에 index가 1000 이상인 사람들에 대해 mapreduce를 한다는 예시이다.
finalize 는 위에 선언해놓은 finalize 함수의 변수를 쓰면 된다. 나머지 옵션은 주석으로 써놓았으니 살펴 보면된다.
이 mapreduce는 cursor라는 곳에 저장을 한다. 이 cursor는 휘발성이므로 계속적으로 사용 할때는 위와 같이 사용 해서는 안된다. 다음에는 이를 막고자 할때 사용하는 system.js 에 대해 알아본다.