ホットスポッティング

コンピュータのホットスポッティングは、Elasticsearchにおいてリソースの利用がノード間で不均等に分配されると発生する可能性があります。一時的なスパイクは通常問題とは見なされませんが、継続的に著しくユニークな利用がある場合、クラスターのボトルネックにつながる可能性があり、レビューが必要です。

ホットスポッティングの検出

ホットスポッティングは、cat nodesを介して報告されるノードのサブセットにおけるリソース利用の著しい上昇(disk.percentheap.percent、またはcpuの)として最も一般的に表れます。個々のスパイクは必ずしも問題ではありませんが、利用が繰り返しスパイクするか、時間の経過とともに一貫して高いままである場合(例えば、30秒以上)、リソースは問題のあるホットスポッティングを経験している可能性があります。

例えば、cat nodesを使用して2つの別々の可能性のある問題を示しましょう:

Python

  1. resp = client.cat.nodes(
  2. v=True,
  3. s="master,name",
  4. h="name,master,node.role,heap.percent,disk.used_percent,cpu",
  5. )
  6. print(resp)

Ruby

  1. response = client.cat.nodes(
  2. v: true,
  3. s: 'master,name',
  4. h: 'name,master,node.role,heap.percent,disk.used_percent,cpu'
  5. )
  6. puts response

Js

  1. const response = await client.cat.nodes({
  2. v: "true",
  3. s: "master,name",
  4. h: "name,master,node.role,heap.percent,disk.used_percent,cpu",
  5. });
  6. console.log(response);

コンソール

  1. GET _cat/nodes?v&s=master,name&h=name,master,node.role,heap.percent,disk.used_percent,cpu

この同じ出力が5分間にわたって2回取得されたと仮定します:

コンソール結果

  1. name master node.role heap.percent disk.used_percent cpu
  2. node_1 * hirstm 24 20 95
  3. node_2 - hirstm 23 18 18
  4. node_3 - hirstmv 25 90 10

ここでは、2つの著しくユニークな利用が見られます:マスターノードがcpu: 95にあり、ホットノードがdisk.used_percent: 90%にあります。これは、これらの2つのノードでホットスポッティングが発生していることを示しており、必ずしも同じ根本原因からではありません。

原因

歴史的に、クラスターは主にハードウェア、シャードの分配、および/またはタスクの負荷の影響としてホットスポッティングを経験します。これらを影響の範囲に応じて順次レビューします。

ハードウェア

ホットスポッティングに寄与する可能性のある一般的な不適切なハードウェア設定は以下の通りです:

  • リソースが均等に割り当てられていません。例えば、1つのホットノードにその仲間の半分のCPUが与えられている場合。Elasticsearchは、データティア上のすべてのノードが同じハードウェアプロファイルまたは仕様を共有することを期待しています。
  • リソースがホスト上の別のサービスによって消費されています。他のElasticsearchノードを含みます。私たちの専用ホストの推奨を参照してください。
  • リソースが異なるネットワークまたはディスクスループットを経験しています。例えば、1つのノードのI/Oがその仲間よりも低い場合。詳細については、より高速なハードウェアを使用するを参照してください。
  • ヒープが31GBを超えるように構成されたJVM。詳細については、JVMヒープサイズを設定するを参照してください。
  • 問題のあるリソースがメモリスワッピングをユニークに報告します。

シャードの分配

Elasticsearchのインデックスは1つ以上のシャードに分割されており、時には不適切に分配されることがあります。Elasticsearchは、データノード間でシャード数をバランスさせることによってこれに対処します。バージョン8.6で導入されたように、Elasticsearchはデフォルトで望ましいバランスを有効にして、インジェスト負荷を考慮します。ノードは、書き込みが多いインデックスやホスティングしている全体のシャードのためにホットスポッティングを経験する可能性があります。

ノードレベル

cat allocationを介してシャードのバランスを確認できますが、バージョン8.6以降、望ましいバランスはシャードを完全にバランスさせることを期待しない場合があります。両方の方法は、クラスターの安定性の問題の間に一時的に問題のある不均衡を示す可能性があることに注意してください。

例えば、cat allocationを使用して2つの別々の可能性のある問題を示しましょう:

Python

  1. resp = client.cat.allocation(
  2. v=True,
  3. s="node",
  4. h="node,shards,disk.percent,disk.indices,disk.used",
  5. )
  6. print(resp)

Ruby

  1. response = client.cat.allocation(
  2. v: true,
  3. s: 'node',
  4. h: 'node,shards,disk.percent,disk.indices,disk.used'
  5. )
  6. puts response

Js

  1. const response = await client.cat.allocation({
  2. v: "true",
  3. s: "node",
  4. h: "node,shards,disk.percent,disk.indices,disk.used",
  5. });
  6. console.log(response);

コンソール

  1. GET _cat/allocation?v&s=node&h=node,shards,disk.percent,disk.indices,disk.used

次のように返される可能性があります:

コンソール結果

  1. node shards disk.percent disk.indices disk.used
  2. node_1 446 19 154.8gb 173.1gb
  3. node_2 31 52 44.6gb 372.7gb
  4. node_3 445 43 271.5gb 289.4gb

ここでは、2つの著しくユニークな状況が見られます。node_2は最近再起動したため、他のすべてのノードよりもはるかに少ないシャード数を持っています。これは、cat recoveryを介して見られるように、disk.indicesdisk.usedよりもはるかに小さいことにも関連しています。node_2のシャード数は少ないですが、進行中のILMロールオーバーのために書き込みホットスポットになる可能性があります。これは、次のセクションで説明する書き込みホットスポットの一般的な根本原因です。

2番目の状況は、node_3node_1よりも高いdisk.percentを持っていることです。これは、シャードのサイズが均等でない場合(最大200Mドキュメントのシャードを目指すか、10GBから50GBのサイズのシャードを目指すを参照)や、多くの空のインデックスがある場合に発生します。

望ましいバランスに基づくクラスターの再バランスは、ノードがホットスポッティングを避けるための重労働を行います。これは、ノードがウォーターマークに達すること(ディスクウォーターマークエラーの修正を参照)や、書き込みが多いインデックスの総シャード数が書き込まれたノードよりもはるかに少ないことによって制限される可能性があります。

ホットスポットノードを確認するには、ノード統計APIを介して確認できます。時間の経過とともに2回ポーリングして、ノードの完全なnode uptimeの統計を取得するのではなく、ノード間の統計の違いを確認することができます。例えば、すべてのノードのインデックス統計を確認するには:

Python

  1. resp = client.nodes.stats(
  2. human=True,
  3. filter_path="nodes.*.name,nodes.*.indices.indexing",
  4. )
  5. print(resp)

Ruby

  1. response = client.nodes.stats(
  2. human: true,
  3. filter_path: 'nodes.*.name,nodes.*.indices.indexing'
  4. )
  5. puts response

Js

  1. const response = await client.nodes.stats({
  2. human: "true",
  3. filter_path: "nodes.*.name,nodes.*.indices.indexing",
  4. });
  5. console.log(response);

コンソール

  1. GET _nodes/stats?human&filter_path=nodes.*.name,nodes.*.indices.indexing

インデックスレベル

ホットスポットノードは、cat thread poolwriteおよびsearchキューのバックアップを介して頻繁に表れます。例えば:

Python

  1. resp = client.cat.thread_pool(
  2. thread_pool_patterns="write,search",
  3. v=True,
  4. s="n,nn",
  5. h="n,nn,q,a,r,c",
  6. )
  7. print(resp)

Ruby

  1. response = client.cat.thread_pool(
  2. thread_pool_patterns: 'write,search',
  3. v: true,
  4. s: 'n,nn',
  5. h: 'n,nn,q,a,r,c'
  6. )
  7. puts response

Js

  1. const response = await client.cat.threadPool({
  2. thread_pool_patterns: "write,search",
  3. v: "true",
  4. s: "n,nn",
  5. h: "n,nn,q,a,r,c",
  6. });
  7. console.log(response);

コンソール

  1. GET _cat/thread_pool/write,search?v=true&s=n,nn&h=n,nn,q,a,r,c

次のように返される可能性があります:

コンソール結果

  1. n nn q a r c
  2. search node_1 3 1 0 1287
  3. search node_2 0 2 0 1159
  4. search node_3 0 1 0 1302
  5. write node_1 100 3 0 4259
  6. write node_2 0 4 0 980
  7. write node_3 1 5 0 8714

ここでは、2つの著しくユニークな状況が見られます。まず、node_1は他のノードと比較して著しくバックアップされた書き込みキューを持っています。次に、node_3は他のノードの2倍の履歴的に完了した書き込みを示しています。これらは、書き込みが多いインデックスが不適切に分配されているか、同じノードに複数の書き込みが多いインデックスが割り当てられているためである可能性があります。プライマリとレプリカの書き込みは、クラスターの作業量の大部分を占めるため、通常、インデックスシャード数を総ノード数に合わせた後、index.routing.allocation.total_shards_per_nodeを設定してインデックスの分散を強制することをお勧めします。

通常、書き込みが多いインデックスには、インデックスノード全体に均等に分散するための十分なプライマリnumber_of_shardsとレプリカnumber_of_replicasが必要です。あるいは、書き込みホットスポッティングのあるノードを軽いノードにシャードを再ルーティングして緩和することができます。

どのインデックスが問題であるかが明らかでない場合は、インデックス統計APIを介してさらに詳しく調査できます。

Python

  1. resp = client.indices.stats(
  2. level="shards",
  3. human=True,
  4. expand_wildcards="all",
  5. filter_path="indices.*.total.indexing.index_total",
  6. )
  7. print(resp)

Ruby

  1. response = client.indices.stats(
  2. level: 'shards',
  3. human: true,
  4. expand_wildcards: 'all',
  5. filter_path: 'indices.*.total.indexing.index_total'
  6. )
  7. puts response

Js

  1. const response = await client.indices.stats({
  2. level: "shards",
  3. human: "true",
  4. expand_wildcards: "all",
  5. filter_path: "indices.*.total.indexing.index_total",
  6. });
  7. console.log(response);

コンソール

  1. GET _stats?level=shards&human&expand_wildcards=all&filter_path=indices.*.total.indexing.index_total

より高度な分析のために、シャードレベルの統計をポーリングすることができ、これによりインデックスレベルとノードレベルの統計を比較できます。この分析は、ノードの再起動やシャードの再ルーティングを考慮しませんが、概要として機能します:

Python

  1. resp = client.indices.stats(
  2. metric="indexing,search",
  3. level="shards",
  4. human=True,
  5. expand_wildcards="all",
  6. )
  7. print(resp)

Ruby

  1. response = client.indices.stats(
  2. metric: 'indexing,search',
  3. level: 'shards',
  4. human: true,
  5. expand_wildcards: 'all'
  6. )
  7. puts response

Js

  1. const response = await client.indices.stats({
  2. metric: "indexing,search",
  3. level: "shards",
  4. human: "true",
  5. expand_wildcards: "all",
  6. });
  7. console.log(response);

コンソール

  1. GET _stats/indexing,search?level=shards&human&expand_wildcards=all

例えば、サードパーティのJQツールを使用して、indices_stats.jsonとして保存された出力を処理できます:

  1. cat indices_stats.json | jq -rc ['.indices|to_entries[]|.key as $i|.value.shards|to_entries[]|.key as $s|.value[]|{node:.routing.node[:4], index:$i, shard:$s, primary:.routing.primary, size:.store.size, total_indexing:.indexing.index_total, time_indexing:.indexing.index_time_in_millis, total_query:.search.query_total, time_query:.search.query_time_in_millis } | .+{ avg_indexing: (if .total_indexing>0 then (.time_indexing/.total_indexing|round) else 0 end), avg_search: (if .total_search>0 then (.time_search/.total_search|round) else 0 end) }'] > shard_stats.json
  2. # 書き込み先シャードの簡略化された統計を表示し、インデックスとノードの参照を含む
  3. cat shard_stats.json | jq -rc 'sort_by(-.avg_indexing)[]' | head

タスクの負荷

シャードの分配の問題は、上記のcat thread poolの例で見られるように、タスクの負荷として最も可能性が高く表れます。タスクが個々の質的な高コストまたは全体的な量的トラフィック負荷のためにノードをホットスポットにする可能性もあります。

例えば、cat thread poolwarmerスレッドプールで高いキューを報告した場合、影響を受けたノードのホットスレッドを調べます。warmerスレッドが100% cpuに関連してGlobalOrdinalsBuilderで報告されたとしましょう。これにより、フィールドデータのグローバルオーディナルを調査する必要があることがわかります。

あるいは、cat nodesがホットスポットのマスターノードを示し、cat thread poolがノード間で一般的なキューイングを示しているとしましょう。これは、マスターノードが圧倒されていることを示唆します。これを解決するには、まずハードウェアの高可用性のセットアップを確認し、その後一時的な原因を調べます。この例では、ノードのホットスレッドAPIotherで複数のスレッドを報告しており、これはガベージコレクションまたはI/Oによって待機またはブロックされていることを示しています。

これらの例のいずれかの場合、問題のあるタスクを確認する良い方法は、cat task managementを介して最も長く実行されている非連続(指定された[c])タスクを確認することです。これは、cat pending tasksを介して最も長く実行されているクラスター同期タスクを確認することで補完できます。別の例を使用して、

Python

  1. resp = client.cat.tasks(
  2. v=True,
  3. s="time:desc",
  4. h="type,action,running_time,node,cancellable",
  5. )
  6. print(resp)

Ruby

  1. response = client.cat.tasks(
  2. v: true,
  3. s: 'time:desc',
  4. h: 'type,action,running_time,node,cancellable'
  5. )
  6. puts response

Js

  1. const response = await client.cat.tasks({
  2. v: "true",
  3. s: "time:desc",
  4. h: "type,action,running_time,node,cancellable",
  5. });
  6. console.log(response);

コンソール

  1. GET _cat/tasks?v&s=time:desc&h=type,action,running_time,node,cancellable

次のように返される可能性があります:

コンソール結果

  1. type action running_time node cancellable
  2. direct indices:data/read/eql 10m node_1 true
  3. ...

これは問題のあるEQLクエリを浮き彫りにします。これに関しては、タスク管理APIを介してさらに洞察を得ることができます。

Python

  1. resp = client.tasks.list(
  2. human=True,
  3. detailed=True,
  4. )
  5. print(resp)

Ruby

  1. response = client.tasks.list(
  2. human: true,
  3. detailed: true
  4. )
  5. puts response

Js

  1. const response = await client.tasks.list({
  2. human: "true",
  3. detailed: "true",
  4. });
  5. console.log(response);

コンソール

  1. GET _tasks?human&detailed

その応答には、このクエリを報告するdescriptionが含まれています:

Eql

  1. indices[winlogbeat-*,logs-window*], sequence by winlog.computer_name with maxspan=1m\n\n[authentication where host.os.type == "windows" and event.action:"logged-in" and\n event.outcome == "success" and process.name == "svchost.exe" ] by winlog.event_data.TargetLogonId

これにより、チェックすべきインデックス(winlogbeat-*,logs-window*)や、EQL検索リクエストボディがわかります。おそらくこれはSIEM関連です。必要に応じて、監査ログと組み合わせてリクエストのソースを追跡できます。