Elasticsearchを理解するためにLuceneを使ってみる
背景
もっとElasticsearchを理解できるように、基本から復習していきます。
(いつまで続くわからないけど)Elasticsearchを基礎から理解するシリーズ第1回はLuceneを使ってみるです。
ElasticsearchはApache Luceneがベースになっているので、Elasticsearchを基本から復習・理解していくには必然的にLuceneのことを理解する必要がありますが、まあまあ難しいのでまずはLuceneを使ってみるところから始めます。
免責
Elasticsearchについて調べたことをまとめています。また、JAVAは初心者です。
おそらく間違えていることがあると思うので、間違いに気づいたらご連絡いただけると嬉しいです。
環境
準備
luceneを追加する
Mavenを使います。
まずdependencyにluceneを追加します。追加するバージョンはともに9.4.1です。
- Apach Lucene(module: core) org.apache.lucene:lucene-coreを選択
- Apach Lucene(module: queryparser) org.apache.lucene:lucene-queryparserを選択
追加の仕方、注意点は以下を参照。
lucene-in-5-minutesをやってみる
ぐぐったらチュートリアルを見つけたので、本のタイトルとisbnコード(両方文字列)を検索するサンプルを試します。
コード
package tutorial; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.StringField; import org.apache.lucene.document.TextField; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.queryparser.classic.ParseException; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TopDocs; import org.apache.lucene.store.ByteBuffersDirectory; import org.apache.lucene.store.Directory; import java.io.IOException; public class Main { public static void main(String[] args) throws IOException, ParseException { // index の作成 StandardAnalyzer analyzer = new StandardAnalyzer(); Directory directory = new ByteBuffersDirectory(); IndexWriterConfig config = new IndexWriterConfig((analyzer)); // ドキュメントの追加 IndexWriter w = new IndexWriter(directory, config); addDoc(w, "Lucene in Action", "193398817"); addDoc(w, "Lucene for Dummies", "55320055Z"); addDoc(w, "Managing Gigabytes", "55063554A"); addDoc(w, "The Art of Computer Science", "9900333X"); w.close(); // 検索 // query の作成 String queryString = args.length > 0 ? args[0]: "lucene"; Query q = new QueryParser("title", analyzer).parse(queryString); // 検索実行 int hitsPerPage = 10; IndexReader reader = DirectoryReader.open(directory); IndexSearcher searcher = new IndexSearcher(reader); TopDocs docs = searcher.search(q, hitsPerPage); ScoreDoc[] hits = docs.scoreDocs; // 結果の表示 System.out.println("Found " + hits.length + " hits."); for (int i=0;i<hits.length;i++){ int docId = hits[i].doc; Document d = searcher.doc(docId); System.out.println((i+1) + "." + d.get("isbn") + "\t" + d.get("title")); } reader.close(); } private static void addDoc(IndexWriter w, String title, String isbn) throws IOException { Document doc = new Document(); doc.add(new TextField("title", title, Field.Store.YES)); doc.add(new StringField("isbn", isbn, Field.Store.YES)); w.addDocument(doc); } }
index作成
インデックスは、ByteBuffersDirectoryを使って、メモリのヒープ領域に作成します。
ドキュメントの追加
analyzerはStandardAnalyzerを使っています。これは、 StandardTokenizer
と LowerCaseFilter
と StopFilter
が使われます。
雑にいうと、英文の場合は空白で区切られ、すべて小文字として扱われます。
ドキュメントはDocumentに対して、Fieldのインスタンスを追加します。各フィールドのフィールドタイプ、フィールド名、値、オプションはFieldクラスで管理しているようです。
まだ中身までちゃんと追えてませんが、文字列処理やindex(luceneでは正確にはsegment)への書き込みはIndexWriterクラスが行います。
queryの作成
当初Mavenでlucene-core
だけ追加していたのですが、QueryParserが存在せずに少しハマりました。
QueryParserはlucene-core
ではなくて、lucene-queryparser
moduleなので忘れずに追加します。
検索実行
検索の流れとしては以下のようになっています。
- DirectoryReaderクラスのopenメソッドで、対象とするDirectoryインスタンスを指定
- IndexReaderインスタンスを指定して、IndexSearcherクラスのインスタンスを作成
- IndexSearcherクラスのsearchメソッドでQueryインスタンスを渡す
実行結果
Found 2 hits. 1.193398817 Lucene in Action 2.55320055Z Lucene for Dummies
検索できた!
indexをファイルに永続化する
String indexDirectoryPath = "./index/";
Directory directory = FSDirectory.open(Paths.get(indexDirectoryPath));
directoryをByteBuffersDirectoryからFSDirectoryに変更します。
これを実行すると作成したインデックスが永続化されています。pathで指定したindexディレクトリができていて中にファイルが作られています。
中身に関しては以下のブログを参照してください。(自分もまだ理解してない...) https://www.m3tech.blog/entry/2021/08/24/162205
index ├── _0.cfe ├── _0.cfs ├── _0.si ├── segments_1 └── write.lock
(おまけ)インデックスの設定
ここでは細かい説明は省きますが、luceneのインデックスにドキュメントを追加する際にはいくつかのステップをふみます。
ざっくり書くと
- メモリ上のバッファに追加する
- バッファがいっぱいになると新しいセグメントをつくる
- セグメントが一定数になればマージする といったステップがあります。
これらの設定はIndexWriterConfigでやるようです。
IndexWriterConfig config = new IndexWriterConfig(analyzer); LogMergePolicy policy = new LogDocMergePolicy(); policy.setMergeFactor(10); config.setMergePolicy(policy); config.setMaxBufferedDocs(100); config.setRAMBufferSizeMB(256.0);
Elasticsearchの設定はここで使われているんだろうなという予想です。
感想
Elasticsearch的にあの処理はこれを使っているのか〜というのがなんとなく見えてきました。 具体的にどういう実装になってるんだろう?というのは引き続き確認していきます。