11.5. リモート検索
リモート検索は埋め込みと非常によく似ていますが、データはネットワークとストレージの両方のエンコーディングとして Google Protocol Buffers を使用する必要があるという顕著な違いがあります。さらに、Hibernate Search アノテーションに依存せずに、データ構造とインデックス要素を定義する protobuf スキーマを記述 (または Java クラスから生成) する必要があります。
protobuf を使用すると、リモートクエリーは Java だけでなく、REST、C#、および Node.js クライアントで機能します。
11.5.1. リモートクエリーの例
組み込みクエリーから Book Sample を再確認しますが、今回は Java Hot Rod クライアントと Infinispan サーバーを使用します。Book
と呼ばれるオブジェクトは books と呼ばれる Infinispan キャッシュに保存されます。Book インスタンスはインデックス化されるため、キャッシュのインデックスを有効にします。
infinispan.xml
<infinispan> <cache-container default-cache="default"> <replicated-cache name="books"> <indexing> <indexed-entities> <indexed-entity>book_sample.Book</indexed-entity> </indexed-entities> </indexing> </replicated-cache> </cache-container> </infinispan>
または、キャッシュのインデックス化がインデックス化されていない場合は、<encoding>
を application/x-protostream
として設定して、ストレージがクエリー可能であることを確認します。
infinispan.xml
<infinispan> <cache-container default-cache="default"> <replicated-cache name="books"> <encoding media-type="application/x-protostream"/> </replicated-cache> </cache-container> </infinispan>
各 Book
は以下の例のように定義されます。@Protofield
アノテーションを使用してプロトコルバッファーメッセージフィールドを識別し、フィールドの @ProtoDoc
アノテーションを使用してインデックス属性を設定します。
Book.java
import org.infinispan.protostream.annotations.ProtoDoc; import org.infinispan.protostream.annotations.ProtoFactory; import org.infinispan.protostream.annotations.ProtoField; @ProtoDoc("@Indexed") public class Book { @ProtoDoc("@Field(index=Index.YES, analyze = Analyze.YES, store = Store.NO)") @ProtoField(number = 1) final String title; @ProtoDoc("@Field(index=Index.YES, analyze = Analyze.YES, store = Store.NO)") @ProtoField(number = 2) final String description; @ProtoDoc("@Field(index=Index.YES, analyze = Analyze.YES, store = Store.NO)") @ProtoField(number = 3, defaultValue = "0") final int publicationYear; @ProtoFactory Book(String title, String description, int publicationYear) { this.title = title; this.description = description; this.publicationYear = publicationYear; } // public Getter methods omitted for brevity }
上記のアノテーションは、コンパイル中に Book
インスタンスの読み取り、書き込み、およびクエリーに必要なアーティファクトを生成します。この生成を有効にするには、空のコンストラクターまたはインターフェイスを使用して新しく作成されたクラスで @AutoProtoSchemaBuilder
アノテーションを使用します。
RemoteQueryInitializer.java
import org.infinispan.protostream.SerializationContextInitializer; import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; @AutoProtoSchemaBuilder( includeClasses = { Book.class }, schemaFileName = "book.proto", schemaFilePath = "proto/", schemaPackageName = "book_sample") public interface RemoteQueryInitializer extends SerializationContextInitializer { }
コンパイル後、ファイル book.proto
ファイルが、アノテーションが付いたインターフェイスの実装 RemoteQueryInitializerImpl.java
とともに、設定された schemaFilePath
に作成されます。この具体的なクラスは Hot Rod クライアントコードで直接使用して、シリアル化コンテキストを初期化します。
すべてを 1 つにまとめます。
RemoteQuery.java
package org.infinispan; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import org.infinispan.client.hotrod.RemoteCache; import org.infinispan.client.hotrod.RemoteCacheManager; import org.infinispan.client.hotrod.Search; import org.infinispan.client.hotrod.configuration.ConfigurationBuilder; import org.infinispan.query.dsl.Query; import org.infinispan.query.dsl.QueryFactory; import org.infinispan.query.remote.client.ProtobufMetadataManagerConstants; public class RemoteQuery { public static void main(String[] args) throws Exception { ConfigurationBuilder clientBuilder = new ConfigurationBuilder(); // RemoteQueryInitializerImpl is generated clientBuilder.addServer().host("127.0.0.1").port(11222) .security().authentication().username("user").password("user") .addContextInitializers(new RemoteQueryInitializerImpl()); RemoteCacheManager remoteCacheManager = new RemoteCacheManager(clientBuilder.build()); // Grab the generated protobuf schema and registers in the server. Path proto = Paths.get(RemoteQuery.class.getClassLoader() .getResource("proto/book.proto").toURI()); String protoBufCacheName = ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME; remoteCacheManager.getCache(protoBufCacheName).put("book.proto", Files.readString(proto)); // Obtain the 'books' remote cache RemoteCache<Object, Object> remoteCache = remoteCacheManager.getCache("books"); // Add some Books Book book1 = new Book("Infinispan in Action", "Learn Infinispan with using it", 2015); Book book2 = new Book("Cloud-Native Applications with Java and Quarkus", "Build robust and reliable cloud applications", 2019); remoteCache.put(1, book1); remoteCache.put(2, book2); // Execute a full-text query QueryFactory queryFactory = Search.getQueryFactory(remoteCache); Query<Book> query = queryFactory.create("FROM book_sample.Book WHERE title:'java'"); List<Book> list = query.execute().list(); // Voila! We have our book back from the cache! } }
11.5.2. Protobuf でエンコードされたエントリーのインデックス化
Remote Query の例にあるように、protobuf エンティティーのクエリーに必要な 1 つの手順は、クライアントおよびサーバーにエンティティーに関連するメタデータ (.proto ファイル) を提供することです。
記述子は ___protobuf_metadata
という名前のサーバーで専用キャッシュに保存されます。このキャッシュのキーと値はどちらもプレーンテキストの文字列です。したがって、新しいスキーマの登録は、スキーマ名をキーとして、スキーマファイル自体を値として使用して、このキャッシュに対して put()
操作を実行するのと同じくらい簡単です。
代わりに、CLI(cache-container=*:register-proto-schemas()
操作経由)、管理コンソール、REST エンドポイント /rest/v2/schemas
、または JMX 経由で ProtobufMetadataManager
MBean を使用できます。セキュリティーが有効になっている場合、リモートプロトコル経由でスキーマキャッシュにアクセスするには、ユーザーが '___schema_manager' ロールに属している必要があります。
キャッシュに対してインデックスが有効になっている場合でも、 protobuf スキーマドキュメントアノテーション (@ProtoDoc)
内の @Indexed
および @Field
を使用してインデックスする必要のあるフィールドを指定しない限り、Protobuf でエンコードされたエントリーのフィールドはインデックス付けされません。
11.5.3. 分析
分析は、入力データを、インデックスを作成してクエリーできる 1 つ以上の用語に変換するプロセスです。Embedded Query のマッピングは、Lucene ベースのアナライザーのセットをサポートする Hibernate Search アノテーション を介して行われますが、クライアントサーバーモードでは、アナライザー定義はプラットフォームに依存しない方法で宣言されます。
11.5.3.1. デフォルトのアナライザー
Data Grid は、以下のようにリモートクエリーに対してデフォルトのアナライザーのセットを提供します。
定義 | 説明 |
---|---|
| テキストフィールドをトークンに分割し、空白と句読点を区切り文字として扱います。 |
| 非文字で区切り、すべての文字を小文字に変換することにより、入力ストリームをトークン化します。空白と非文字は破棄されます。 |
| テキストストリームを空白で分割し、空白以外の文字のシーケンスをトークンとして返します。 |
| テキストフィールド全体を単一トークンとして扱います。 |
| SnowballPorter フィルターを使用して英語の単語を語幹にします。 |
| デフォルトでサイズ 3 つのグラムである n-gram トークンを生成します。 |
|
テキストフィールドを |
これらのアナライザー定義は Apache Lucene をベースとし、as-is で提供されます。tokenizers、filters、および CharFilters に関する詳細は、適切な Lucene のドキュメントを参照してください。
11.5.3.2. アナライザー定義の使用
アナライザー定義を使用するには、.proto
スキーマファイルで名前でそれらを参照します。
-
Analyze.YES
属性を追加して、プロパティーが分析されていることを示します。 -
@Analyzer
アノテーションで アナライザー定義を指定します。
以下は、参照されたアナライザー定義の例になります。
/* @Indexed */ message TestEntity { /* @Field(store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "keyword")) */ optional string id = 1; /* @Field(store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "simple")) */ optional string name = 2; }
@ProtoField
アノテーションが付けられた Java クラスを使用する場合、宣言は以下のようになります。
@ProtoDoc("@Field(store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = \"keyword\"))") @ProtoField(number = 1) final String id; @ProtoDoc("@Field(store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = \"simple\"))") @ProtoField(number = 2) final String description;
11.5.3.3. カスタムアナライザー定義の作成
カスタムアナライザー定義が必要な場合は、以下を行います。
-
JAR
ファイルにパッケージ化されたProgrammaticSearchMappingProvider
インターフェイスの実装を作成します。 -
JAR
のMETA-INF/services/
ディレクトリーにorg.infinispan.query.spi.ProgrammaticSearchMappingProvider
という名前のファイルを指定します。このファイルには、実装の完全修飾クラス名が含まれている必要があります。 JAR
を Data Grid インストールのlib/
ディレクトリーにコピーします。重要jar は、起動時に Data Grid サーバーで利用できる必要があります。サーバーがすでに実行中の場合は、追加できません。
以下は、
ProgrammaticSearchMappingProvider
インターフェイスの実装例です。import org.apache.lucene.analysis.core.LowerCaseFilterFactory; import org.apache.lucene.analysis.core.StopFilterFactory; import org.apache.lucene.analysis.standard.StandardFilterFactory; import org.apache.lucene.analysis.standard.StandardTokenizerFactory; import org.hibernate.search.cfg.SearchMapping; import org.infinispan.Cache; import org.infinispan.query.spi.ProgrammaticSearchMappingProvider; public final class MyAnalyzerProvider implements ProgrammaticSearchMappingProvider { @Override public void defineMappings(Cache cache, SearchMapping searchMapping) { searchMapping .analyzerDef("standard-with-stop", StandardTokenizerFactory.class) .filter(StandardFilterFactory.class) .filter(LowerCaseFilterFactory.class) .filter(StopFilterFactory.class); } }