CLOVER🍀

That was when it all began.

Apache Lucene 9.0.0でRAMDirectoryが削除されていたという話(代わりにByteBuffersDirectoryを使う)

これは、なにをしたくて書いたもの?

前に、こういうエントリーを書きました。

Apache Luceneでベクトル検索(kNN検索)を試す - CLOVER🍀

自分はApache Luceneで簡単なプログラムを書く時に、インデックスの保存先をインメモリーにすることが多いのですが、その用途で
いつも使っていたRAMDirectoryがなくなっていてちょっと困ったので。

今はByteBuffersDirectoryを使うのがよさそうです。

Apache Lucene 9.0.0でのRAMDirectoryの削除

RAMDirectoryが削除されたのは、Apache Lucene 9.0.0のようです。

Lucene Change Log / Release 9.0.0 / API Changes

対象のissue。

Placeholder for the remainder of the original patch, removing all 8.x-deprecated RAMDirectory classes and replacing their use cases with ByteBuffersDirectory.

[LUCENE-8474] Remove deprecated RAMDirectory - ASF JIRA

Apache Lucene 8.0.0の時点で、RAMDirectoryは非推奨になっていたようですね。

Deprecated.
This class uses inefficient synchronization and is discouraged in favor of MMapDirectory. It will be removed in future versions of Lucene.

RAMDirectory (Lucene 8.0.0 API)

同時実行性が低く、パフォーマンスが悪いことが理由のようです。

[LUCENE-8438] RAMDirectory speed improvements and cleanup - ASF JIRA

というわけで、置き換え先はByteBuffersDirectoryですね。

ByteBuffersDirectory (Lucene 9.10.0 core API)

名前のとおり、java.nio.ByteBufferを使ったApache LuceneDirectoryの実装です。

ただこのクラス、実験的APIなんですけどね…。

WARNING: This API is experimental and might change in incompatible ways in the next release.

もっとも、これを使うのはテスト用途だったりすると思うので、特に問題ないでしょう。

今回はこちらを使ったコードを載せて終わりにしようと思います。

環境

今回の環境はこちら。

$ java --version
openjdk 21.0.2 2024-01-16
OpenJDK Runtime Environment (build 21.0.2+13-Ubuntu-122.04.1)
OpenJDK 64-Bit Server VM (build 21.0.2+13-Ubuntu-122.04.1, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.6 (bc0240f3c744dd6b6ec2920b3cd08dcc295161ae)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 21.0.2, vendor: Private Build, runtime: /usr/lib/jvm/java-21-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.15.0-107-generic", arch: "amd64", family: "unix"

準備

Maven依存関係など。

    <properties>
        <maven.compiler.release>21</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>9.10.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
            <version>9.10.0</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.10.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.25.3</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

Query Parserはなんとなく使った感じですね。

ByteBuffersDirectoryを使う

ByteBuffersDirectoryを使ったテストコードはこちら。

src/test/java/org/littlewings/lucene/directory/ByteBuffersDirectoryTest.java

package org.littlewings.lucene.directory;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.StoredFields;
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 org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Arrays;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

class ByteBuffersDirectoryTest {
    @Test
    void inMemory() throws IOException, ParseException {
        try (Directory directory = new ByteBuffersDirectory()) {
            Analyzer analyzer = new StandardAnalyzer();
            IndexWriterConfig config = new IndexWriterConfig(analyzer);

            try (IndexWriter writer = new IndexWriter(directory, config)) {
                Document document1 = new Document();
                document1.add(new TextField("field1", "Apache Lucene", Field.Store.YES));
                writer.addDocument(document1);

                Document document2 = new Document();
                document2.add(new TextField("field1", "Elasticsearch", Field.Store.YES));
                writer.addDocument(document2);

                Document document3 = new Document();
                document3.add(new TextField("field1", "Apache Solr", Field.Store.YES));
                writer.addDocument(document3);
            }

            try (DirectoryReader reader = DirectoryReader.open(directory)) {
                IndexSearcher searcher = new IndexSearcher(reader);

                QueryParser queryParser = new QueryParser("field1", analyzer);
                Query query = queryParser.parse("field1: Apache");

                TopDocs topDocs =
                        searcher.search(query, 10);
                ScoreDoc[] scoreDocs = topDocs.scoreDocs;
                StoredFields storedFields = searcher.storedFields();

                List<Document> resultDocuments =
                        Arrays
                                .stream(scoreDocs)
                                .map(scoreDoc -> {
                                    try {
                                        return storedFields.document(scoreDoc.doc);
                                    } catch (IOException e) {
                                        throw new UncheckedIOException(e);
                                    }
                                })
                                .toList();

                assertThat(resultDocuments.get(0).getField("field1").stringValue()).isEqualTo("Apache Lucene");
                assertThat(resultDocuments.get(1).getField("field1").stringValue()).isEqualTo("Apache Solr");
            }
        }
    }
}

使い方はとても簡単で、インスタンスを作成してApache LuceneDirectoryとして使えばOKです。

        try (Directory directory = new ByteBuffersDirectory()) {

おわりに

Apache Lucene 9.0.0より前で使えていたRAMDirectoryの代替になるのは、ByteBuffersDirectoryということを書きました。

話としてはそれだけなのですが、RAMDirectoryが見つからなかった時に「さて代わりは???」と探すのにちょっと困ったので
メモとして。