見出し画像

Elasticsearchのreindexとalias作成を平日日中にやってみた

みなさん、こんにちは。那須です。

気がつけば技術記事を3ヶ月も書かずに仕事してました。もう何年もブログを書いてきたので、書いてない期間が長ければ長いほど自分の中に「書かないと気持ち悪い」という思いが出てきます。その思いをこの記事で消してしまおうと思ってこれを書いています。

今回はAmazon Elasticsearch Service(以下、Elasticsearch)の運用についてのお話です。実際にElasticsearchを運用されている方にとっては当たり前の内容なのかもしれません。しかし私にとっては初めての経験でとても大変な思いをしたので、忘れないようにここにまとめておきます。


なぜreindexをすることにしたのか?

私が所属するdotDではonedogというサービスを提供していて、その一部の機能でデータストアにElasticsearchを使っています。2021年6月現在、Elasticsearchは以下のような使い方をしています。

・データノードのインスタンスサイズはm5.xlarge.elasticsearch * 3
・データノードのEBSサイズは200GB * 3
・インデックスは1つのみ
・シャード数は5つ(AWSのESのデフォルト)
・今のところ古いデータをアーカイブしたり削除したりはしていない

また、以下のAWSのドキュメントにはこのような記載があります。

A good rule of thumb is to try to keep shard size between 10–50 GiB. Large shards can make it difficult for Elasticsearch to recover from failure, but because each shard uses some amount of CPU and memory, having too many small shards can cause performance issues and out of memory errors.

AWSサポートの方によると、シャードサイズが50GBを超えているとシャードの再配置や移行で問題が発生する可能性が高くなることがあるそうです。Elasticsearchの構成変更やアップグレード時にドメインが処理中のままになる等ですね。ただ、必ずそうなるというわけではなさそうです。

というわけで、放っておくとElasticsearch運用の観点から危険な状況になる可能性があることがわかりました。reindexでシャード数を増やしてシャードサイズを減らせることがわかったので、データがシャードから溢れることを防ぐためにやってみることにしました。


なぜalias作成をしたのか?

reindexで同じマッピング設定で新インデックスにドキュメントをコピーして、アプリから新インデックスにアクセスするようにすれば何も問題はないのですが、こういった作業をするたびにアプリの接続先を変更するのは正直面倒です。また、作業ミス等で変更に失敗すると、その時間だけサービスを止めてしまうことになります。

Elasticsearchにはaliasという便利なものがあって、インデックスに対してaliasを作れます。実際のインデックス名がindex1で、alias名がindexだった場合、アプリからはindexという名前のインデックスにアクセスするようにしておけば、裏でインデックス名が変わってもアプリには何も影響がないようにできます。こうすることで、サービス停止することなくインデックスに対する作業を行うことができますね。


何が問題だったのか?

2021年6月からめでたくonedogを海外のユーザにもご利用いただけるようになりました! ですので、単純にreindexとalias作成するだけではなく、いかにサービスを停止することなく、もしくは停止時間を短くできるかが重要なポイントになっています。日本国内だけにしかユーザがいない場合は夜間にある程度サービスを止めても影響は小さいですが、グローバル展開後はそうもいかないですよね。

また、reindex実施中はインデックス内のドキュメントの丸ごとコピーが行われるので、CPU負荷とIOが激増します。それが一瞬で終わればいいのですが、Elasticsearchには以下のドキュメントがありました。

・ドキュメント数:約8億5000万
・ドキュメントサイズ:約200GB

一度本番環境のElasticsearchのレプリカを作って何もチューニングせずにreindexのテストをしたところ、1日経過しても終わりませんでした。CPU利用率も90%近くまで上がります。しかも6時間くらい経過するとCPU負荷もIOも1/4程度まで下がってしまって、reindexのスピードがかなり低下します。これではサービスへの影響が大きすぎますね。

AWSサポートに上記の事象を説明し、どうすれば最短時間でreindexを完了できるかを問い合わせてみたところ、以下のように遅くなった原因の回答がありました。

・EBSのバーストクレジットが途中で枯渇している
・ElasticsearchはEC2インスタンスおよびEBSボリュームを使用しているので、EBSボリュームと同じ制約が適用される

最近、EC2インスタンスの運用をしていなかったのでEBSのバーストクレジットの存在を忘れていました。しかも、まさかElasticsearchにもそれが適用されるとは… というわけで、以下のようにElasticsearchドメインを変更しました。

・データノードのインスタンスサイズ:m5.4xlarge.elasticsearch * 3
・データノードのEBSサイズ:3072GB * 3

データノードのインスタンスサイズを4倍、EBSサイズを10倍以上にしました。これだけでもかなり時間短縮できたんですが、さらにElasticsearch内の新インデックスのチューニングを以下のように行いました。

・refresh_intervalを "-1" に設定
・number_of_replicasを "0" に設定
・translog.durabilityを "async" に設定
・reindex実行時のクエリにslices=autoを設定(これが一番効いた!)

これで再度reindexのテストをしたところ、約3時間でreindex作業を完了することができました!


いつやるか?

あとは手順を正確に書いて実行するだけですね。最初は何かあった時のために夜間作業にしようと思っていました。ただ、インフラ運用に携わっている方ならわかると思いますが、夜間作業って安全なようで安全じゃないんですよね。

・普段寝ている時間に起きて作業するので手作業だとオペミスする可能性大
・作業ミス等で予想外のことが起きても誰も助けてくれない(寝てるので

無停止でできる作業ならなるべく平日日中にやりたい。そう思うのは当然です。今回の作業は、事前のテストで負荷がかかるのは約3時間とわかっていますし、サービスの停止も必要ありません。そして、以下のCloudWatchメトリクスが作業対象のElasticsearchのCPU利用率とIndexingRateなんですが、ピークが1日に2回あってほぼちょうど12時間周期なんです。

画像1

もしかして平日日中にできるのでは…と思ったので、一番頭が元気な月曜の日中にすることにしました。平日日中であればエラーにもすぐ気づけますし、他のメンバーのサポートも受けることができるので安心です。


実際の作業の流れ

最初にElasticsearchのスケールアップです。スケールアップ中はインスタンス数が2倍になりますが、その時にシャードの状態を見てみると新しくできたデータノードにRELOCATINGしている様子がわかりますね。

curl https://xxx.ap-northeast-1.es.amazonaws.com/_cat/shards?v
index  shard prirep state           docs  store ip            node
aaa    3     p      STARTED    171386089 40.2gb x.x.x.x tFeRJ2T
aaa    3     r      STARTED    171386089 40.2gb x.x.x.x WDfvLeZ
aaa    1     p      STARTED    171383176 40.2gb x.x.x.x WDfvLeZ
aaa    1     r      RELOCATING 171383176 40.3gb x.x.x.x  7ZU3PTa -> x.x.x.x tFeRJ2TKRP6_mNettuLwOw tFeRJ2T
aaa    4     p      RELOCATING 171374950 40.2gb x.x.x.x  qUSFDz6 -> x.x.x.x rLpedxzSQQ-ZWUtVF44QKA rLpedxz
aaa    4     r      RELOCATING 171374950 40.3gb x.x.x.x xslgACl -> x.x.x.x tFeRJ2TKRP6_mNettuLwOw tFeRJ2T
aaa    2     p      STARTED    171377573 40.2gb x.x.x.x tFeRJ2T
aaa    2     r      RELOCATING 171377573 40.3gb x.x.x.x  7ZU3PTa -> x.x.x.x rLpedxzSQQ-ZWUtVF44QKA rLpedxz
aaa    0     p      STARTED    171356113 40.4gb x.x.x.x rLpedxz
aaa    0     r      RELOCATING 171356113 40.3gb x.x.x.x xslgACl -> x.x.x.x WDfvLeZoTKmuvNVL2HQhgA WDfvLeZ

スケールアップが完了したら、作業前の状態を確認しておきます。

# index数確認
curl https://xxx.ap-northeast-1.es.amazonaws.com/_cat/indices?v

# shardサイズ確認
curl https://xxx.ap-northeast-1.es.amazonaws.com/_cat/shards?v

# index settings確認
curl https://xxx.ap-northeast-1.es.amazonaws.com/aaa/_settings?pretty

# mapping確認
curl https://xxx.ap-northeast-1.es.amazonaws.com/aaa/_mapping?pretty

ではreindexの宛先となる新インデックスを作成しましょう。アプリからの接続とElasticsearchの内部の状態の図は以下になります。旧インデックスは今まで使っていたインデックス、新インデックスは今作った空のインデックスです。

画像3

# 新index作成
curl -XPUT 'https://xxx.ap-northeast-1.es.amazonaws.com/bbb' -H 'Content-Type: application/json' -d'
{
 "settings": {
   "refresh_interval" : "-1",
   "number_of_shards" : "12",
   "number_of_replicas" : "0",
   "translog.durability" : "async"
 },
 "mappings": {
   ...省略
 }
}
'

# index数確認
curl https://xxx.ap-northeast-1.es.amazonaws.com/_cat/indices?v
--
health status index   uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   aaa     uEsxWninRgOyhF1RN_V6tg   5   1  856959620      1017639     20.1kb           10kb
green  open   bbb     EzBqmn7pRUuM56fpi5ynaQ  12   0          0            0      2.6kb          2.6kb

それではreindexを実施しましょう。1回目のreindexで実行時の全データをコピー、2回目のreindexで1回目のreindex中に発生した差分だけをコピーしています。

# reindex実施
curl -X POST "https://xxx.ap-northeast-1.es.amazonaws.com/_reindex?slices=auto&wait_for_completion=false&pretty" -H 'Content-Type: application/json' -d'
{
 "conflicts": "proceed",
 "source": {
   "index": "aaa"
 },
 "dest": {
   "index": "bbb",
   "version_type": "external"
 }
}
'

# reindex task一覧を確認する
curl https://xxx.ap-northeast-1.es.amazonaws.com/_tasks?actions=*reindex

# reindex 親タスクで完了確認する
curl https://xxx.ap-northeast-1.es.amazonaws.com/_tasks/<task_id>?pretty

# 差分だけreindex実施
curl -X POST "https://xxx.ap-northeast-1.es.amazonaws.com/_reindex?slices=auto&pretty" -H 'Content-Type: application/json' -d'
{
 "conflicts": "proceed",
 "source": {
   "index": "aaa",
   "query": {
     "range": {
       "timestamp": {
         "gte": "now-4h"
       }
     }
   }
 },
 "dest": {
   "index": "bbb",
   "version_type": "external"
 }
}
'

これでほぼ全てのデータが新インデックスにコピーできました。新インデックスを使うように切り替える前に、障害発生してもデータが消えないようにレプリカシャードを作成しておきましょう。その他のインデックス設定も通常運用の状態にしておきます。

# index設定を戻す
# レプリカ作成完了までstatusはyellowになる
curl -X PUT 'https://xxx.ap-northeast-1.es.amazonaws.com/bbb/_settings?pretty' -H 'Content-Type: application/json' -d'
{
 "index" : {
   "refresh_interval" : null,
   "number_of_replicas" : "1",
   "translog.durability" : null
 }
}
'

本来であれば、ここでalias作成とともに旧インデックスを削除するのですが、最後のreindex実施からここまでで僅かながら差分データが発生しています。この差分も取り込みつつ今も流れてくるデータを新インデックスに入れたいので、今回はアプリのElasticsearchの接続先を一時的に切り替えます。その後、最後のreindexを実施して新インデックスが最新の状態になるようにしましょう。

画像4

これでalias作成の準備が整いました。以下のコマンドでaliasを作成しましょう。aliasを作成すると同時に旧インデックスは削除します(名前が重複するため)。alias作成後は、アプリのElasticsearchの接続先を元の名前(今作ったalias名)に戻しましょう。

画像5

# snapshotが実行されていないことを確認
curl https://xxx.ap-northeast-1.es.amazonaws.com/_snapshot/_status

# alias作成
curl -X POST "https://xxx.ap-northeast-1.es.amazonaws.com/_aliases?pretty" -H 'Content-Type: application/json' -d'
{
   "actions" : [
       { "add":  { "index": "bbb", "alias": "aaa" }},
       { "remove_index": { "index": "aaa" }}
   ]
}'

# alias確認
curl https://xxx.ap-northeast-1.es.amazonaws.com/_aliases?pretty

これでメインの作業は完了です。reindexを実施すると.tasksというインデックスが作成されていると思いますが、これはタスクの状況を確認できるようにするためのインデックスなので削除してもOKです。

# .task index削除
curl -X DELETE "https://xxx.ap-northeast-1.es.amazonaws.com/.tasks"

最後のステップです。最初にElasticsearchをスケールアップしているので、元のインスタンスタイプとEBSサイズに戻しましょう。そのままでもサービスは大丈夫ですが、コスト的には大丈夫じゃないです。


作業結果

サービス無停止、データロスもなし、ピーク時間帯に負荷をかけることもなく、平日日中に作業を完了させることができました! CloudWatchメトリクスでみるとCPUは50%程度の増加だったので、ピーク時間帯にかかってもギリギリ大丈夫だったのかもしれません。シャード数は5から12に増えているので、CPU利用率が若干上がっているような雰囲気もありますが、負荷への影響もほとんどなさそうです。

画像2

これでしばらくはデータ量の増加にも耐えられますし、aliasを利用することで背後のインデックスを考慮する場面もかなり減りました。


これからのこと

今回の作業でシャード数が増えてデータ保存量は増えましたが、根本的には同じ問題を抱えたままです。古いデータを別のデータストアにアーカイブするのか、ユーザによって残すデータを定義して保存量を制御していくのか、そもそもElasticsearchではなく別のデータストアに変更するなど、どうするかはこれから考えていきます。データ量が一定量を保つようになれば今回のようなreindex作業はほぼなくなると思いますので、真剣に考えていきます。


さいごに

Elasticsearchのreindexとalias作成について書きました。やってみて初めてわかりましたが、Elasticsearchの運用って意外と難しいですね。誰か1人にでもこの記事が役に立てば嬉しいです。

今回はElasticsearchについて書きましたが、弊社では主にAWSサービスを活用して様々なサービスを開発しています。いい感じで運用できている部分やまだまだ改善の余地ありな部分までいろいろありますが、日々改善を重ねています。
一緒にインフラの運用や改善活動をやっていただけるエンジニアの方を大募集しています!少しでも興味があればお声がけください!


この記事が気に入ったらサポートをしてみませんか?