포켓몬으로 알아보는 루씬 (1) 인덱싱

jinwook han·2022년 3월 2일
0

이번 글에서는 포켓몬 데이터를 루씬에 저장하겠습니다.
전체 코드는 다음 링크에서 볼 수 있습니다.

포켓몬 데이터 분석

다음은 예시로 사용할 포켓몬 데이터입니다.

abilities,against_bug,against_dark,against_dragon,against_electric,against_fairy,against_fight,against_fire,against_flying,against_ghost,against_grass,against_ground,against_ice,against_normal,against_poison,against_psychic,against_rock,against_steel,against_water,attack,base_egg_steps,base_happiness,base_total,capture_rate,classfication,defense,experience_growth,height_m,hp,japanese_name,name,percentage_male,pokedex_number,sp_attack,sp_defense,speed,type1,type2,weight_kg,generation,is_legendary
"['Overgrow', 'Chlorophyll']",1,1,1,0.5,0.5,0.5,2,2,1,0.25,1,2,1,1,2,1,1,0.5,49,5120,70,318,45,Seed Pokémon,49,1059860,0.7,45,Fushigidaneフシギダネ,Bulbasaur,88.1,1,65,65,45,grass,poison,6.9,1,0
...

타입이 string, double, integer 세 가지임을 알 수 있습니다.
우선 가장 간단한 방법을 사용하겠습니다.
string 타입은 string으로, 숫자 타입은 double로 저장할 겁니다. (integer는 사용하지 않겠습니다.)

pokemon.csv 파싱

인덱싱 하기 전에 pokemon.csv을 파싱합니다.
CsvReader를 사용합니다.

File file = new File("pokemon.csv");
FileReader fileReader = new FileReader(file);
CSVReader csvReader = new CSVReader(fileReader);
List<String[]> pokemonList = csvReader.readAll();

String[] statTitles = pokemonList.get(0);

indexPokemons(pokemonList, statTitles);

pokemonList에는 포켓몬들 데이터가 들어가 있고, statTitles에는 ("attack", "name"...) 과 같은 필드 이름 정보가 있습니다.
한 개의 포켓몬이 가장 작은 데이터 단위(문서)입니다.

IndexWriter 생성

한 문서(포켓몬)를 인덱싱하기 위해서는 IndexWriter가 필요합니다.
특정 디렉토리 설정으로 IndexWriter를 만들겠습니다.

private static IndexWriter getIndexWriter() throws IOException {
  File file = new File("hi");
  Directory directory = FSDirectory.open(file.toPath());

  IndexWriterConfig indexWriterConfig = new IndexWriterConfig();

  return new IndexWriter(directory, indexWriterConfig);
}

생성된 IndexWriter는 다음과 같이 싱글 포켓몬(문서)를 인덱싱합니다.

private static void indexSinglePokemon(String[] statNames, IndexWriter indexWriter, List<String> singlePokemon) throws IOException {
  Document document = new Document();

  for (int i = 0; i < singlePokemon.size(); i++) {`
  indexSinglePokemonStat(singlePokemon.get(i), statNames[i], document);
  }

  indexWriter.addDocument(document);

  indexWriter.commit();
}

필드 인덱싱

세부적인 포켓몬 데이터(ex 이름, 공격력, 방어력 등...)을 인덱싱하기 위해서는 각 지표를 어떤 필드 타입으로 저장할 건지 정해야합니다.

Lucene에는 수많은 필드 타입들이 있습니다.

저는 double 타입은(ex 공격력) DoublePoint로, String 타입은(ex 이름) Field로 인덱싱했습니다.

private static void indexString(Document document, String pokemonStatValue, String statFieldName) {
  FieldType type = new FieldType();
  type.setStored(true);
  Field field = new Field(statFieldName, pokemonStatValue, type);
  document.add(field);
}
...

private static void indexDouble(Document document, String pokemonStatValue, String statFieldName) {`
  DoublePoint doublePoint = new DoublePoint(statFieldName,Double.parseDouble(pokemonStatValue));
  document.add(doublePoint);
}

코드는 여기까지입니다.
이제 main 함수를 실행해서, Lucene에 포켓몬 데이터를 저장하면 됩니다.

왜 doublePoint값이 검색 결과로 안 나오는 걸까?

인덱싱 결과를 보면, DoublePoint로 저장했던 값들이 노출되지 않습니다.

public static void main(String[] args) throws IOException {
  File file = new File("hi");
  Directory directory = FSDirectory.open(file.toPath());
  DirectoryReader directoryReader = DirectoryReader.open(directory);

  IndexSearcher indexSearcher = new IndexSearcher(directoryReader);
  Query query = new MatchAllDocsQuery();
  TopDocs search = indexSearcher.search(query, 50);
  Document doc = indexSearcher.doc(search.scoreDocs[43].doc);

  // 출력
  System.out.println(doc);
}

위 코드는 MatchAllDocsQuery로 전체 문서를 가져온 이후에, 결과를 출력합니다.
그런데 출력 결과를 보면 String 타입만 나오고, doublePoint 타입은 나오지 않습니다.

그 이유는 DoublePoint 타입은 store: false이기 때문입니다.
lucene에서는 저장에 관한 두 가지 개념이 있습니다: index, store
lucene에서 index는 inverted index 자료구조로 저장하는 것을 의미하고, store는 inverted index가 아닌 일반적인 자료구조로 저장하는 것을 의미입니다.

https://lucene.apache.org/core/3_0_3/fileformats.html
(위 링크에서 Types of Fields 참조)

store: false일 경우, 검색 결과 document id는 확인할 수 있지만 검색 결과 document의 해당 필드의 value는 확인할 수 없습니다.
예로 들어 공격력 필드가 store:false이고 공격력이 33 이상인 포켓몬(문서)를 가져오라고 하는 쿼리를 실행하면, 해당 조건을 만족하는 포켓몬이 누구인지는 알 수 있지만 그 포켓몬의 공격력은 확인할 수 없습니다.

위에서 String 타입(ex: 이름)은 store: true로 설정했기 때문에, 검색 결과에서 확인할 수 있습니다.

private static void indexString(Document document, String pokemonStatValue, String statFieldName) {`
  FieldType type = new FieldType();
  type.setStored(true); // 검색 결과에서 값 확인 가능!
  Field field = new Field(statFieldName, pokemonStatValue, type);
  document.add(field);
}

보통 데이터베이스에서 저장이 되어 있다는 건, 곧 값을 눈으로 확인할 수 있다와 같은 의미입니다.
여기서 루씬은 특이하게도, 두 개의 기능이 구분됩니다.

검색 결과에서 값을 확인하고 싶다면, DoublePoint 대신 StoredField를 사용하면 됩니다.

마치며

후에 검색 관련 연습을 진행하며, 인덱싱하는 조건도 조금씩 수정하겠습니다.
감사합니다.

참고자료

lucene 자료구조
https://lucene.apache.org/core/3_0_3/fileformats.html
https://sease.io/2015/07/exploring-solr-internals-lucene.html

0개의 댓글