Bering Note – formerly 流沙河鎮

情報技術系のこと書きます。

OpenSearchCon Europe 2026「ホットスポットの先へ: ヒートを意識したシャード操作でOpenSearchの性能を変える」- Gaurav Bafna & Arpit Bandejiya, Amazon

OpenSearchCon Europe 2026のセッション「Beyond Hot Spots: Revolutionising OpenSearch Performance Through Heat-Aware Operations」をまとめます。

www.youtube.com

スピーカー

このセッションは、Amazon の Arpit Bandejiya 氏と Gaurav Bafna 氏によって行われました。両氏とも OpenSearch のコア機能開発に携わっており、本セッションではクラスターのシャードアロケーションとリバランスの仕組み、そしてそこに潜むホットスポット問題を解決するための新しいフレームワークを提案しています。前半では Arpit 氏が現状のシャードアロケーションの仕組みと課題を整理し、後半では Gaurav 氏がシャードヒートを軸とした解決策とアーキテクチャを説明する構成です。

現在のシャードアロケーションと重み関数の仕組み


OpenSearchでインデックスを作成すると、プライマリとレプリカの数を指定します。これらのシャードを実際にどのノードへ配置するかを決めるのがシャードアロケーションで、最良の性能が出るようにクラスターマネージャがノードを選びます。

アロケーションが走るきっかけは、クラスター状態が変化するイベントです。新しいインデックスの作成、リサイズ、ノード障害、ディスク使用量のwatermark(閾値)超過などがこれに当たります。

OpenSearchには2種類のアロケーターがあります。Gateway Allocatorは既存シャードの再配置を担います。ノードが一度落ちて復帰したとき、すでにそのノードにあったデータと概ね同期しているシャードをどう割り当て直すかを決めます。もう一方のBalanced Shard Allocatorはより複雑で、create indexで新しく生まれたシャードをクラスターのどこに割り当てるかを扱います。Balanced Shard Allocatorは3つのステップで動きます。未割り当てのシャードを割り当てること、スケールダウンやノード除外に伴って起動済みのシャードを移動すること、そしてノード間でシャードをリバランスすることです。各シャードについて、まずAllocation DecidersがYES/NOで配置可能なノードを絞り込み、残った候補を重み関数でスコアリングします。重みが最も低いノードが配置先として選ばれます。

重み関数は、あるシャードをあるノードに置いたときのコストを数値化したものです。重みはshard_weightとindex_weightの和で定義されます。shard_weightは、そのノードのシャード数からクラスター全体の平均シャード数を引いた値に係数θ₀を掛けたものです。index_weightは、割り当てようとしているインデックスについて、そのノードに既に置かれているシャード数と平均との差にθ₁を掛けたものになります。

2つの係数は重み関数の振る舞いを決めます。θ₀を大きくすると全シャードの総数の均等化が重視され、θ₁を大きくすると個々のインデックスがクラスター全体に分散されるよう働きます。これらはcluster.routing.allocation.balance.shardがθ₀を、cluster.routing.allocation.balance.indexがθ₁を制御します。

もう一つ、cluster.routing.allocation.balance.thresholdという設定があります。これはリバランスを発火させる最小の重み差を表し、どの程度の偏りでリバランスを行うかを左右します。重みが最も低いノードがそのシャードの最適な配置先となり、ノード間の重みの差が大きいときに偏りと判断してリバランスがトリガーされる、という仕組みです。

シャード数ベースのこの方式には明確な強みがあります。単純で軽量、かつ決定論的に動くためシグナルの平滑化を必要としません。すべてのシャードのワークロードが似通っている場合にはうまく機能します。クラスター状態の変化イベントでのみ起動するため、常時ポーリングする必要もありません。ディスク使用量は通常のバランシングでは考慮されず、watermark超過の場合にのみ判断材料になります。OpenSearchにはlow watermark、high watermark、flood stageの3段階があり、それぞれ新しいシャードを置けるか、そのノードから退避すべきか、そのノードを読み取り専用にするか、という判断に対応します。

問題は規模が大きくなったときに表面化します。この方式は「すべてのシャードは等しい」と仮定しており、実際のリソースフットプリントを無視しています。大規模クラスターでは、シャードごと、インデックスごとにCPU使用率が異なる不均一なワークロードがむしろ常態です。この前提が崩れると新しいノードがホットスポット化します。空のノードはシャード数の偏差が大きいため、新規インデックスのシャードを総取りしてしまうのです。総シャード数の偏差がインデックスレベルの重みを支配するためにこうなります。cross-clusterやrollover、ISMといった回避策は複雑さを増すだけで、根本原因の解決にはなりません。

スケール時に崩れる前提とSegment Replicationが生む偏り


Segment Replication と Remote Store の導入によって、シャードを等価とみなす前提がさらに大きく崩れます。Segment Replication では、インデックス時にセグメントを実際に構築するのはプライマリシャードだけです。レプリカはプライマリで作られたセグメントを受け取るだけで、自前でセグメントを組み立てる必要がありません。つまりプライマリはCPUを集中的に消費し、レプリカはほとんどCPUを使わないという非対称な負荷構造になります。

ところが重み関数はプライマリとレプリカを区別しません。バランスの基準はあくまでノードあたりのシャード総数であって、プライマリの数ではないのです。この食い違いがノード障害やノード追加のイベントで表面化します。あるノードが落ちると、他ノードにあったレプリカが昇格して生き残ったノードがプライマリを抱え込み、逆にノードを追加すると新しいノードへレプリカばかりが割り当てられます。結果として、同じクラスター内のノード間でCPU負荷が大きく偏ります。プライマリが特定のノードに積み上がる、いわゆる Primary Skew が起きるわけです。

このプライマリ偏りに対して、私たちは段階的な対策を重ねてきました。

最初の対策は、リバランス時にインデックス単位でプライマリを分散させる制約です(#6422)。特定のインデックスについて、そのプライマリシャードがクラスター全体に散らばるよう調整します。ただしこれはベストエフォートで、分散を保証するものではありませんでした。

次に、クラスター全体でプライマリ数を見るグローバルな制約を加えました(#12250#12656)。重み関数が index weight と shard weight の二つを持っていたように、ここではインデックス単位のバランスに加えて、ノードあたりのプライマリ総数をリバランスの考慮対象に組み込んでいます。

さらに、プライマリ数にハードリミットを設ける方式も用意しました(#17293#17295)。index.routing.allocation.total_primary_shards_per_nodecluster.routing.allocation.total_primary_shards_per_node という設定で、1ノードが保持できるプライマリシャード数の上限をインデックス単位またはクラスター単位で決めます。Allocation Decider がこの上限を強制するため、ベストエフォートではなく硬い制約として効きます。

もっとも、これらはいずれも Segment Replication を成立させるために割り当て戦略へ当てたパッチにすぎません。シャードヒートを軸にした割り当てこそが本質的な解決策になります。

ここまでの議論を煎じ詰めると、問題の核心は重み関数がシャード数しか見ていないことに尽きます。プライマリかレプリカかも、各シャードが実際に背負うワークロードも区別しません。ディスクの low/high ウォーターマークや flood stage を割り込んだときにだけ反応する仕組みも、本質的にはシャード数の世界の話です。クラスター内のワークロードはインデックスごと、クエリパターンごと、時間帯ごとに大きく変わるのに、すべてのシャードが等しい負荷を持つという前提が現実と合っていないのです。

この食い違いが連鎖的な悪化を招きます。一部のノードにリソース消費が集中してホットスポットが生まれ、インデックスのレイテンシが上がりスループットが落ちます。過負荷ノードでサーキットブレーカーが発火し始め、CPU と JVM を多く消費するノードが脱落すると、その負荷が他ノードへ波及して連鎖障害に発展します。最終的にしわ寄せはコストに来ます。過負荷ノードを支えるために CPU や JVM の上限を引き上げ、ピーク負荷に備えてオーバープロビジョニングせざるを得なくなり、運用コストが膨らんでいくのです。

本番で観測されたホットスポットと手動対処の限界


本番環境では、いくつかのホットスポットのパターンが繰り返し観測されています。最も単純なのは over-utilisation、つまりノード全体のリソースが軒並み高使用率になっている状態です。この場合は素直にスケールアップするほかなく、アロケーションの工夫では解決できません。

問題はそれ以外のケースにあります。heat imbalance は、シャード数では均衡しているのに一部のノードにホットシャードが集中し、そのノードだけが詰まる状態です。insufficient shards は、10ノードのクラスターに4シャードしかないインデックスを作ったような場合で、その4ノードのリソース使用率が残り6ノードより極端に高くなり、偏りを生みます。インデックス内部でも、カスタムルーティングキーによって特定のシャードへアクセスが集中する hot partition が起こります。ルーティングキーは、ドキュメントをどのシャードに配置・検索するかを決める値で、これが偏ると同一インデックス内でもシャード間の負荷差が生じます。

さらに log analytics のように時間とともにシャードサイズが増え続けるケースは、現状ではディスクの watermark でしか扱えません。加えて、インデックスローテーションやディスク閾値の超過、ノード障害が頻繁なリバランスを誘発し、JVM や CPU の churn を増やしてクラスター全体のスループットを下げる rebalancing storm も起こります。ISM(Index State Management、インデックスのライフサイクルを自動管理する仕組み)のポリシーが過度に積極的だと、この churn はさらに悪化します。

こうしたホットスポットに対して、現状の対処は完全に手作業です。最初にノードレベルの CPU・JVM・I/O メトリクスを監視して過負荷のノードを特定し、続いて Performance Analyzer プラグインからシャードレベルのメトリクスを取得して、CPU・heap・I/O を突き合わせ、そのノード上で実際に負荷の原因となっているシャードを割り出します。

そのうえで移送先となる適切なノードを探すのですが、ここで AZ awareness(可用性ゾーンをまたいでレプリカを分散させる制約)、ディスク watermark、シャード数のバランス、除外フィルターといった Allocation Decider の条件を人手で一つずつ確認しなければなりません。問題がなければ cluster/_reroute API を実行してシャードを手動で移動させます。

この一連の作業はすべて事後対応であり、手間がかかり、ミスも起きやすいものです。さらに厄介なのは、私たちが手で動かしている裏で OpenSearch 自身のリバランスアルゴリズムも動いている点です。シャード数を均そうとする内部の動きが手動の移送を打ち消し、結果としてクラスター全体のリバランスをかえって増やしてしまうことすらあります。

この問題を根本から解こうとするときに核となる概念が shard heat です。shard heat は、シャードが消費しているリソースを複数の次元で捉えた合成指標として定義します。次元には CPU 使用率、JVM heap / メモリ圧、ディスク I/O(read と write のスループット)、ディスク使用量(シャードサイズ)、そして OS レベルのリクエストメトリクス(bulk のドキュメント数やシャードイベント)が含まれます。これは固定ではなく柔軟なリストで、OpenSearch の対象範囲が広がれば次元を追加できます。

計算は各次元のヒートの総和で、shard_heat = w_cpu·RU_cpu + w_jvm·RU_jvm + w_io·RU_io + w_disk·RU_disk という形になります。RU は各リソースの利用量、w はその次元に与える重みです。重みはユースケースごとにチューニングできるところがポイントになります。スループットが CPU で律速される log analytics では CPU の重みを大きくし、メモリ律速になりやすい検索系のユースケースでは JVM の重みを大きくする、という具合です。この重み付けによって、シャード単位のヒートを用途に合わせて算出できるようになります。

多次元ベクトルで捉えるシャードヒートの定義


shard heat を実装に落とすにあたり、各シャードのヒートは5次元のベクトルとして表現します。CPU、メモリ、ディスクI/O、ディスクサイズ、OS メトリクスの5つを軸に取り、それぞれの値を0から10のスケールに正規化します。0が最も負荷が低く、10が最も高い状態です。

正規化の考え方はシンプルです。CPU を例に取ると、シャード単位の CPU 使用量をそのノードが持つ総 CPU 量で割り、10倍します。V_cpu = (shard CPU usage / node total CPU) × 10 という形です。各次元を4ビットで符号化するため、5次元あわせて1シャードのヒートが20ビットに収まります。

このシャードヒートを足し合わせていくことで、上位の指標を組み立てます。ノードヒートはそのノード上の全シャードのヒートベクトルを次元ごとに合算したもので、クラスタヒートは全ノードのヒートベクトルの平均になります。

ノードとクラスタのヒートを定義すると、ノードを温度で分類できるようになります。ノードヒート H_node は、そのノードに載っている全シャードの V_resource を次元ごとに合算した値です。各次元がもともと0〜10に正規化されているため、合算後も平均はその範囲内に収まります。ノードの「温度」は、このヒートベクトルの大きさ(マグニチュード)|H_node| として表します。クラスタヒート H_cluster は全ノードのヒートベクトルの平均です。

クラスタ平均からの偏差に応じて、各ノードを Hot、Warm、Cool、Cold の4段階に分類します。Hot はクラスタ平均より大きく上回るノード、Warm は中程度に上回るノード、Cool は平均付近かわずかに下回るノード、Cold は大きく下回るノードです。

この分類を踏まえると、私たちが目指す成功基準も明確になります。アロケーションとリバランスの目標は、できるだけ多くのノードがクラスタ平均の近くに収まること、つまりノードヒートをクラスタ平均へ収束させ、平均からの偏差を最小化することにあります。

これらの指標をどう集めるかも設計の一部です。メトリクスは5秒ごとに収集し、15分単位のバケットに集約します。この粒度だと1日あたり次元ごとに96サンプルとなり、1シャードあたり384バイトに収まります。1万シャードのクラスタ全体でも約3.8MBで、保持コストは小さく抑えられます。

シャードの集約ヒートには時間加重平均を使います。各バケットに重みを持たせ、古い読み取りには小さい重み、新しい読み取りには大きい重みを掛けて合算します。agg_heat = w1·heat_t1 + w2·heat_t2 + … で、w1 > w2 > w3 という関係です。これにより直近の挙動を強く反映しつつ曲線を滑らかにし、シャードが時間とともにどう振る舞っているかを捉えられます。

メトリクスの収集元としては、これまで Performance Analyzer プラグインが OpenSearch プロセスのサイドカーとして動き、5秒ごとに一時ファイルを生成してきました。ただしこの方式はリソース消費が大きいことが分かっており、より軽量な新しいメトリクスフレームワークへ移行を進めています。

ヒートプロファイリングで追跡するメトリクスは、5つの次元それぞれに対応します。CPU は1シャードあたりの CPU_Utilization で、5秒ウィンドウにおける CPU 時間の割合です。メモリは Heap_AllocRate(シャードあたり毎秒のヒープ割り当てバイト数)と Paging_RSS(実メモリページの resident set size)の2つで捉えます。ディスクI/O は IO_ReadThroughput と IO_WriteThroughput で、読み取りスループットは検索に、書き込みスループットはインデキシングに対応します。ディスク使用量はディスク上のシャードサイズ、OpenSearch のワークロードは ShardBulkDocs(5秒あたりのインデックス済みドキュメント数)と ShardEvents(5秒あたりのシャードイベント総数)で測ります。

すべてのメトリクスは (ShardID, IndexName, ShardRole) でスコープされます。ShardRole はプライマリかレプリカかを表す軸です。Segment Replication ではプライマリだけがセグメントを構築して CPU を消費するため、ShardID と IndexName だけでは負荷の偏りを正しく区別できません。ロールまで含めて記録することで、プライマリとレプリカを分離して扱えるようになります。

ヒートを使った配置・リバランスとヒートウォーターマーク


新規シャードを配置するとき、シャード数ベースの重み関数にノードの熱負荷を加味します。既存の重み関数は cluster.routing.allocation.balance.shard でシャード総数を、cluster.routing.allocation.balance.index でインデックスごとのシャード数をそれぞれ均そうとします。私たちはこれにノードのサーマルロード、つまりノードヒートという次元を追加します。各ノードは自分のヒートを持ち、より低いヒートのノードへシャードを割り当てるという発想です。

配置アルゴリズムには power of two random choices を採用します。これは2つの候補をランダムに選び、負荷の低い方を採るという軽量な負荷分散手法です。Allocation Deciders がまず配置可能なノードの一覧を返します。これはハードな制約で、例えば total_shards_per_node が1なら、そのインデックスのシャードを同一ノードに2つ以上は置けないといった条件で候補を絞り込みます。絞り込まれた候補から2つをランダムに選び、よりクール(ヒートの低い)なノードへ割り当てます。ヒートが同じなら、既存シャード数の少ないノードを選ぶといった単純なタイブレークで十分です。ログ分析のようなユースケースでは、インデックスごとの平均シャード数を下回るノードを優先します。

リバランスは周期的に実行します。ヒートのばらつきが閾値を超えたときだけ動かす形で、それ自体がクラスターに負荷を与えるため、頻繁には走らせません。

アルゴリズムは過負荷ノードの特定から始まります。ノードヒートが mean + 2σ(平均+標準偏差の2倍)を超えたノードを過負荷とみなします。次に移動するシャードを選びます。優先するのはヒートが高く書き込みトラフィックの少ないシャードで、移動の影響を最小に抑えられます。大きいシャードより小さいシャードを選び、作成直後の最も熱いシャードは避けます。選んだシャードをヒート順に並べ、移動後も平均を超えない余力のあるノードへ再配置します。過負荷ノードが見つからなくなるまでこれを繰り返し、反復的にクラスターを均していくという仕組みです。

この周期的なリバランスだけでは不十分です。ノード障害のようにクラスターが壊れる事象に対しては、イベント駆動の仕組みも必要になります。そこで、ディスクウォーターマークになぞらえたヒートウォーターマークへ拡張します。ノードヒートが heat high watermark を超えたら、そのノードからシャード移動を発火させます。

ヒートウォーターマークは、ディスク容量に対するウォーターマークと同じ考え方をヒートに適用したものです。OpenSearch には low/high/flood の3段階のディスクウォーターマークがあり、それぞれ新規シャード受け入れ停止・既存シャードの移動・書き込み拒否を引き起こします。ヒートでも同様に、low watermark を超えたノードは新規シャードの受け入れを止め、high watermark を超えたらそのノードからシャードを積極的に移動させてクールダウンします。flood を超えたら ingestion や search のリクエスト受け付け自体を止めます。ノードがダウンするのを防ぐための赤信号です。判定は単一次元ではなく、ヒートの合成スコアに基づきます。

移行するシャードの選定には、進行中のトラフィックを乱さないためのヒューリスティックを用意しました。ヒートが高く書き込みの少ないシャードを優先します。書き込みの少なさを重視するのは、アクティブにトラフィックを受けるシャードを別ノードへ移すと、整合性と耐久性を保つためのロックが必要になり、レイテンシが増えてコーディネーターやデータノードからのリトライも増えるためです。小さいシャードは移動が速くフットプリントも小さいので優先します。最も熱いシャードは多くのトラフィックを受けている可能性があり、移動が混乱を招くため避けます。作成直後のシャードも書き込みと読み取りの大半を引き受けているため避けます。混乱を最小にしたい一方で、ヒートを確実に下げたいというトレードオフです。

集約は15分バケットで行い、24時間にわたる最大値をとることでピーク需要を捉えます。リバランス自体はばらつきが閾値を超えたときにだけ実行し、churn(不要な移動の連鎖)を避けます。

Internal/Externalブレインと線形計画による最適配置

Internal Brain(推奨アーキテクチャ)


heat-aware allocation を実装する形には、ロジックをクラスター内部に置く Internal Brain と、外部に置く External Brain の二択があります。私たちが推奨するのは前者の Internal Brain で、すべての処理を OpenSearch 内部のプラグイン(Heat Balanced Allocator Plugin)として動かします。

このプラグインは3つのコンポーネントで構成されます。Heat Profiling Module はリソース利用状況を収集し、シャード単位の粒度でヒートデータを集約・加工します。Heat-Based Allocator はそのノードレベルのヒート可視性をもとに、新規シャードの配置先を決めます。これは既出の重み関数にヒート次元を加える仕組みです。Heat-Based Relocation は周期的に動き、ヒートのばらつきが大きいときに最も熱いノードから最も冷たいノードへシャードを移してクラスターのバランスを取り戻します。

データ連携の方式には push と pull がありますが、Internal Brain ではすべてがクラスター内部で完結するため、プラグインが必要なときにメトリクスを取りに行く pull model を採ります。クラスター外に依存するものは存在しません。

External Brain(代替案)


もう一方の External Brain は、リバランスのロジックをクラスターの外部で動かす設計です。メトリクスフレームワークが S3 や CloudWatch といった外部ストレージへデータを書き出し、別に立てたマイクロサービスがそこから温度プロファイルを構築します。マイクロサービスはヒートのばらつきを分析して不均衡を特定し、最適なリバランス案を計算します。計算アルゴリズム自体は Internal Brain と同じものを使い、スケジュールされたジョブが cluster/_reroute API を叩いて実際のシャード移動を実行します。

この設計の利点は、リバランスロジックがクラジ外にあるため、稼働中のクラスターを乱さずに済むことです。CPU を多く使う重い計算も許容でき、アルゴリズムを差し替え可能にしておけば Rust など別の言語で書くこともできます。さらに同じフリートで数百のクラスターを一括して扱えるため、フリート全体に渡るポリシーを適用できます。

一方で外部にあることの弱点もあります。定期的に処理されたメトリクスに依存するためデータに遅延が生じ、インデックス作成時のシャード配置には関与できません。マイクロサービスとストレージという追加のインフラを抱える負担も発生します。これらの理由から、私たちはあまり推奨していません。

Push と Pull のデータ連携モデル


データ連携の2方式は、それぞれ得意とする場面が異なります。push model は外部集約型で、メトリクスをコレクタ経由で S3 や CloudWatch に送り、マイクロサービスがそれを集約してプラグインのインデックスに書き戻します。そこには最も熱い/冷たいシャードやノードといった、行動に移せる要約済みのデータが入ります。スケーラブルでフリート全体のポリシーを実現できる反面、追加インフラを必要とし、データが古くなる問題を抱えます。

pull model はオンデマンド型で、プラグインがメトリクスフレームワークに対して REST 呼び出しを行いヒートプロファイルを取得します。たとえば /metric_store/heat/shardDetails?node=2&tier=warm のように、ノードやティアを指定してその場で必要なプロファイルを引き出せます。リアルタイムで文脈に応じたデータが得られ、外部依存がないためインデックス作成時の判断にも使えます。ただしリクエストごとにクラスターへの負荷がかかります。どちらを選ぶかは、クラスター規模、リバランスの頻度、インフラの利用可否によって変わります。

線形計画による最適配置(長期ビジョン)


より理想的な解として、私たちはシャード配置を線形計画(Linear Programming, LP)で解くことを構想しています。LP は与えられた制約と目的関数のもとで最適解を求める数理最適化の手法です。現状のリバランスは適切なバランスに達するまで移動を繰り返す反復型ですが、LP なら一度の求解で最適な配置を導けます。

パイプラインはこう動きます。過去のヒートプロファイルを収集し、ヒューリスティクスと制約を加えてそれを LP 変数へ変換します。これをソルバーに通すと最適配置が得られ、その解をシャード移動の指示へとデコードします。制約には既存の Allocation Decider をそのまま組み込めるほか、作成直後のインデックスは動かさない、なるべく小さなシャードを選ぶ、ヒートが高く書き込みの少ないシャードを優先する、といった経験則も表現できます。目的関数(コスト関数)は、ワークロードのばらつき、ディスク使用量のばらつき、そしてシャード移動の回数を最小化するように設計します。

こうして最小限の移動で目標とするバランスを一度に達成できますが、代償もあります。求解には計算コストがかかり、通常のユースケースには重すぎる場合があるのです。

期待される効果とロードマップ


heat-aware allocation を導入すると、シャードヒートがクラスタ全体で均一に近づくことで効果が連鎖的に現れます。

まずクエリレイテンシが下がります。特定ノードの JVM が逼迫したり、CPU やキューの増大で ingestion が遅くなったりといった、ホットスポット起因の遅延がなくなるためです。負荷が均等になることでリソース利用率も最適化され、検索スループットを最大まで引き出せます。過負荷ノードが落ちてトラフィックが他ノードへ流れ、連鎖的に倒れていくカスケード障害も、ヒートウォーターマークによる事前検知で防げます。

コスト面の効果も大きいです。消費がノード間でほぼ均一になれば、1台のホットノードのためだけに全体を過剰にプロビジョニングする必要がなくなり、容量は実需要に沿って緩やかに増やせます。これは将来の容量計画を立てやすくすることにもつながります。さらに、移動の判断を高信頼なものに絞ることでリバランスストーム(不要な再配置の連鎖)が減り、重みの次元をユースケースごとに調整できるため、log analytics と search のように負荷特性の異なるワークロードへ個別にチューニングできます。

私たちはこのフレームワークを5段階で段階的に届ける計画です。いきなり全自動のリバランスへ飛び込むのではなく、可視化から始めて信頼を積み上げてから自動化へ進めます。

Phase 1 はヒートプロファイルの可視化です。ヒートプロファイルを API で公開し、ダッシュボードに統合して、ノード間でヒートがどう分布しているかを見られるようにします。Phase 2 ではそこから一歩進み、ヒートの偏りが大きいクラスタを検出します。Phase 3 はヒートに基づくシャード移動で、温度の高いノードから低いノードへ移すべきシャードを判定し、reroute API でその移動を実行します。ただしこの段階では自動化せず、判定結果をユーザーが確認したうえで reroute をトリガーする形にします。

Phase 4 で初めてプラグイン自身がリバランスを行う自動ヒートリバランスに入ります。これはコアエンジンへの変更を伴い、閾値超過が長く続いたときにオフピーク帯で再配置します。ただしこの自動リバランスは負荷が顕在化したあとに動く、いわば事後対応です。最終段階の Phase 5 では、過去のパターンを使って配置前にヒートを予測する事前管理へ進みます。log analytics のようにアクセスパターンが安定したワークロードでは、新しいシャードが直近24時間分のデータをどれだけクエリされるかが過去から読めます。配置時点でそのシャードのヒートを予測できれば、後からリバランスする必要そのものをなくせるという発想です。

まとめ

OpenSearch のシャードアロケーションは長らくシャード数だけを基準にしてきましたが、Segment Replication や不均一なワークロードのもとでは、その前提がプライマリ偏りやホットスポットとなって表面化します。本セッションの提案は、CPU・メモリ・ディスクI/O・ディスクサイズ・OS メトリクスの5次元から成る shard heat を導入し、配置とリバランスの双方にノードの熱負荷を組み込むことで、ノードヒートをクラスタ平均へ収束させようとするものです。展開は可視化から検出、自動リバランス、そして配置前のヒート予測へと段階的に進める計画で、いずれもオープンソースで開発されコントリビューションを歓迎するとのことでした。

Q&A

質問1: シャードサイズはノードのヒート(負荷)と直接関係するのでしょうか。CPUやJVMと比べると、シャードサイズの重みは低いように思えるのですが。
回答: シャードサイズはヒートと直接結び付くものではなく、リソースを完全に使い切ってしまうことを未然に防ぐための指標です。ディスクが満杯になってから動かすのでは手遅れになる場面を実際に見てきました。ノードが完全に埋まる前に、プロアクティブにシャードを移してリソースを上限内に収めたいという考え方です。シャードサイズが不均一なら、能動的に均一化していきます。CPUやJVMと比べたときの重み付けが低くなる、というご指摘はそのとおりで、その点は同意します。あくまで均一分散をこの観点からも得るための仕組みです。

質問2: 検索性能の観点から、シャードサイズは一定の範囲に抑える必要があるのではないでしょうか。
回答: おっしゃるとおりで、検索性能のためにもシャードサイズは上限を設ける必要があります。小さいシャードへ寄せていく方向性はありますが、それには別の難しさがあります。シャードを小さくするとクラスター全体のシャード数が増え、別の悪影響を生むため、適切なバランスが必要です。ユースケースによりますが、ログ分析では1シャードあたり50GB程度を推奨しています。検索の場合はシャード数が増えにくいので、より少ないシャード数が望ましいこともあります。なお、既存シャードを分割する機能については、まだ開発中です。

質問3: データストリームを使って顧客向けのデータを扱っています。過去のインデックスにはIDが振られていて次の番号が分かるような場合、ローリングなどの判断は過去データのどんな情報に基づいて行われるのでしょうか。
回答: 過去のインデックスの傾向(IDの連番など)から次のインデックスを予測し、それに合わせて判断していく形になります。