Elasticsearchのスナップショットについて復習
この記事は Elastic Stack (Elasticsearch) Advent Calendar22日目の記事です。
今回はElasticsearchのスナップショットについて復習していきます。
背景と目的
ある日仕事で「Searchable Snapshotなにもわからない。」となりまして、「いや、そもそもSearchable Snapshotというかスナップショット自体、使っているわりに詳しく知らないわ」ということでスナップショットの復習していきたいと思います。
スナップショットといっても復元や削除も含めると範囲が広いため、今回は作成だけにスコープをしぼります。
Elasticsearchについて簡単な復習
「そもそもこんな記事を開いて、さらにこの先を読もうとしている人は知ってるだろ...」とは思いつつ、前提になるElasticsearchの物理概念についてざっくり復習します。
ノード
Elasticsearchが動くサーバーのことをノードと呼びます。
ノードにはいくつか役割があります。ひとつのノードがひとつの役割を持つこともあれば、複数の役割を持つこともあります。
正確には他にも種類や役割がありますが、ここでは割愛して後の説明で出てくる役割だけ紹介します。
それ以外の役割についてはドキュメントや本を読んでください。
クラスタ
Elasticsearchはノードを起動すると、互いにメッセージを送信しあい自律的にノードグループを形成することができます。 これをクラスタとよびます。
インデックス
Elasticsearchでドキュメントを保存する場所をインデックスと呼びます。RDBでいうとテーブルのようなものです。
シャード
インデックスに保存されるデータは、一定数(最低一つ)のシャードと呼ばれる単位に分割されてノードに分散されて格納されます。 これにより検索性能をスケールさせることができます。
シャードには種類が2つあります。
オリジナルのシャードであるプライマリシャードと、プライマリシャードをコピーしたレプリカシャードです。 レプリカシャードに複製することで、インデックスの可用性を担保します。また検索処理はレプリカシャードでも行われるため検索性能が向上するメリットもあります。
セグメント
名前がややこしいのですが、Elasticsearchのシャードは実態はLuceneインデックスです。 このLuceneインデックスはセグメントとよばれる単位でデータを格納しています。
シャード(Luceneインデックス)にドキュメントが書き込まれると、一定の間隔でLuceneはデータをセグメントに書き込みます。 このセグメントは一度作成されると中身を変更されることのないファイルになります。
実際はもっと複雑ですが、今回の趣旨から外れてしまうため、詳しくは以下のドキュメントを参照してください。
Inside a Shard | Elasticsearch: The Definitive Guide [2.x] | Elastic
スナップショット
概要
やっと本題に入りいます。
スナップショットは起動しているクラスタやインデックスのバックアップをとる機能です。
バックアップは、S3やGCSのようなオブジェクトストレージをリポジトリとして使えます。ノードのローカルストレージもリポジトリとして使用可能です。
実行の仕方
具体的な使い方の説明は割愛するので、ドキュメントを見てください。
Snapshot and restore | Elasticsearch Guide [8.5] | Elastic
スナップショットの仕組み
スナップショットには、クラスタの状態やデータストリームやインデックスが含まれます。
インデックスをバックアップする際に、インデックスのセグメントをコピーしてリポジトリに格納します。 セグメントは不変なので、コピーされるのは前回のバックアップからの差分のみです。
バックアップする際にオリジナルのコピーであるレプリカシャードは不要なため、コピーされるセグメントはプライマリシャードのみです。 したがって、インデックスのスナップショットの処理はプライマリシャードをもつデータノードのみで行われます。
一方で、クラスタの状態をリポジトリに書き込む処理はマスターノードで行われます。
また、プライマリシャードのセグメントをコピーするため、プライマリシャードが使えない状態であるときはスナップショットに失敗します。 もしプライマリシャードが開始中・再配置中の場合はプロセスが終わるの待ってからコピーが始まります。
生成されるファイルを確認する
なんとなくスナップショットの仕組みがわかったところで、実際にどういうファイルが生成されるのかを確認します。
今回は手元にノードを立ち上げてローカルファイルストレージをリポジトリとして登録します。
環境は以下を使います。
dockerでElasticsearchを立ち上げて、type: "fs"
でリポジトリのlocationを登録します。
この際ni
url="http://localhost:9200" index_name="test_index" # register a snapshot repository curl -XPUT -H 'Content-Type: application/json' ${url}/_snapshot/fs_backup -d @- <<EOS { "type": "fs", "settings": { "location": "/usr/share/elasticsearch/backup" } } EOS
完了したら適当なインデックスを作って、いくつかドキュメントを追加します。
# init index & data curl -XDELETE ${url}/${index_name} curl -XPUT ${url}/${index_name} curl -XPOST -H 'Content-Type: application/json' ${url}/${index_name}/_doc/1 -d '{"name": "宇宙兄弟"}' curl -XPOST -H 'Content-Type: application/json' ${url}/${index_name}/_doc/2 -d '{"name": "カオスゲーム"}' curl -XPOST -H 'Content-Type: application/json' ${url}/${index_name}/_doc/3 -d '{"name": "ダンジョンの中の人"}' curl -XPOST -H 'Content-Type: application/json' ${url}/${index_name}/_doc/4 -d '{"name": "ガクサン"}' curl -XPOST -H 'Content-Type: application/json' ${url}/${index_name}/_doc/5 -d '{"name": "トリリオンゲーム"}' curl -XPOST -H 'Content-Type: application/json' ${url}/${index_name}/_doc/6 -d '{"name": "ワールドトリガー"}' curl -XPOST -H 'Content-Type: application/json' ${url}/${index_name}/_doc/7 -d '{"name": "暗殺教室"}'
最後に作ったインデックスを対象にスナップショット作成します。
エンドポイントのmy_snapshot_1
は独自につけることができ、これが今回作成するスナップショットを識別する名前になります。
今回は生成されるファイルを確認しやすくするため、インデックス (test_index
)をひとつだけ指定して、my_snapshot_1
という名前のスナップショットを作成します。
url="http://localhost:9200" index_name="test_index" curl -XPUT -H 'Content-Type: application/json' ${url}/_snapshot/fs_backup/my_snapshot_1 -d @- <<EOS { "indices": "${index_name}", "include_global_state": false }
buckupに指定したディレクトリを見ると以下のようなファイル郡が作成されていました。
indices/ LyrD9CRASh2-hs2o2L3Wjg/ 0/ __BLqNUJH1QjSLsZyiW_zO9Q __nlDaU-UySNuvtFu8zHZ2Pw index-DCx8RmXXQeq5IOIJQ0spwQ snap-k4KAjMGQRAqtC4eysZ1l6g.dat meta-IGxlKoUBCn8g1JRSN441.dat index-0 index.latest meta-k4KAjMGQRAqtC4eysZ1l6g.dat snap-k4KAjMGQRAqtC4eysZ1l6g.dat
生成されたファイルの解説
生成されたファイルがなにか簡単に説明していきます。正直ここは理解が怪しいところです。間違っている可能性が大いにあります。
file or directory | 説明 | 中身 |
---|---|---|
indices/ | インデックスの全データを含むディレクトリ | |
indices/LyrD9CRASh2-hs2o2L3Wjg/ | test_indexのデータを含むディレクトリ。indicesディレクトリ内の一意のIDが名前 | |
indices/LyrD9CRASh2-hs2o2L3Wjg/0/ | test_indexの初回スナップショットのデータを含むディレクトリ。2回目のスナップショットを行うと1が作成される。 | |
indices/LyrD9CRASh2-hs2o2L3Wjg/0/__BLqNUJH1QjSLsZyiW_zO9Q | セグメントファイル | 前回スナップショットとの差分のみのセグメントファイル |
indices/LyrD9CRASh2-hs2o2L3Wjg/0/__nlDaU-UySNuvtFu8zHZ2Pw | セグメントファイル | 前回スナップショットとの差分のみのセグメントファイル |
indices/LyrD9CRASh2-hs2o2L3Wjg/0/index-DCx8RmXXQeq5IOIJQ0spwQ | test_indexのシャードのすべてのスナップショットに関する情報 | SMILE形式にシリアライズされたorg.elasticsearch.index.snapshots.blobstore.BlobStoreIndexShardSnapshots |
indices/LyrD9CRASh2-hs2o2L3Wjg/0/snap-k4KAjMGQRAqtC4eysZ1l6g.dat | スナップショットmy_snapshot_1が参照するすべてのファイルのリストとスナップショットに関するメタデータ | SMILE形式にシリアライズされたorg.elasticsearch.index.snapshots.blobstore.BlobStoreIndexShardSnapshot |
indices/LyrD9CRASh2-hs2o2L3Wjg/meta-IGxlKoUBCn8g1JRSN441.dat | test_indexのメタデータ | SMILE形式にシリアライズされたorg.elasticsearch.cluster.metadata.IndexMetadata |
index-0 | すべてのスナップショットidと各スナップショットのインデックスの情報, 数字は世代 | json |
index.latest | 最新のスナップショットの世代を持つファイル | numeric value |
meta-k4KAjMGQRAqtC4eysZ1l6g.dat | スナップショットmy_snapshot_1のクラスタのメタデータ | SMILE形式にシリアライズされたorg.elasticsearch.cluster.metadata.Metadata |
snap-k4KAjMGQRAqtC4eysZ1l6g.dat | スナップショットmy_snapshot_1に関する情報 | SMILE形式にシリアライズされたorg.elasticsearch.snapshots.SnapshotInfo |
ディレクトリやファイル名はuuidになっていて読みにくいですが、インデックス名とスナップショット名の対応がindex-0に書かれています。
{ "min_version": "7.12.0", "uuid": "AGqsvostTMKOAIvPvI2unQ", "cluster_id": "LRMOnF-tQc-0u33BqcAseQ", "snapshots": [ { "name": "my_snapshot_1", "uuid": "k4KAjMGQRAqtC4eysZ1l6g", "state": 1, "index_metadata_lookup": { "LyrD9CRASh2-hs2o2L3Wjg": "2Um7BIJBT6CFF-HZj-7sdw-_na_-1-2-1" }, "version": "8.5.3", "start_time_millis": 1671453554078, "end_time_millis": 1671453554278, "slm_policy": "" } ], "indices": { "test_index": { "id": "LyrD9CRASh2-hs2o2L3Wjg", "snapshots": [ "k4KAjMGQRAqtC4eysZ1l6g" ], "shard_generations": [ "DCx8RmXXQeq5IOIJQ0spwQ" ] } }, "index_metadata_identifiers": { "2Um7BIJBT6CFF-HZj-7sdw-_na_-1-2-1": "IGxlKoUBCn8g1JRSN441" } }
セグメントファイル以外のほとんどのファイルがElasticsearchのクラスがもとになったバイナリファイルのようです。
正直なところ、この説明だけをみても中身や用途がわかりません。 このあたりを読み解くには実際にコードを追っていく必要があります。
スナップショット生成時に行われる処理
先程みたファイル郡を生成する処理は、BlobStoreRepositoryクラスにベースとなる実装があります。