カテゴリー: テクノロジー

[Solr] Cross Collection Join と Range Hash Query

Solr リファレンスの Cross Collection Join の項に以下の説明があります。

If the local index is sharded according to the join key field, the cross collection join can leverage a secondary query parser called the “hash_range” query parser. The hash_range query parser is responsible for returning only the documents that hash to a given range of values. This allows the CrossCollectionQuery to query the remote Solr collection and return only the join keys that would match a specific shard in the local Solr collection. This has the benefit of making sure that network traffic doesn’t increase as the number of shards increases and allows for much greater scalability.

https://solr.apache.org/guide/8_9/other-parsers.html#cross-collection-join

意訳

ローカルインデックスが join key フィールドでシャードに分けられている場合、Cross Collection Join はHash Range クエリパーザという補助的なクエリパーザを利用できます。Hash Range クエリパーザは、そのハッシュが指定された範囲に収まるドキュメントだけを返す役目を持っています。これにより、CrossCollectionQuery はリモートの Solr コレクションにクエリを投げたときにローカル側のコレクションの特定のシャードにだけマッチする join key を受け取ることができます。この機能にはシャード数が増加してもネットワークトラフィックが増えないという利点があり、非常に優れたスケーラビリティを実現します。

この動作を確認してみます。前回同様

ローカルインデックス: http://localhost:8983/solr/collection1
リモートインデックス: http://localhost:7574/solr/collection2

です。それぞれ以下のようなデータが入っています。

$ curl -s 'http://localhost:8983/solr/collection1/select?q.op=AND&q=*%3A*&rows=5'
{
  "responseHeader":{
    "zkConnected":true,
    "status":0,
    "QTime":4,
    "params":{
      "q":"*:*",
      "q.op":"AND",
      "rows":"5"}},
  "response":{"numFound":9238,"start":0,"maxScore":1.0,"numFoundExact":true,"docs":[
      {
        "id":"名所・旧跡!4548",
        "type_str":"名所・旧跡",
        "name_str":"高砂神社",
        "_version_":1706788558129332224},
      {
        "id":"名所・旧跡!4549",
        "type_str":"名所・旧跡",
        "name_str":"高崎神社",
        "_version_":1706788558131429376},
      {
        "id":"名所・旧跡!4550",
        "type_str":"名所・旧跡",
        "name_str":"大阪護国神社",
        "_version_":1706788558131429377},
      {
        "id":"名所・旧跡!4551",
        "type_str":"名所・旧跡",
        "name_str":"極楽寺",
        "_version_":1706788558131429378},
      {
        "id":"名所・旧跡!4552",
        "type_str":"名所・旧跡",
        "name_str":"あびこ観音",
        "_version_":1706788558131429379}]
  }}
$ curl -s 'http://localhost:7574/solr/collection2/select?q.op=AND&q=*%3A*&rows=5'
{
  "responseHeader":{
    "zkConnected":true,
    "status":0,
    "QTime":5,
    "params":{
      "q":"*:*",
      "q.op":"AND",
      "rows":"5"}},
  "response":{"numFound":9238,"start":0,"maxScore":1.0,"numFoundExact":true,"docs":[
      {
        "id":"名所・旧跡!4548",
        "area_str":"住之江区",
        "address_str":"住之江区北島3-14-12",
        "address_p":"34.6003421111111,135.477833138888",
        "_version_":1706808733657464832},
      {
        "id":"名所・旧跡!4549",
        "area_str":"住之江区",
        "address_str":"住之江区南加賀屋4-15-3",
        "address_p":"34.6014560555555,135.474182833333",
        "_version_":1706808733658513408},
      {
        "id":"名所・旧跡!4550",
        "area_str":"住之江区",
        "address_str":"住之江区南加賀屋1-1-77",
        "address_p":"34.6102029722222,135.473905944444",
        "_version_":1706808733659561984},
      {
        "id":"名所・旧跡!4551",
        "area_str":"住吉区",
        "address_str":"住吉区遠里小野5丁目",
        "address_p":"34.6003890555555,135.495708055555",
        "_version_":1706808733659561985},
      {
        "id":"名所・旧跡!4552",
        "area_str":"住吉区",
        "address_str":"住吉区我孫子4丁目",
        "address_p":"34.5989438333333,135.508442333333",
        "_version_":1706808733659561986}]
  }}

idフィールドが CompositId になっており、type_str の値でシャーディングされます。
シャードが4つの場合、以下のような配置となっています。

$ curl -s 'http://localhost:8983/solr/collection1/select?facet.field=type_str&facet=on&q=*:*&rows=0&shards=shard1'|jq .facet_counts.facet_fields.type_str
[
  "名所・旧跡",
  561,
  "環境・リサイクル",
  53
]
$ curl -s 'http://localhost:8983/solr/collection1/select?facet.field=type_str&facet=on&q=*:*&rows=0&shards=shard2'|jq .facet_counts.facet_fields.type_str
[
  "駐車場・駐輪場",
  1049,
  "学校・保育所",
  1045,
  "医療・福祉",
  840,
  "会館・ホール",
  642,
  "官公庁",
  301,
  "その他",
  141
]
$ curl -s 'http://localhost:8983/solr/collection1/select?facet.field=type_str&facet=on&q=*:*&rows=0&shards=shard3'|jq .facet_counts.facet_fields.type_str
[
  "駅・バス停",
  2574,
  "警察・消防",
  330,
  "文化・観光",
  290
]
$ curl -s 'http://localhost:8983/solr/collection1/select?facet.field=type_str&facet=on&q=*:*&rows=0&shards=shard4'|jq .facet_counts.facet_fields.type_str
[
  "公園・スポーツ",
  1090,
  "公衆トイレ",
  322
]

それぞれのシャードのハッシュの値の範囲は以下の通りです。

$ curl -s 'http://localhost:8983/solr/admin/collections?action=COLSTATUS&collection=collection1&coreInfo=true' |jq '.collection1.shards | {(keys[0]):[.[].range][0],(keys[1]):[.[].range][1],(keys[2]):[.[].range][2],(keys[3]):[.[].range][3]}'
{
  "shard1": "80000000-bfffffff",
  "shard2": "c0000000-ffffffff",
  "shard3": "0-3fffffff",
  "shard4": "40000000-7fffffff"
}

ここで type_str:文化・観光 で area_type:中央区 のデータを Cross Collection Join で検索してみます。

type_str:文化・観光 {!join ttl=1 routed=\"true\" method=\"crossCollection\" fromIndex=\"collection2\" from=\"id\" to=\"id\" solrUrl=\"http://localhost:7574/solr\" v=\"area_str:中央区\"}

routed=true を指定して、Range Hash クエリを有効にしています。
この検索におけるリモートインデックス側(7574のSolr)のログを確認すると、以下のような検索クエリが来ていることが分かります。

2021-07-31 16:52:56.699 INFO  (qtp1620529408-898) [c:collection2 s:shard1 r:core_node3 x:collection2_shard1_replica_n1] o.a.s.c.S.Request [collection2_shard1_replica_n1]  webapp=/solr path=/stream params={indent=off&expr=unique(search(collection2,q%3D"area_str:中央区",fq%3D"{!hash_range+f%3Did+l%3D0+u%3D1073741823}",fl%3Did,sort%3D"id+asc",qt%3D"/export",method%3DcrossCollection),over%3Did)&wt=javabin&version=2.2} status=0 QTime=0

Hash Range を指定しているのは以下の部分です。

{!hash_range f:id l:0 u:1073741823}

idフィールドのハッシュ値の下限が0,上限が1073741823と指定されています。1073741823を16進数に直すと3FFFFFFFです。type_str:文化・観光 が属する shard3 にマッチするものだけが要求されていることが分かります。


iOSでアプリを固定する方法

設定自体は割と簡単で下記の手順で行えます。

アクセシビリティ

手順1.アクセスガイドの設定を有効にする
設定>アクセシビリティ>アクセスガイドを有効にしてパスコードを設定する

手順2.アクセスガイドを起動する
固定したいアプリを起動してアクセスガイドの設定の下に書いてあった「ホームボタン」か「トップボタン」を3回押して何も選択せず開始

アプリの固定は上記の手順2で完了です。解除する場合は同じように3回押してパスコードを入力後終了すれば戻ります。

利用シーンとしては業務用アプリとして他のアプリを起動しないようにして貸し出す時やお子さんなどに対象のアプリ以外起動して欲しくない時などに使える機能かと思います。

元々は電子書籍をiPadで読んでる際に下のバーが気になって気になってしょうがなかったのですがアプリの機能として提供されているものかと思っていたらiOSの標準機能としてあると知り、それをどうにか消せないかと調べた結果知った機能です。起動手順や解除手順が少し面倒なのでホームボタン自体を消す設定を追加して欲しいところですが消す方法がないよりはマシではあります。

電子書籍アプリについては前まではKindle一択でしたがhontoのアプリを知ってからは漫画、小説はhonto一択です。

  • 漫画、小説がKindleと違ってちゃんとシリーズ化される(Kindleは漫画は一部おかしかったり小説はシリーズ化されない)
  • 新刊が出たらシリーズ中に新作表示やお知らせに続刊の表示がされる
  • マイリストには1巻登録すればシリーズ全てが入るのでお気に入りリストが作りやすい(Kindleは選択した巻だけで見づらい)
  • 起動した時はKindleと違ってお知らせではなく書籍一覧のライブラリの画面が表示される
  • 読み終わった際に次の巻があればそのまま続きで読める(Kindleは自分で次の巻を探す必要がある)
  • 再度読み直す際にhontoは最初から、続きからで選べる(Kindleは続きからのみで最初から読み直す際は手動で戻す必要がある)

数ヶ月使ってみて400冊ほど購入してKindleと比べた結果ですがたまにアプリが落ちる時があるのを除いて圧倒的にhontoが一押しです。


[Solr] Cross Collection Join

はじめに

Solr 8.6 以降ではコレクションをまたがった Join クエリを実行することができます。

Cross Collection Join

メインの検索対象を collection1、Join 対象を collection2 とします。Cross Collection Join は、collection2 に対する検索条件にヒットするデータの join key を持つ collection1 側のデータを取得する一種のフィルタクエリとして機能します。注意が必要なのは collection2 側のデータのフィールドは取得できないということです。collection2 は別のサーバで動く Solr 上にあっても構いません。

動作確認

Cross Collection Join の動作を確認するため、2つの Solr プロセスを起動します。

bin/solr start -cloud -p 8983
bin/solr start -cloud -p 7574

8983 の方は solrconfig.xml に以下を記載します。

<queryParser name="join" class="org.apache.solr.search.JoinQParserPlugin">
    <arr name="allowSolrUrls">
      <str>http://localhost:7574/solr</str>
    </arr>
</queryParser>

8983 の collection1 には以下のようなデータが入っています。

$ curl -s 'http://localhost:8983/solr/collection1/select?q.op=OR&q=*:*&rows=5'
{
  "responseHeader":{
    "zkConnected":true,
    "status":0,
    "QTime":0,
    "params":{
      "q":"*:*",
      "q.op":"OR",
      "rows":"5",
      "_":"1625974694981"}},
  "response":{"numFound":9238,"start":0,"numFoundExact":true,"docs":[
      {
        "id":"10",
        "type_str":"官公庁",
        "name_str":"軽自動車検査協会大阪主管事務所",
        "_version_":1704977365000519680},
      {
        "id":"11",
        "type_str":"官公庁",
        "name_str":"大阪陸運支局なにわ自動車検査登録事務所",
        "_version_":1704977365035122688},
      {
        "id":"12",
        "type_str":"官公庁",
        "name_str":"住吉税務署",
        "_version_":1704977365036171264},
      {
        "id":"13",
        "type_str":"官公庁",
        "name_str":"玉出年金事務所",
        "_version_":1704977365036171265},
      {
        "id":"14",
        "type_str":"官公庁",
        "name_str":"大阪南労働基準監督署",
        "_version_":1704977365037219840}]
  }}

7574 の collection2 には以下のようなデータが入っています。

$ curl -s 'http://localhost:7574/solr/collection2/select?q.op=OR&q=*:*&rows=5'
{
  "responseHeader":{
    "zkConnected":true,
    "status":0,
    "QTime":0,
    "params":{
      "q":"*:*",
      "q.op":"OR",
      "rows":"5",
      "_":"1625993148833"}},
  "response":{"numFound":9238,"start":0,"numFoundExact":true,"docs":[
      {
        "id":"10",
        "area_str":"住之江区",
        "address_str":"住之江区南港東3-4-62",
        "address_p":"34.6164938333333,135.438210722222",
        "_version_":1704977387371888640},
      {
        "id":"11",
        "area_str":"住之江区",
        "address_str":"住之江区南港東3-1-14",
        "address_p":"34.6190439722222,135.442191833333",
        "_version_":1704977387419074560},
      {
        "id":"12",
        "area_str":"住吉区",
        "address_str":"住吉区住吉2丁目17番37号",
        "address_p":"34.6109641111111,135.491388722222",
        "_version_":1704977387420123136},
      {
        "id":"13",
        "area_str":"住之江区",
        "address_str":"住之江区北加賀屋2-3-6",
        "address_p":"34.6231918888888,135.477992138888",
        "_version_":1704977387420123137},
      {
        "id":"14",
        "area_str":"西成区",
        "address_str":"西成区玉出中2丁目13番27号",
        "address_p":"34.6237640555555,135.4924",
        "_version_":1704977387421171712}]
  }}

中央区にある「文化・観光」の施設を検索してみます。施設タイプは collection1、エリア情報は collection2 にあるので Cross Collection Join の出番です。

type_str:文化・観光 {!join method="crossCollection" fromIndex="collection2" from="id" to="id" solrUrl="http://localhost:7574/solr" v="area_str:中央区"}

solrUrl で指定した Solr のコレクション collection2 で area_str:中央区 を検索して collection1 で type_str:文化・観光 を検索した結果と JOIN する、join key はそれぞれの id フィールド、というクエリです。

$ curl -s 'http://localhost:8983/solr/collection1/select?q=type_str%3A%E6%96%87%E5%8C%96%E3%83%BB%E8%A6%B3%E5%85%89%20%7B!join%20method%3D%22crossCollection%22%20fromIndex%3D%22collection2%22%20from%3D%22id%22%20to%3D%22id%22%20solrUrl%3D%22http%3A%2F%2Flocalhost%3A7574%2Fsolr%22%20v%3D%22area_str%3A%E4%B8%AD%E5%A4%AE%E5%8C%BA%22%7D'
{
  "responseHeader":{
    "zkConnected":true,
    "status":0,
    "QTime":0,
    "params":{
      "q":"type_str:文化・観光 {!join method=\"crossCollection\" fromIndex=\"collection2\" from=\"id\" to=\"id\" solrUrl=\"http://localhost:7574/solr\" v=\"area_str:中央区\"}",
      "q.op":"AND",
      "debugQuery":"false",
      "_":"1625975185887"}},
  "response":{"numFound":56,"start":0,"numFoundExact":true,"docs":[
      {
        "id":"3101",
        "type_str":"文化・観光",
        "name_str":"大阪市立島之内図書館",
        "_version_":1704977365352841232},
      {
        "id":"3136",
        "type_str":"文化・観光",
        "name_str":"上方浮世絵館",
        "_version_":1704977365354938370},
      {
        "id":"3137",
        "type_str":"文化・観光",
        "name_str":"府立上方演芸資料館(ワッハ上方)",
        "_version_":1704977365354938371},
      {
        "id":"3138",
        "type_str":"文化・観光",
        "name_str":"国立文楽劇場",
        "_version_":1704977365354938372},
      {
        "id":"3140",
        "type_str":"文化・観光",
        "name_str":"湯木美術館",
        "_version_":1704977365354938374},
      {
        "id":"3143",
        "type_str":"文化・観光",
        "name_str":"適塾",
        "_version_":1704977365354938377},
      {
        "id":"3144",
        "type_str":"文化・観光",
        "name_str":"府立現代美術センター",
        "_version_":1704977365354938378},
      {
        "id":"3145",
        "type_str":"文化・観光",
        "name_str":"大阪歴史博物館",
        "_version_":1704977365354938379},
      {
        "id":"3146",
        "type_str":"文化・観光",
        "name_str":"ピースおおさか:大阪国際平和センター",
        "_version_":1704977365354938380},
      {
        "id":"3147",
        "type_str":"文化・観光",
        "name_str":"大阪城天守閣",
        "_version_":1704977365354938381}]
  }}

[Solr]コレクションのインクリメンタルバックアップ

はじめに

Solr 8.9から SolrCloud におけるコレクションのバックアップの形式が変更されてインクリメンタルバックアップ対応となりました。

バックアップ

インクリメンタルバックアップ対応に伴い、コレクションAPIのBACKUPアクションのパラメータに以下の2つが追加されました。

incremental

true (デフォルト)ならインクリメンタルバックアップ、false なら従来と同様のスナップショットによるバックアップとなります。スナップショットによるバックアップは近いうちに廃止となる予定です。

maxNumBackupPoints

インクリメンタルバックアップではバックアップポイントという形で直前のバックアップとの差分が保存されていきます。
最大で何個をバックアップポイントを保存するのかを maxNumBackupPoints で指定します。

サンプル

サンプルとして大阪の施設情報を利用します。

以下のようなデータを1件ずつ追加して、その都度バックアップします。

os_1.json
[{"id":"10","type":"官公庁","area":"住之江区","name":"軽自動車検査協会大阪主管事務所","address":"住之江区南港東3-4-62","address_p":"34.6164938333333,135.438210722222"}]

os_2.json
[{"id":"11","type":"官公庁","area":"住之江区","name":"大阪陸運支局なにわ自動車検査登録事務所","address":"住之江区南港東3-1-14","address_p":"34.6190439722222,135.442191833333"}]

os_3.json
[{"id":"12","type":"官公庁","area":"住吉区","name":"住吉税務署","address":"住吉区住吉2丁目17番37号","address_p":"34.6109641111111,135.491388722222"}]

os_4.json
[{"id":"13","type":"官公庁","area":"住之江区","name":"玉出年金事務所","address":"住之江区北加賀屋2-3-6","address_p":"34.6231918888888,135.477992138888"}]
$ curl 'http://localhost:8983/solr/backup_test/update?commit=true&indent=true' --data-binary @os_1.json -H 'Content-Type: application/json'

$ curl 'http://localhost:8983/solr/admin/collections?omitHeader=true&action=BACKUP&name=backup1&collection=backup_test&location=/tmp/solr_backup&incremental=true&maxNumBackupPoints=3'
{
  "success":{
    "127.0.1.1:8983_solr":{
      "responseHeader":{
        "status":0,
        "QTime":2},
      "response":[
        "startTime","2021-06-28T14:00:11.906389Z",
        "indexFileCount",18,
        "uploadedIndexFileCount",18,
        "indexSizeMB",0.005,
        "uploadedIndexFileMB",0.005,
        "shard","shard1",
        "endTime","2021-06-28T14:00:11.908343Z",
        "shardBackupId","md_shard1_1"]}},
  "response":[
    "collection","backup_test",
    "numShards",1,
    "backupId",1,
    "indexVersion","8.9.0",
    "startTime","2021-06-28T14:00:11.903010Z",
    "indexSizeMB",0.005]}

$ curl 'http://localhost:8983/solr/backup_test/update?commit=true&indent=true' --data-binary @os_2.json -H 'Content-Type: application/json'

$ curl 'http://localhost:8983/solr/admin/collections?omitHeader=true&action=BACKUP&name=backup1&collection=backup_test&location=/tmp/solr_backup&incremental=true&maxNumBackupPoints=3'
{
  "success":{
    "127.0.1.1:8983_solr":{
      "responseHeader":{
        "status":0,
        "QTime":2},
      "response":[
        "startTime","2021-06-28T14:00:36.142084Z",
        "indexFileCount",35,
        "uploadedIndexFileCount",18,
        "indexSizeMB",0.01,
        "uploadedIndexFileMB",0.005,
        "shard","shard1",
        "endTime","2021-06-28T14:00:36.144073Z",
        "shardBackupId","md_shard1_2"]}},
  "response":[
    "collection","backup_test",
    "numShards",1,
    "backupId",2,
    "indexVersion","8.9.0",
    "startTime","2021-06-28T14:00:36.137745Z",
    "indexSizeMB",0.01]}

$ curl 'http://localhost:8983/solr/backup_test/update?commit=true&indent=true' --data-binary @os_3.json -H 'Content-Type: application/json'

$ curl 'http://localhost:8983/solr/admin/collections?omitHeader=true&action=BACKUP&name=backup1&collection=backup_test&location=/tmp/solr_backup&incremental=true&maxNumBackupPoints=3'
{
  "success":{
    "127.0.1.1:8983_solr":{
      "responseHeader":{
        "status":0,
        "QTime":4},
      "response":[
        "startTime","2021-06-28T14:01:38.399993Z",
        "indexFileCount",52,
        "uploadedIndexFileCount",18,
        "indexSizeMB",0.015,
        "uploadedIndexFileMB",0.005,
        "shard","shard1",
        "endTime","2021-06-28T14:01:38.404263Z",
        "shardBackupId","md_shard1_3"]}},
  "response":[
    "collection","backup_test",
    "numShards",1,
    "backupId",3,
    "indexVersion","8.9.0",
    "startTime","2021-06-28T14:01:38.398621Z",
    "indexSizeMB",0.015],
  "deleted":[[
      "startTime","2021-06-28T13:56:09.437719Z",
      "backupId",0,
      "size",5223,
      "numFiles",18]],
  "collection":"backup_test"}

$ curl 'http://localhost:8983/solr/backup_test/update?commit=true&indent=true' --data-binary @os_4.json -H 'Content-Type: application/json'

$ curl 'http://localhost:8983/solr/admin/collections?omitHeader=true&action=BACKUP&name=backup1&collection=backup_test&location=/tmp/solr_backup&incremental=true&maxNumBackupPoints=3'
{
  "success":{
    "127.0.1.1:8983_solr":{
      "responseHeader":{
        "status":0,
        "QTime":3},
      "response":[
        "startTime","2021-06-28T14:02:05.875017Z",
        "indexFileCount",69,
        "uploadedIndexFileCount",18,
        "indexSizeMB",0.019,
        "uploadedIndexFileMB",0.005,
        "shard","shard1",
        "endTime","2021-06-28T14:02:05.878413Z",
        "shardBackupId","md_shard1_4"]}},
  "response":[
    "collection","backup_test",
    "numShards",1,
    "backupId",4,
    "indexVersion","8.9.0",
    "startTime","2021-06-28T14:02:05.873449Z",
    "indexSizeMB",0.019],
  "deleted":[[
      "startTime","2021-06-28T14:00:11.903010Z",
      "backupId",1,
      "size",5223,
      "numFiles",18]],
  "collection":"backup_test"}

各バックアップの応答に backupId が記載されています。リストアの際に backupId を指定することで特定のバックアップポイントに戻すことができます。

4回目のバックアップの応答に deleted という項目があります。maxNumBackupPoints=3 を指定しているので、4回目のバックアップでは最初のバックアップが削除されてバックアップポイントが3個に保たれていることが分かります。

ここでインデックスの状態を確認します。ドキュメントを1件ずつ4回投入したので、全件検索すると4件ヒットします。

$ curl -s 'http://localhost:8983/solr/backup_test/select?omitHeader=true&q.op=OR&q=*%3A*&rows=0'
{
  "response":{"numFound":4,"start":0,"numFoundExact":true,"docs":[]
  }}

リストア

LISTBACKUP アクションで現在利用可能なバックアップポイントを確認できます。

$ curl 'http://localhost:8983/solr/admin/collections?omitHeader=true&action=LISTBACKUP&name=backup1&location=/tmp/solr_backup'
{
  "collection":"backup_test",
  "backups":[{
      "indexFileCount":0,
      "indexSizeMB":0.0,
      "shardBackupIds":{"shard1":"md_shard1_2.json"},
      "collection.configName":"backup_test",
      "backupId":2,
      "collectionAlias":"backup_test",
      "startTime":"2021-06-28T14:00:36.137745Z",
      "indexVersion":"8.9.0"},
    {
      "indexFileCount":0,
      "indexSizeMB":0.0,
      "shardBackupIds":{"shard1":"md_shard1_3.json"},
      "collection.configName":"backup_test",
      "backupId":3,
      "collectionAlias":"backup_test",
      "startTime":"2021-06-28T14:01:38.398621Z",
      "indexVersion":"8.9.0"},
    {
      "indexFileCount":0,
      "indexSizeMB":0.0,
      "shardBackupIds":{"shard1":"md_shard1_4.json"},
      "collection.configName":"backup_test",
      "backupId":4,
      "collectionAlias":"backup_test",
      "startTime":"2021-06-28T14:02:05.873449Z",
      "indexVersion":"8.9.0"}]}

ドキュメント2件のときのバックアップ(backupId=2)に戻します。

$ curl 'http://localhost:8983/solr/admin/collections?action=RESTORE&name=backup1&collection=backup_test&location=/tmp/solr_backup&backupId=2'
{
  "responseHeader":{
    "status":0,
    "QTime":599}}
tfukui@deskmini:~/dev/splout_blog/sample_data$ curl -s 'http://localhost:8983/solr/backup_test/select?omitHeader=true&q.op=OR&q=*%3A*&rows=0'
{
  "response":{"numFound":2,"start":0,"numFoundExact":true,"docs":[]
  }}

ドキュメント3件のときのバックアップ(backupId=3)に戻します。

$ curl 'http://localhost:8983/solr/admin/collections?action=RESTORE&name=backup1&collection=backup_test&location=/tmp/solr_backup&backupId=3'
{
  "responseHeader":{
    "status":0,
    "QTime":624}}
tfukui@deskmini:~/dev/splout_blog/sample_data$ curl -s 'http://localhost:8983/solr/backup_test/select?omitHeader=true&q.op=OR&q=*%3A*&rows=0'
{
  "response":{"numFound":3,"start":0,"numFoundExact":true,"docs":[]
  }}

既存のコレクションに対するリストアができるようになったことと併せて、コレクションのバックアップ機能がより使いやすくなりました。


[Solr]既存のコレクションに対してバックアップをリストアできるようになりました

はじめに

Solr 8.8 までは、SolrCloud で取得したバックアップをリストアするときには新しいコレクションを指定しなければならないという制限がありました。したがって、クライアントに公開するコレクション名をエイリアスで運用して、バックアップのリストア後にエイリアスを切り替えるという工夫が必要でした。

Solr 8.9 からは既存のコレクションに対してリストアできるようになりました。

バックアップ

curl 'http://localhost:8983/solr/admin/collections?action=BACKUP&name=backup1&collection=backup_test&location=/tmp/solr_backup'

backup_test というコレクションのバックアップを /tmp/solr_backup 以下に作成します。リストア時に参照するときの名前は backup1 です。

リストア(8.8 までの場合)

既存のコレクション名 backup_test を指定するとパラメータエラーとなります。

$ curl 'http://localhost:8983/solr/admin/collections?action=RESTORE&name=backup1&collection=backup_test&location=/tmp/solr_backup'
{
  "responseHeader":{
    "status":400,
    "QTime":0},
  "error":{
    "metadata":[
      "error-class","org.apache.solr.common.SolrException",
      "root-error-class","org.apache.solr.common.SolrException"],
    "msg":"Collection 'backup_test' exists, no action taken.",
    "code":400}}

新しいコレクション名 new_backup_test を指定すると、そのコレクションが作られてからリストアされます。

$ curl 'http://localhost:8983/solr/admin/collections?action=RESTORE&name=backup1&collection=new_backup_test&location=/tmp/solr_backup'
{
  "responseHeader":{
    "status":0,
    "QTime":612},
  "success":{
    "127.0.1.1:8983_solr":{
      "responseHeader":{
        "status":0,
        "QTime":238},
      "core":"new_backup_test_shard1_replica_n1"}}}

リストア(8.9の場合)

既存のコレクション名 backup_test を指定してリストアできます。

試しに、バックアップ後にインデックスの内容を全部消去します。

$ curl -s 'http://localhost:8983/solr/backup_test/select?omitHeader=true&q.op=OR&q=*%3A*&rows=0'
{
  "response":{"numFound":9238,"start":0,"numFoundExact":true,"docs":[]
  }}

$ curl -s 'http://localhost:8983/solr/backup_test/update?stream.body=*:*&wt=json&commit=true'
{
  "responseHeader":{
    "rf":1,
    "status":0,
    "QTime":2}}

$ curl -s 'http://localhost:8983/solr/backup_test/select?omitHeader=true&q.op=OR&q=*%3A*&rows=0'
{
  "response":{"numFound":0,"start":0,"numFoundExact":true,"docs":[]
  }}

その後、既存のコレクション backup_test を対象にしてリストアします。8.8までとは違ってエラーになりません。

$ curl 'http://localhost:8983/solr/admin/collections?action=RESTORE&name=backup1&collection=backup_test&location=/tmp/solr_backup'
{
  "responseHeader":{
    "status":0,
    "QTime":977}}

全件検索したときの件数が元に戻りました。

$ curl -s 'http://localhost:8983/solr/backup_test/select?omitHeader=true&q.op=OR&q=*%3A*&rows=0'
{
  "response":{"numFound":9238,"start":0,"numFoundExact":true,"docs":[]
  }}

既存のコレクションに対するリストアの仕組み

SOLR-1587 によると、使用中である可能性もある既存のコレクションに対するリストアを実現するために、Solr 8.1 で追加された Read-Only モードが使われているそうです。

  1. 指定されたコレクションを Read-Only モードにセットする
  2. コレクションの各シャードにバックアップをリストアする
  3. コレクションの Read-Only モードを解除する