SolrJ

SolrJ は、Java(または JVM ベースの任意の言語)で記述されたアプリケーションが Solr と簡単に通信できるようにする API です。SolrJ は Solr への接続の詳細を多く隠蔽し、アプリケーションがシンプルな高レベルメソッドを使用して Solr と対話できるようにします。SolrJ はほとんどの Solr API をサポートしており、高度に構成可能です。

SolrJ アプリケーションのビルドと実行

SolrJ API は Solr に同梱されているため、他に何もダウンロードまたはインストールする必要はありません。ただし、SolrJ とその依存関係を含めるようにビルドを設定する必要があります。

一般的なビルドシステム

ほとんどの主流のビルドシステムは依存関係管理を大幅に簡素化し、SolrJ をプロジェクトに追加しやすくなっています。

Ant(Ivy 使用)でビルドされたプロジェクトの場合、次のものを `ivy.xml` に配置します。

<dependency org="org.apache.solr" name="solr-solrj" rev="9.5.0"/>

Maven でビルドされたプロジェクトの場合、次のものを `pom.xml` に配置します。

<dependency>
  <groupId>org.apache.solr</groupId>
  <artifactId>solr-solrj</artifactId>
  <version>9.5.0</version>
</dependency>

Gradle でビルドされたプロジェクトの場合、次のものを `build.gradle` に配置します。

compile group: 'org.apache.solr', name: 'solr-solrj', version: '9.5.0'

`CloudSolrClient` を使用し、ZooKeeper と直接通信する場合、`solr-solrj-zookeeper` アーティファクトへの依存関係を追加する必要があります。

ストリーミング式 クラスを Java コードで使用していない場合は、`solr-solrj-streaming` 依存関係を除外できます。

SolrJ のクラスパスへの手動追加

上記のビルドシステムを使用していない場合でも、SolrJ をビルドに追加するのは簡単です。

ビルド時には、SolrJ jar 自体(`solr-solrj-9.5.0.jar`)のみが必要です。SolrJ を使用するコードを手動でコンパイルするには、次のような `javac` コマンドを使用します。

javac -cp .:$SOLR_TIP/server/solr-webapp/webapp/WEB-INF/lib/solr-solrj-9.5.0.jar ...

実行時には、SolrJ 自体に加えて、SolrJ の依存関係がいくつか必要です。Solr 配布版では、これらの依存関係は Solr の依存関係から分離されていないため、すべてを含めるか、必要とされる正確なセットを手動で選択する必要があります。使用するバージョンに必要な正確な依存関係については、maven リリース を参照してください。次のようなクラスパスでプロジェクトを実行します。

java -cp .:$SOLR_TIP/server/lib/ext:$SOLR_TIP/server/solr-webapp/webapp/WEB-INF/lib/* ...

SolrJ ライブラリによってクライアントアプリケーションのサイズが大きくなることを懸念する場合は、ProGuard などのコード難読化ツールを使用して、使用していない API を削除できます。

SolrJ の概要

その柔軟性にもかかわらず、SolrJ はいくつかのシンプルなインターフェースを中心に構築されています。

Solr へのすべてのリクエストは、SolrClient によって送信されます。SolrClient は SolrJ のコアにある主要な作業馬であり、Solr への接続と通信の処理を行い、ユーザー設定の大部分がここで行われます。

リクエストは SolrRequests の形式で送信され、SolrResponses として返されます。

SolrClients の種類

SolrClient にはいくつかの具体的な実装があり、それぞれ異なる使用パターンまたは回復力モデルを対象としています。

  • HttpSolrClient - クエリ中心のワークロードを対象としていますが、汎用クライアントとしても優れています。単一の Solr ノードと直接通信します。

  • Http2SolrClient - HTTP/2 を活用した非同期、ノンブロッキング、汎用クライアントです。このクラスは実験的なものであるため、API は SolrJ のマイナーバージョンで変更または削除される可能性があります。

  • LBHttpSolrClient - Solrノードのリスト全体にリクエスト負荷を分散します。ノードのヘルスに基づいて、「稼働中」ノードのリストを調整します。

  • LBHttp2SolrClient - LBHttpSolrClientと同様ですが、Http2SolrClientを使用します。このクラスは実験的なものであるため、APIはSolrJのマイナーバージョンで変更または削除される可能性があります。

  • CloudSolrClient - SolrCloudデプロイメントとの通信を目的としています。既に記録されたZooKeeperの状態を使用して、ヘルシーなSolrノードを検出し、リクエストをルーティングします。

  • ConcurrentUpdateSolrClient - インデックス作成中心のワークロードを目的としています。Solrに大量のバッチを送信する前に、内部的にドキュメントをバッファリングします。

  • ConcurrentUpdateHttp2SolrClient - ConcurrentUpdateSolrClientと同様ですが、Http2SolrClientを使用します。このクラスは実験的なものであるため、APIはSolrJのマイナーバージョンで変更または削除される可能性があります。

共通の構成オプション

SolrJの構成の大部分は、SolrClientレベルで行われます。最も一般的/重要なものについては以下で説明します。SolrClientを調整する方法に関する包括的な情報については、関連するクライアントとその対応するビルダーオブジェクトのJavadocを参照してください。

基本URL

ほとんどのSolrClient実装(CloudSolrClientHttp2SolrClientを除く)では、ユーザーは1つ以上のSolr基本URLを指定する必要があります。クライアントはこれを使用してSolrにHTTPリクエストを送信します。ユーザーが提供する基本URLに含めるパスは、作成されたクライアントの動作に影響します。

  1. 特定のコアまたはコレクションを指すパスを含むURL(例:http://hostname:8983/solr/core1)。基本URLでコアまたはコレクションが指定されている場合、そのクライアントで行われる後続のリクエストでは、影響を受けるコレクションを再指定する必要はありません。ただし、クライアントは、そのコア/コレクションへのリクエストの送信に限定され、他のコア/コレクションへのリクエストを送信することはできません。

  2. ルートSolrパスを指すURL(例:http://hostname:8983/solr)。基本URLでコアまたはコレクションが指定されていない場合、任意のコア/コレクションにリクエストを行うことができますが、影響を受けるコア/コレクションはすべてのリクエストで指定する必要があります。

一般的に、SolrClientを単一のコア/コレクションでのみ使用する場合は、そのエンティティをパスに含めるのが最も便利です。より柔軟性が求められる場合は、コレクション/コアを除外する必要があります。

Http2SolrClientの基本URL

Http2SolrClientは、異なるノードへの接続を効率的に管理します。Http2SolrClientbaseUrlを必要としません。baseUrlが提供されない場合、SolrRequest.basePathを設定する必要があります。これにより、Http2SolrClientはリクエストを送信するノードを認識します。そうでない場合は、IllegalArgumentExceptionがスローされます。

CloudSolrClientの基本URL

CloudSolrClientの基本URLを指定することもできますが、URLはルートSolrパスを指す必要があります(例:http://hostname:8983/solr)。コレクション、コア、その他のパスコンポーネントを含めることはできません。

final List<String> solrUrls = new ArrayList<>();
solrUrls.add("http://solr1:8983/solr");
solrUrls.add("http://solr2:8983/solr");
return new CloudSolrClient.Builder(solrUrls).build();

baseUrlが提供されない場合、ZooKeeperホスト(ポート付き)とZooKeeperルートのリストを提供する必要があります。ZooKeeperルートを使用しない場合は、メソッドの一部としてjava.util.Optional.empty()を提供する必要があります。

final List<String> zkServers = new ArrayList<>();
zkServers.add("zookeeper1:2181");
zkServers.add("zookeeper2:2181");
zkServers.add("zookeeper3:2181");
return new CloudSolrClient.Builder(zkServers, Optional.empty()).build();
final List<String> zkServers = new ArrayList<>();
zkServers.add("zookeeper1:2181");
zkServers.add("zookeeper2:2181");
zkServers.add("zookeeper3:2181");
return new CloudSolrClient.Builder(zkServers, Optional.of("/solr")).build();

さらに、solr-solrj-zookeeperアーティファクトに依存する必要があります。そうでない場合は、ClassNotFoundExceptionが発生します。

ZooKeeperベースの接続は、CloudSolrClientが動作するための最も信頼性が高く、パフォーマンスの高い手段です。一方、これは、Solrノード以外にZooKeeperをより広範囲に公開することを意味し、セキュリティリスクとなります。また、JARの依存関係も増えます。

タイムアウト

すべてのSolrClient実装では、Solrとの通信の接続と読み取りのタイムアウトをユーザーが指定できます。これらは、以下の例のように、クライアント作成時に提供されます。

final String solrUrl = "https://127.0.0.1:8983/solr";
return new HttpSolrClient.Builder(solrUrl)
    .withConnectionTimeout(10000, TimeUnit.MILLISECONDS)
    .withSocketTimeout(60000, TimeUnit.MILLISECONDS)
    .build();

これらの値が明示的に提供されない場合、SolrJは実行中のOS/環境のデフォルトを使用します。

ConcurrentUpdateSolrClientとその対応物であるConcurrentUpdateHttp2SolrClientは、応答しないノードへのリクエストを、ソケットタイムアウトを待つよりも早く失敗させることができるストール防止タイムアウトも実装しています。このタイムアウトのデフォルト値は15000msに設定されており、システムプロパティsolr.cloud.client.stallTimeで調整できます。この値はsolr.jetty.http.idleTimeout(デフォルトは120000ms)よりも小さく、最大更新リクエストの処理時間よりも大きくする必要があります。

クラウドリクエストルーティング

SolrJのCloudSolrClient実装(CloudSolrClientおよびCloudHttp2SolrClient)は、shards.preferenceパラメーターを尊重します。したがって、上記のいずれかのクライアントを使用して単一シャードのコレクションに送信されたリクエストは、分散リクエストが個々のシャードにルーティングされるのと同じ方法でリクエストをルーティングします。shards.preferenceパラメーターが提供されない場合、クライアントはレプリカをランダムにソートします。

更新リクエストの場合、レプリカはリクエストで定義された順序でソートされますが、リーダーレプリカは常に最初にソートされます。

SolrJでのクエリ

SolrClientには、Solrから結果を取得するための多くのquery()メソッドがあります。これらの各メソッドは、任意のクエリパラメーターをカプセル化するオブジェクトであるSolrParamsを受け取ります。そして、各メソッドは、結果ドキュメントおよびその他の関連メタデータにアクセスするために使用できるラッパーであるQueryResponseを出力します。

次のスニペットは、SolrClientを使用してSolrの「techproducts」サンプルコレクションをクエリし、結果を反復処理します。

final SolrClient client = getSolrClient();

final Map<String, String> queryParamMap = new HashMap<>();
queryParamMap.put("q", "*:*");
queryParamMap.put("fl", "id, name");
queryParamMap.put("sort", "id asc");
MapSolrParams queryParams = new MapSolrParams(queryParamMap);

final QueryResponse response = client.query("techproducts", queryParams);
final SolrDocumentList documents = response.getResults();

print("Found " + documents.getNumFound() + " documents");
for (SolrDocument document : documents) {
  final String id = (String) document.getFirstValue("id");
  final String name = (String) document.getFirstValue("name");

  print("id: " + id + "; name: " + name);
}

SolrParamsにはSolrQueryサブクラスがあり、クエリ作成を大幅に簡素化するいくつかの便利なメソッドを提供します。次のスニペットは、前の例からのクエリを、SolrQueryの便利なメソッドの一部を使用して構築する方法を示しています。

final SolrQuery query = new SolrQuery("*:*");
query.addField("id");
query.addField("name");
query.setSort("id", ORDER.asc);
query.setRows(numResultsToReturn);

SolrJでのインデックス作成

SolrJを使用すると、インデックス作成も簡単です。ユーザーは、インデックスを作成するドキュメントをSolrInputDocumentのインスタンスとして構築し、SolrClientadd()メソッドのいずれかの引数として提供します。

次の例は、SolrJを使用してSolrの「techproducts」サンプルコレクションにドキュメントを追加する方法を示しています。

final SolrClient client = getSolrClient();

final SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", UUID.randomUUID().toString());
doc.addField("name", "Amazon Kindle Paperwhite");

final UpdateResponse updateResponse = client.add("techproducts", doc);
// Indexed documents must be committed
client.commit("techproducts");
上記のインデックス作成の例は、構文を示すことを目的としています。簡潔にするために、いくつかのSolrインデックス作成のベストプラクティスに違反しています。通常は、一度に1つずつではなく、より大きなバッチでドキュメントをインデックス作成する必要があります。また、Solr管理者は、明示的なcommit()呼び出しを使用するのではなく、Solrの自動コミット設定を使用してドキュメントをコミットすることをお勧めします。

Javaオブジェクトバインディング

SolrJが提供するUpdateResponseおよびQueryResponseインターフェースは便利ですが、アプリケーションでより簡単に理解できるドメイン固有のオブジェクトを使用する方が便利なことがよくあります。ありがたいことに、SolrJは、Fieldアノテーションで特別にマークされたクラスとの間のドキュメントの暗黙的な変換をサポートしています。

Javaオブジェクトの各インスタンス変数は、Fieldアノテーションを使用して対応するSolrフィールドにマップできます。Solrフィールドはデフォルトでアノテーション付き変数と同じ名前を共有しますが、明示的なフィールド名をアノテーションに提供することで上書きできます。

以下の例のスニペットは、Solrの「techproducts」サンプルコレクションからの結果を表すために使用できるアノテーション付きTechProductクラスを示しています。

public static class TechProduct {
  @Field public String id;
  @Field public String name;

  public TechProduct(String id, String name) {
    this.id = id;
    this.name = name;
  }

  public TechProduct() {}
}

上記のアノテーション付きTechProductクラスにアクセスできるアプリケーションコードは、以下の例のスニペットのように、変換なしでTechProductオブジェクトを直接インデックス作成できます。

final SolrClient client = getSolrClient();

final TechProduct kindle = new TechProduct("kindle-id-4", "Amazon Kindle Paperwhite");
final UpdateResponse response = client.addBean("techproducts", kindle);

client.commit("techproducts");

同様に、QueryResponsegetBeans()メソッドを使用して、検索結果をBeanオブジェクトに直接変換できます。

final SolrClient client = getSolrClient();

final SolrQuery query = new SolrQuery("*:*");
query.addField("id");
query.addField("name");
query.setSort("id", ORDER.asc);

final QueryResponse response = client.query("techproducts", query);
final List<TechProduct> products = response.getBeans(TechProduct.class);

その他のAPI

SolrJは、クエリとインデックス作成だけではありません。SolrのすべてのAPIをサポートしています。Solrの他のAPIにアクセスするには、適切なリクエストオブジェクトを見つけ、必要なパラメーターを提供し、SolrClientrequest()メソッドに渡すだけです。request()NamedListを返します。これは、リクエストによって返されたJSONまたはXMLの階層構造を反映する汎用オブジェクトです。

以下の例は、SolrJユーザーがSolrCloudデプロイメントのCLUSTERSTATUS APIを呼び出し、返されたNamedListを操作する方法を示しています。

final SolrClient client = getSolrClient();

@SuppressWarnings({"rawtypes"})
final SolrRequest request = new CollectionAdminRequest.ClusterStatus();

final NamedList<Object> response = client.request(request);
@SuppressWarnings({"unchecked"})
final NamedList<Object> cluster = (NamedList<Object>) response.get("cluster");
@SuppressWarnings({"unchecked"})
final List<String> liveNodes = (List<String>) cluster.get("live_nodes");

print("Found " + liveNodes.size() + " live nodes");