Elasticsearchを使う前に先に読んでおくべきドキュメント
はじめに
Elasticsearchの運用は難しい。最初から知っておけばよかった・・・というプラクティスをまとめておく。
原則的に6.8系の公式ドキュメントに記載されていることを書いていく。
そして、やっぱりドキュメントを呼んで理解して使うことが大事。
プラクティス
一般的に推奨されること
ドキュメント
大きな結果セットを返さない
Elasticsearchはデータベースではなく検索エンジンなので、上位ドキュメントを取得するのに向いている。全件取得するものではない。
取得する場合はScroll APIを使用すること。
大きなドキュメントを避ける
http.max_content_length(HTTPリクエストの最大コンテンツ。デフォルトは100mbで Integer.MAX_VALUEより大きい値に設定すると、100mbにリセットされる。)より大きなドキュメントをインデックス出来ない。そしてLuceneに2GBの制限がある。
Elasticsearchは_id
を取得する必要があるため、_source
を使わないリクエストでも、大きなドキュメントだとネットワークやメモリ使用量やディスクに大きな負荷がかかる。このドキュメントにインデックスをつけるとドキュメントの乗数のメモリが使われることもある。
重要なElasticsearchの構成
ドキュメント
network.host
デフォルトでは127.0.0
のループバックに設定されているが、クラスタを構成するにはルークバック以外を設定する必要がある。
また、network.host
を設定したらElasticsearchは開発モードから本番モードに移行していると想定してシステム起動チェックがWarningからExceptionにアップグレードされる。
開発環境ではWarningで良かったものがExceptionになるため、起動できなくなる。これは構成が正しくない状態で起動しないようにする安全措置である。
例:
network.host: 192.168.1.10
AWSの場合:
network.host: _ec2_
GCPの場合:
network.host: _gce_
Discovery設定
discovery.zen.minimum_master_nodes
データの損失を防ぐためには、discovery.zen.minimum_master_nodesを設定して最小となるmaster候補ノード(master-eligible nodes)数を知ることが重要である。
この設定が無いと、ネットワーク障害が発生したクラスタは、2つの独立したクラスタ(split brain)に分割されるリスクがあり、データの損失につながる。
詳細はAvoiding split brain with minimum_master_nodeseditに記載されている。
split brainを回避するにはクォーラムに設定する必要がある。
(master_eligible_nodes / 2) + 1
マスター候補ノードが3つある場合は、最小マスターノードを(3/2)+1または2に設定する必要がある。
discovery.zen.minimum_master_nodes: 2
しかし、デフォルトではすべてのノードがmster-eligible,data,ingestのnodeのroleになっているため、3台以上に構成すれば問題はない。
しかし、大規模なクラスタとなるとmasterは安定して負荷がないように、専用にするべきなので、分けた場合は上記を考慮する必要がある。X
ヒープサイズの設定
デフォルトではJVMに最小最大サイズが1GBのヒープを使用するようにしている。そのため、本番環境を構築する際はjvm.optionでXms
,Xmx
を正しく設定する必要がある。また、同じ値に設定する。
経験則的には以下。
- 使用できるヒープが多いほどキャッシュに使用できるメモリが多くなるが、多すぎるとGC時の停止時間が長くなる可能性がある
-
Xms
とXmx
は物理RAMの50%以下。カーネル、ファイルシステムキャッシュに十分な物理RAMがあるようにする -
Xms
とXmx
をJVMが圧縮オブジェクトポインタ(compressed oops)に使用するカットオフ値以上に設定しない。正確なカットオフは様々だが、32GB近く。
以下のようなログがあると制限を下回っていることを確認出来る。
heap size [1.9gb], compressed ordinary object pointers [true]
- ゼロベース圧縮oopsを下回ること。正確なカットオフは様々だが、26GBが安全で、一部のシステムでは30GBになる可能性もある。 JVM options
-XX:+UnlockDiagnosticVMOptions
を指定して起動し、次の行を探すことで制限を下回っていることを確認できる。
heap address: 0x000000011be00000, size: 27648 MB, zero based Compressed Oops
ゼロベース圧縮oopsが有効になっていることを示す。
heap address: 0x0000000118400000, size: 28672 MB, Compressed Oops with base: 0x00000001183ff000
Tempディレクトリ
Elasticsearchはスタットアップスクリプトが/tmp
以下を使用する。Linuxのディストリビューションによっては、/tmp
領域をクリーンアップする。
スタートアップ以外で使用する機能が追加された場合、問題が生じる可能性がある。
.deb
や.rpm
をsystemd
以下で実行するとクリーンアップから除外される。
しかし、.tar.gz
形式の使うのであれば、 $ES_TMPDIR
環境変数にて書き込み可能な場所を指定して起動する必要がある。
重要なシステム設定
ドキュメント
OS設定系
.zip
か.tar.zip
パッケージを使用する場合、以下のシステム構成が出来る
- 一時的に
ulimit
を使う、もしくは etc/security/limits.conf
RPM か Debian パッケージを使用した場合、ほとんどの設定はシステム構成ファイルで設定される。 systemd
を使用する場合はsystemdの設定ファイルで設定する必要がある。
ulimit
sudo su
ulimit -n 65535
su elasticsearch
/etc/security/limits.conf
elasticsearch - nofile 65535
sysconfig file
- RPM: /etc/sysconfig/elasticsearch
- Debian: /etc/default/elasticsearch
Systemd configuration
[Service]
LimitMEMLOCK=infinity
保存後に sudo systemctl daemon-reload しておく
swappingを無効化する
swappingが発生するとパフォーマンスとノードの安定性が著しく低下するため、発生しないようにする。
GCがミリ秒ではなく、数分間続く可能性があるため、応答が遅延したりクラスタから切断さられる可能性がある。
swappingを無効化する方法は以下の3つ。
-
sudo swapoff -a
で一時的に無効化する。永続的には/etc/fstab
のswap
をコメントアウトする。 - sysctlの
vm.swappiness
を1にする。 - elasticsearch.ymlに
bootstrap.memory_lock: true
を追加する
以下で設定を確認できる。"mlockall" : true
になっておけばいい。
curl -X GET "localhost:9200/_nodes?filter_path=**.mlockall&pretty"
ファイルディスクリプタ
ファイルディスクリプタ不足するとデータが損失する恐れがある。
そのため、65536以上に増やしておく必要がある。
.zip
や.tar.gz
パッケージの場合は起動前にulimit -n 65535
を実行しておくか、/etc/security/limits.conf
にてnofileを65536にしておく必要がある。
RPMやDebianのパッケージの場合はデフォルトで65536に設定しているから必要はない。
以下で確認できる。"max_file_descriptors" : 65535
になっておけばいい。
curl -X GET "localhost:9200/_nodes/stats/process?filter_path=**.max_file_descriptors&pretty"
仮想メモリ
Elasticsearchはデフォルトでmmapfs(メモリにマッピングすることでファイルシステム上のインデックスを格納する)を使用してインデックスを保存する。
OSのデフォルトでは低すぎてメモリ不足の例外が発生する可能性がある。そのため、以下でlimitを増やすことが出来る。永続的には /etc/sysctl.conf
のvm.max_map_count
を設定する。
なお、RPMおよびDebianパッケージの場合は自動で構成されるため、何もしなくていい。
sysctl -w vm.max_map_count=262144
スレッド数
Elasticsearchは多数のスレッドプールを作成する。そのため、最低でも4096は無いといけない。 ulimit -u 4096
で設定できるが、永続的には /etc/security/limits.conf
の nproc
を4096
にする。
systemdで動作させるパッケージディストリビューションの場合は自動で設定するので、追加の必要はない。
indexing速度の調整
ドキュメント
bulk requestを使用する
大量の単一ドキュメントのインデックスリクエストより、bulk requestsははるかにパフォーマンスがいい。
最適なサイズ見積もりは以下で行う。
1. 単一ノード単一シャードでベンチマークを行う
2. 100個 -> 200個 -> 400個といったように2倍にしていく
3. indexの作成速度が一定になる部分を見つける
同じスコアなら、ドキュメントが多すぎるより、少なすぎるほうが安全なので、少ない方にする。
しかし、いくらパフォーマンスが向上しているといってもリクエストが数十メガバイトオーダーになるとクラスタ全体がメモリ不足になる可能性がある。
複数のworker/threadを使用してElasticsearchにデータを送信する
bulk requestが単一スレッドで最大限に活用できる可能性はほぼ無い。複数スレッドを使うようにする。クラスタリソースを有効に使えてfsyncのコストも削減できる。
TOO_MANY_REQUESTS (429)
(javaクライアントだとEsRejectedExecutionException
)が発生しないことを確認する。これはElasticsearchのインデックスが間に合ってないことを示す。
発生時はエクスポネンシャルバックオフでリトライするようにする。
サイズと同様に、ワーカー数も徐々に増やしてディスクI/OやCPUがクラスタで飽和になるまで試すしかない。
更新間隔を長くする
index.refresh_interval
(更新操作を実行する頻度。これの実施タイミングで最近の変更が検索に表示される)のデフォルトは1秒である。これによりElasticsearchは毎秒新しいセグメントを作成する。例えば30sとかに、この値を長くすると大きなセグメントがフラッシュされ、将来のマージのプレッシャーを削減することが出来る。
初回ロードのrefreshとreplicaを無効にする
一度に大量のデータをロードする必要がある場合、index.refresh_interval
を-1
にして、index.number_of_repicas
を0にする。
データの損失のリスクはあるが、インデックス作成が高速になる。作業完了後にもとの値に戻すようにする。
※筆者の見解だが、これはreindexなどの際に用いるのが有用だと思われる。
ファイルシステムキャッシュにメモリを与える
ファイルシステムキャッシュはI/O操作をバッファリングするために使われる。Elasticsearchを実行しているマシンの少なくとも半分をファイルシステムキャッシュに割り当てる必要がある。
自動生成されたIDを使用する
明示的なIDをドキュメントにつけることが出来るが、これは同じIDがシャード内にあるかどうかをチェックする処理が必要になるため負荷が高い。インデックスが大きくなるほど負荷がかかる。
自動生成されたIDだとこの操作がスキップされるため、自動生成を使うべきである。
高速なハードウェアを使うこと
多くのメモリをシステムファイルキャッシュに使ったり、SSDを使うようにする。また、NFSやSMBのようなリモートファイルシステムは使用してはならない。
AWSであるとPIOPSのEBSを使用するようにする。
インデックスバッファサイズ
ノードが重いインデックス作成のみを行っている場合、indices.memory.index_buffer_sizeが、重いインデックス作成を行っているシャードごとに最大で512MBのインデックス作成バッファを提供するのに十分な大きさであることを確認する(これを超えると、通常、インデックス作成のパフォーマンスは向上しない)。Elasticsearchはこの設定(Javaヒープに対するパーセンテージまたは絶対的なバイトサイズ)を、すべてのアクティブなシャードの共有バッファとして使用する。非常にアクティブなシャードは、軽量インデックスを実行しているシャードよりも当然このバッファを使用する。
例えば、JVMに10GBのメモリを与えた場合、インデックス・バッファには1GBが割り当てられるが、これはインデックスを多用する2つのシャードをホストするのに十分な量。
インデックスバッファ・・・インデックスバッファは新しくインデックスが付けられたドキュメントを格納するのに使われる。これがいっぱいになるとバッファ内のドキュメントがディスク上のセグメントに書き込まれる。
_field_names
を無効にする
この_field_names
ではインデックスタイムにオーバーヘッドが発生するため、existsを実行する必要がない場合は無効にする。
検索速度を調整する
ドキュメント
ここでも、ファイルシステムキャッシュにメモリを与えるように言及されている
ドキュメントモデリング
検索時のコストが下がるようにドキュメントをモデル化する。
結合(join)は避ける。nested
クエリが数倍遅くなる。親子(parent-child
)関係だと数百倍遅くなる可能性がある。
非正規化で大幅なスピードアップが期待できる。
できるだけ少ないフィールドを検索する
query_string
やmulti_match
クエリのターゲットが多いほど検索は遅くなる。複数のフィールドの検索速度を向上させるにはインデックス時に値を1つのフィールドにコピーし検索時のこのフィールドを使用する。これはcopy-to
で自動化出来る。
日付は四捨五入する
now
を使うとキャッシュされない。クエリキャッシュを有効活用できるように四捨五入する。
秒は丸めて分にするなど。丸める範囲を大きくすれば大きくするほどキャッシュ効率はよくなるが、ユーザーエクスペリエンスが低下するトレード・オフとなる。
読み取り専用インデックスをForce-mergeする
特に時系列でインデックスを作るような用途の場合、Force Merge
するとメリットが得られる。
※必ず読み取り専用となるインデックスでなければならない。非常に大きなセグメントが生成される可能性があり、それがシャードに残る可能性がある。また、マージ中は書き込みがブロックされる。
forcemergeを使用すると保持するセグメント数を減らすことが可能。(パフォーマンスが向上する)
レプリカはスループットに役立つ可能性があるが、常にそうとは限らない
レプリカは復元力の向上に加えて、スループットの向上にも役に立つ。
しかし、可用性とスループットにはトレードオフがある。replica数が増えると、ノードあたりのシャード数が増えるため、パフォーマンスが低下する。これはファイルシステムキャッシュがElasticsearchの最大のパフォーマンス要因であるため。
レプリカの適切な数はどうすればいいか。最大ノード障害数max_failures
に対応したい場合は以下となる。
max(max_failures, ceil(num_nodes / num_primaries) - 1)
shardのサイズ
ドキュメント
6.8についてのドキュメントはなし
7.2
sharding戦略について
前提として、万能なシャーディング戦略はない。最良の方法は本番で動いているようなクエリで負荷試験をしてパフォーマンスのベンチマークを行うしか無い。
その中でもオーバーシャーディングというものがある。オーバーシャーディングはシャードの数が多くてクラスターが不安定になる現象のことである。
オーバーシャーディングについて、どう対応すればいいか。
より長い期間をカバーする time-baseのインデックスを作成する
時系列データでは、より長い期間をカバーするインデックスを作成することができる。例えば、日足ではなく、月足や年足のインデックスを作成することができる。
ILM(インデックスライフサイクル管理)を使用している場合は、ロールオーバーアクションのmax_age
閾値を増やすことで実現出来る。
リテンションポリシーで許可されている場合は、max_age のしきい値を省略し、代わりに max_docs
や max_size
のしきい値を使用することで、より大きなインデックスを作成することもできる。
空または不要なインデックスは削除する
ILMを使用している場合、max_age
のしきい値に基づいてインデックスをロールオーバーすると、ドキュメントのないインデックスを誤って作成してしまうことがある。このような空のインデックスは何のメリットもないが、リソースを消費する。
オフピーク時にマージする。
force merge
を使用して、小さなセグメントを大きなセグメントにマージする。こうすることによってシャードのオーバーヘッドを減らして、検索速度を向上させられる。
ただ、force mergeはリソースを大量に消費するため、オフピーク時に実行すること。
既存のインデックスをより少ないshardに減らす
書き込みの必要が無くなったら shrink API
を使用してshard数を減らすことが出来る。
小さなインデックスを組み合わせる
reindex APIを使用して類似したmappingを持つインデックスを1つの大きなインデックスに結合することが出来る。
例えば my-index-2099.10.11
というインデックスがあった場合、 my-index-2099.10
にreindexして小さなインデックスを削除する
ベストプラクティス
ドキュメントではなインデックスを削除する
削除されたドキュメントはElasticsearchのファイルシステムからすぐに削除されない。関連する各シャードでドキュメントを削除済みとしてマークする。マークされたドキュメントは定期的な セグメントマージで削除されるまでリソースを使用し続ける。
削除する場合は、インデックス自体を削除することによってリソースを開放できる。
時系列データにはデータストリームとILM(インデックスライフルサイクル管理)を使用する
データストリームを使用すると、時系列データを複数のtime-baseバッキングインデックスにストアできる。ILMを使用すると、このバッキングインデックスを自動的に管理出来る。
この設定のメリットは、自動ロールオーバーがあって max_age
max_docs
max_size
の敷地に従って新しいインデックスが作成される。これらのしきい値を使用して保持するインデックスを作成出来る。
インデックスが不要になった場合は、ILMを使用してインデックスを自動的に削除してリソースを開放できる。
- 新しいインデックスのシャード数をへらしたい?
- matching index template の index.number_of_shards を変更する
- 大きなシャードを作りたい?
- ILMポリシーの rollover threshold を増やす
- 短いインターバルのインデックスが必要?
- 古いインデックスを早く削除することによってシャード数の増加を相殺することが出来る。そのためにポリシー削除フェーズの
min_age
しきい値を下げる必要がある。
- 古いインデックスを早く削除することによってシャード数の増加を相殺することが出来る。そのためにポリシー削除フェーズの
10GB 〜 65GB のシャードサイズを目安にする
65GB以上のシャードはクラスタの障害回復能力を低下する可能性がある。ノードに障害が発生するとそのノードのシャードを他のノードにリバランシングする。
大きなシャードはネットワーク上での移動が難しく、ノードのリソースを圧迫する可能性がある。
ILMを使用している場合は、ロールオーバーアクションの max_primary_shard_size
の敷地を65GBに設定することで大きなシャードにならないようにすることができる。
ヒープメモリ1GBあたり20個以下のシャードを目指す
ノードが保持できるシャードの数はノードのヒープメモリに比例する。例えば、30GBのヒープメモリを持つノードでは最大で600個のシャードを持つことが出来る。1GBあたり20シャードを超える場合は別のノードを追加する必要がある。
ノードのホットスポットを避ける
特定のノードにあまりにも多くのシャードが割り当てられると、そのノードがホットスポットになることがある。例えば、大量にインデキシングするインデックスに対して1つのノードにシャードが多すぎる場合、ノードに問題が発生する可能性がある。
ホットスポットを防ぐには update index settings APIを使用して index.routing.allocation.total_shards_per_nodeのインデックス設定を使用して1つのノード上のシャード数を明示的に制限する。