SolrJによるドキュメントの部分更新(アトミック操作)

1. 要件分析

(1) 要件:

Solr内のドキュメントに新しいフィールドを追加したり、既存のフィールドを変更したりするが、変更しないフィールドは元の値のままにしておきたい。つまり、完全な上書き操作は行わない。

(2) 前提条件:

追加するフィールドは、事前にschema.xmlファイルで定義されている必要がある。そうでないと、Solrはそのフィールドを処理できず、追加に失敗する。

schema.xmlファイルの設定については、以下を参照: Solrのschema.xmlスキーマファイルの解説(Solrのスキーマ設計と最適化)

(3) 分析: Solrが提供するアトミック更新機能を利用することで、この要件を実現できます。

Solrがサポートするアトミック更新:

  • set: 指定されたドキュメントのフィールドの値を変更します。このフィールドが既に存在する場合は更新され、存在しない場合はドキュメントに追加されます。単一値でも複数値でも可能です。
  • add: 指定されたドキュメントのフィールドに値を追加します。このフィールドは複数値(multi-valued)タイプでなければなりません。そうでない場合、エラーが発生します。複数値のみ対応。
  • inc: 指定されたドキュメントの数値型の値をインクリメント(増加)します。数値型(int、long、float、doubleなど)のみ対応。

2. 実装方法

2.1. pom.xmlの依存関係

(プロジェクトが古いため、4.10.4バージョンのSolrを使用しています)

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

2.2. Javaコードの例

(1) まずSolrへの接続を確立します:

String zkHost = "ip:port,ip:port,ip:port";
// 同時接続数を増やす
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(HttpClientUtil.PROP_MAX_CONNECTIONS, 1000);
params.set(HttpClientUtil.PROP_MAX_CONNECTIONS_PER_HOST, 100);
HttpClient client = HttpClientUtil.createClient(params);
LBHttpSolrServer lbServer = new LBHttpSolrServer(client);

CloudSolrServer solrServer = new CloudSolrServer(zkHost, lbServer);
// Solr接続用のデフォルトコレクションを設定
solrServer.setDefaultCollection("C_Book");

// ZooKeeperの接続タイムアウト時間を設定
solrServer.setZkClientTimeout(18000);
solrServer.setZkConnectTimeout(36000);

(2) 処理対象のSolrドキュメントを準備します。コード内のコメントで詳細な注意点を説明しています:

// 効率化のためにバッチ操作を使用
Collection<SolrInputDocument> documentsToUpdate = new ArrayList<>();

// 更新するドキュメントのIDリスト(例)
List<String> documentIds = Arrays.asList("1", "2", "3", "4", "5");

for (String docId : documentIds) {
    SolrInputDocument solrDoc = new SolrInputDocument();
    // 部分更新では、schema.xmlで定義された主キー(id)を指定する必要があります。
    // 主キーにはset、addなどの情報を追加する必要はありません。他のアトミック更新が必要なフィールドはMapとして構築する必要があります。
    solrDoc.addField("id", docId);
    
    // 出版社を「人民邮电出版社」に設定するためのMap。部分更新にはMapが必要で、キーは「set」である必要があります。
    Map<String, String> publisherUpdateMap = new HashMap<>();
    publisherUpdateMap.put("set", "人民邮电出版社");
    // 図書の出版社を更新します。キーはフィールド名、値は上記のMapです。
    solrDoc.addField("publisher", publisherUpdateMap);
 
    // 既存の在庫に複数の都市を追加します。注意:このフィールドは複数値タイプでなければなりません。
    Map<String, List<String>> stockCityUpdateMap = new HashMap<>();
    List<String> citiesToAdd = new ArrayList<>();
    citiesToAdd.add("广州");
    citiesToAdd.add("深圳");
    // 部分追加にはMapが必要で、キーは「add」である必要があります。
    stockCityUpdateMap.put("add", citiesToAdd);
    // 図書の在庫都市を更新します。キーはフィールド名、値は上記のMapです。
    solrDoc.addField("stockCity", stockCityUpdateMap);
    
    // 既存の図書価格に基づき:各冊を9.50元増加させます。注意:このフィールドは数値型でなければなりません。
    Map<String, Long> priceUpdateMap = new HashMap<>();
    // 部分インクリメントにはMapが必要で、キーは「inc」である必要があります。
    priceUpdateMap.put("inc", 950L); // 9.50元をlong型で表現
    // 図書の価格を更新します。キーはフィールド名、値は上記のMapです。
    solrDoc.addField("price", priceUpdateMap);
    
    // _version_の値が0の場合:更新対象のドキュメントが存在すれば更新し、存在しなければ追加します。
    solrDoc.addField("_version_", 0);
 
    documentsToUpdate.add(solrDoc);
}

(3) SolrCloudにバッチ追加リクエストを送信します:

// SolrCloudに接続
solrServer.connect();
// 更新するドキュメントのリストを追加
UpdateResponse rsp = solrServer.add(documentsToUpdate);

System.out.println("操作ステータス: " + rsp.getStatus() + ", 操作時間:" + rsp.getQTime());

// コミット戦略:手動でコミットする必要はなく、Solrサービスが設定に基づいて自動的にソフトコミットを実行します。
// 手動でコミットする場合、引数なしのメソッドは使用せず、コミット戦略を指定することを推奨します:リフレッシュを待つか(ブロックする可能性があるため非推奨)、検索可能になるのを待つか(ブロックする可能性があるため非推奨)、ソフトコミット
UpdateResponse rspCommit = solrServer.commit(false, false, true);
System.out.println("コミットステータス: " + " result:" + rspCommit.getStatus() + ", 操作時間: " + rspCommit.getQTime());

3. 補足説明

3.1. `_version_`フィールドの値について

(1) `version < 0`: 更新対象のドキュメントが存在する場合、Solrは更新を拒否します。存在しない場合は、そのドキュメントを追加します。

(2) `version = 0`: 更新対象のドキュメントが存在する場合、そのドキュメントを更新します。存在しない場合は、そのドキュメントを追加します。

(3) `version = 1`: 更新対象のドキュメントが存在する場合、そのドキュメントを更新します。存在しない場合は、Solrはそれを拒否し、以下のようなエラーメッセージをスローします:

version conflict for 1 expected=1 actual=-1

(4) `version > 1`: ドキュメントの`_version_`値と渡された`_version_`値が異なる場合、Solrは更新を拒否します。値が同じ場合のみ、更新が実行されます。

3.2. `store=true/false`の違い

(1) あるフィールドがschema.xmlで`store=false`と指定されている場合、そのフィールドに値があっても、更新時にSolrによって破棄されます。`store=true`と指定されたフィールドは破棄されません。

(2) 複数フィールド(multi-field)の場合、`store=false`と指定されていると、アトミック更新で`add`を使用した際に、そのフィールドの以前のデータが連鎖的にクリアされます。

タグ: Solr SolrJ 部分更新 アトミック操作

5月17日 17:04 投稿