ホットスポッティング
コンピュータのホットスポッティングは、Elasticsearchにおいてリソースの利用がノード間で不均等に分配されると発生する可能性があります。一時的なスパイクは通常問題とは見なされませんが、継続的に著しくユニークな利用がある場合、クラスターのボトルネックにつながる可能性があり、レビューが必要です。
ホットスポッティングの検出
ホットスポッティングは、cat nodesを介して報告されるノードのサブセットにおけるリソース利用の著しい上昇(disk.percent
、heap.percent
、またはcpu
の)として最も一般的に表れます。個々のスパイクは必ずしも問題ではありませんが、利用が繰り返しスパイクするか、時間の経過とともに一貫して高いままである場合(例えば、30秒以上)、リソースは問題のあるホットスポッティングを経験している可能性があります。
例えば、cat nodesを使用して2つの別々の可能性のある問題を示しましょう:
Python
resp = client.cat.nodes(
v=True,
s="master,name",
h="name,master,node.role,heap.percent,disk.used_percent,cpu",
)
print(resp)
Ruby
response = client.cat.nodes(
v: true,
s: 'master,name',
h: 'name,master,node.role,heap.percent,disk.used_percent,cpu'
)
puts response
Js
const response = await client.cat.nodes({
v: "true",
s: "master,name",
h: "name,master,node.role,heap.percent,disk.used_percent,cpu",
});
console.log(response);
コンソール
GET _cat/nodes?v&s=master,name&h=name,master,node.role,heap.percent,disk.used_percent,cpu
コンソール結果
name master node.role heap.percent disk.used_percent cpu
node_1 * hirstm 24 20 95
node_2 - hirstm 23 18 18
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
resp = client.cat.allocation(
v=True,
s="node",
h="node,shards,disk.percent,disk.indices,disk.used",
)
print(resp)
Ruby
response = client.cat.allocation(
v: true,
s: 'node',
h: 'node,shards,disk.percent,disk.indices,disk.used'
)
puts response
Js
const response = await client.cat.allocation({
v: "true",
s: "node",
h: "node,shards,disk.percent,disk.indices,disk.used",
});
console.log(response);
コンソール
GET _cat/allocation?v&s=node&h=node,shards,disk.percent,disk.indices,disk.used
コンソール結果
node shards disk.percent disk.indices disk.used
node_1 446 19 154.8gb 173.1gb
node_2 31 52 44.6gb 372.7gb
node_3 445 43 271.5gb 289.4gb
ここでは、2つの著しくユニークな状況が見られます。node_2
は最近再起動したため、他のすべてのノードよりもはるかに少ないシャード数を持っています。これは、cat recoveryを介して見られるように、disk.indices
がdisk.used
よりもはるかに小さいことにも関連しています。node_2
のシャード数は少ないですが、進行中のILMロールオーバーのために書き込みホットスポットになる可能性があります。これは、次のセクションで説明する書き込みホットスポットの一般的な根本原因です。
2番目の状況は、node_3
がnode_1
よりも高いdisk.percent
を持っていることです。これは、シャードのサイズが均等でない場合(最大200Mドキュメントのシャードを目指すか、10GBから50GBのサイズのシャードを目指すを参照)や、多くの空のインデックスがある場合に発生します。
望ましいバランスに基づくクラスターの再バランスは、ノードがホットスポッティングを避けるための重労働を行います。これは、ノードがウォーターマークに達すること(ディスクウォーターマークエラーの修正を参照)や、書き込みが多いインデックスの総シャード数が書き込まれたノードよりもはるかに少ないことによって制限される可能性があります。
ホットスポットノードを確認するには、ノード統計APIを介して確認できます。時間の経過とともに2回ポーリングして、ノードの完全なnode uptimeの統計を取得するのではなく、ノード間の統計の違いを確認することができます。例えば、すべてのノードのインデックス統計を確認するには:
Python
resp = client.nodes.stats(
human=True,
filter_path="nodes.*.name,nodes.*.indices.indexing",
)
print(resp)
Ruby
response = client.nodes.stats(
human: true,
filter_path: 'nodes.*.name,nodes.*.indices.indexing'
)
puts response
Js
const response = await client.nodes.stats({
human: "true",
filter_path: "nodes.*.name,nodes.*.indices.indexing",
});
console.log(response);
コンソール
GET _nodes/stats?human&filter_path=nodes.*.name,nodes.*.indices.indexing
インデックスレベル
ホットスポットノードは、cat thread poolのwrite
およびsearch
キューのバックアップを介して頻繁に表れます。例えば:
Python
resp = client.cat.thread_pool(
thread_pool_patterns="write,search",
v=True,
s="n,nn",
h="n,nn,q,a,r,c",
)
print(resp)
Ruby
response = client.cat.thread_pool(
thread_pool_patterns: 'write,search',
v: true,
s: 'n,nn',
h: 'n,nn,q,a,r,c'
)
puts response
Js
const response = await client.cat.threadPool({
thread_pool_patterns: "write,search",
v: "true",
s: "n,nn",
h: "n,nn,q,a,r,c",
});
console.log(response);
コンソール
GET _cat/thread_pool/write,search?v=true&s=n,nn&h=n,nn,q,a,r,c
コンソール結果
n nn q a r c
search node_1 3 1 0 1287
search node_2 0 2 0 1159
search node_3 0 1 0 1302
write node_1 100 3 0 4259
write node_2 0 4 0 980
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
resp = client.indices.stats(
level="shards",
human=True,
expand_wildcards="all",
filter_path="indices.*.total.indexing.index_total",
)
print(resp)
Ruby
response = client.indices.stats(
level: 'shards',
human: true,
expand_wildcards: 'all',
filter_path: 'indices.*.total.indexing.index_total'
)
puts response
Js
const response = await client.indices.stats({
level: "shards",
human: "true",
expand_wildcards: "all",
filter_path: "indices.*.total.indexing.index_total",
});
console.log(response);
コンソール
GET _stats?level=shards&human&expand_wildcards=all&filter_path=indices.*.total.indexing.index_total
より高度な分析のために、シャードレベルの統計をポーリングすることができ、これによりインデックスレベルとノードレベルの統計を比較できます。この分析は、ノードの再起動やシャードの再ルーティングを考慮しませんが、概要として機能します:
Python
resp = client.indices.stats(
metric="indexing,search",
level="shards",
human=True,
expand_wildcards="all",
)
print(resp)
Ruby
response = client.indices.stats(
metric: 'indexing,search',
level: 'shards',
human: true,
expand_wildcards: 'all'
)
puts response
Js
const response = await client.indices.stats({
metric: "indexing,search",
level: "shards",
human: "true",
expand_wildcards: "all",
});
console.log(response);
コンソール
GET _stats/indexing,search?level=shards&human&expand_wildcards=all
例えば、サードパーティのJQツールを使用して、indices_stats.json
として保存された出力を処理できます:
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
# 書き込み先シャードの簡略化された統計を表示し、インデックスとノードの参照を含む
cat shard_stats.json | jq -rc 'sort_by(-.avg_indexing)[]' | head
タスクの負荷
シャードの分配の問題は、上記のcat thread poolの例で見られるように、タスクの負荷として最も可能性が高く表れます。タスクが個々の質的な高コストまたは全体的な量的トラフィック負荷のためにノードをホットスポットにする可能性もあります。
例えば、cat thread poolがwarmer
スレッドプールで高いキューを報告した場合、影響を受けたノードのホットスレッドを調べます。warmer
スレッドが100% cpu
に関連してGlobalOrdinalsBuilder
で報告されたとしましょう。これにより、フィールドデータのグローバルオーディナルを調査する必要があることがわかります。
あるいは、cat nodesがホットスポットのマスターノードを示し、cat thread poolがノード間で一般的なキューイングを示しているとしましょう。これは、マスターノードが圧倒されていることを示唆します。これを解決するには、まずハードウェアの高可用性のセットアップを確認し、その後一時的な原因を調べます。この例では、ノードのホットスレッドAPIがother
で複数のスレッドを報告しており、これはガベージコレクションまたはI/Oによって待機またはブロックされていることを示しています。
これらの例のいずれかの場合、問題のあるタスクを確認する良い方法は、cat task managementを介して最も長く実行されている非連続(指定された[c]
)タスクを確認することです。これは、cat pending tasksを介して最も長く実行されているクラスター同期タスクを確認することで補完できます。別の例を使用して、
Python
resp = client.cat.tasks(
v=True,
s="time:desc",
h="type,action,running_time,node,cancellable",
)
print(resp)
Ruby
response = client.cat.tasks(
v: true,
s: 'time:desc',
h: 'type,action,running_time,node,cancellable'
)
puts response
Js
const response = await client.cat.tasks({
v: "true",
s: "time:desc",
h: "type,action,running_time,node,cancellable",
});
console.log(response);
コンソール
GET _cat/tasks?v&s=time:desc&h=type,action,running_time,node,cancellable
コンソール結果
type action running_time node cancellable
direct indices:data/read/eql 10m node_1 true
...
これは問題のあるEQLクエリを浮き彫りにします。これに関しては、タスク管理APIを介してさらに洞察を得ることができます。
Python
resp = client.tasks.list(
human=True,
detailed=True,
)
print(resp)
Ruby
response = client.tasks.list(
human: true,
detailed: true
)
puts response
Js
const response = await client.tasks.list({
human: "true",
detailed: "true",
});
console.log(response);
コンソール
GET _tasks?human&detailed
その応答には、このクエリを報告するdescription
が含まれています:
Eql
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関連です。必要に応じて、監査ログと組み合わせてリクエストのソースを追跡できます。