👏

OpenSearch における最新のシャード配置戦略について

2023/01/16に公開

Amazon OpenSearch Service において、先日シャード配置戦略に関するアップデートが有りました。

https://aws.amazon.com/jp/blogs/news/impact-of-infrastructure-failures-on-shard-in-amazon-opensearch-service/

主なアップデート内容は以下の 2 つの仕組みの導入です

  1. Forced Zone Awareness によるアベイラビリティゾーン間のシャード移動の抑止
  2. Load-Aware Shard Allocation による特定ノードへのシャード過積載の抑止

各アップデートの概要とアップデートによる挙動の変化については AWS 公式 Blog で解説されているので、本記事ではこれらのアップデートを実装レベルで見ていきたいと思います。

Forced Zone Awareness

Forced Zone Awareness の概要

Forced Zone Awarenss について、Blog 内では以下のように解説されています

OpenSearch には、シャードを割り当てる必要があるアベイラビリティーゾーンを設定するために使用される、forced awareness と呼ばれる既存のシャードバランシング設定があります。たとえば、zoneという awareness 属性があり、zone1 と zone2 にノードを設定している場合、forced awareness 機能を使用して、使用可能なゾーンが1つしかない場合にOpenSearchがレプリカを割り当てないようにすることができます。

cluster.routing.allocation.awareness.attributes: zone
cluster.routing.allocation.awareness.force.zone.values: zone1,zone2

この構成例では、node.attr.zone を zone1 に設定して 2 つのノードを起動し、5 つのシャードと 1 つのレプリカでインデックスを作成すると、OpenSearch はインデックスを作成して 5 つのプライマリシャードを割り当てますが、レプリカは割り当てません。レプリカは、node.attr.zone が zone2 に設定されているノードが使用可能になったときにのみ割り当てられます。

OpenSearch はノードにカスタム属性を付与することが可能です。opensearch.yml 内で設定する、ないしプロセス起動時に環境変数として指定することも可能です。

https://github.com/opensearch-project/OpenSearch/blob/2.3/distribution/src/config/opensearch.yml#L25-L28

ここで指定したカスタム属性は、Allocation Awareness の設定内で利用可能です。
cluster.routing.allocation.awareness.attributes で指定した zone は、node.attr.zone というカスタム属性を定義して初めて利用できるようになるわけです。

また cluster.routing.allocation.awareness.force.zone.values というプロパティも、cluster.routing.allocation.awareness.attributeszone を指定することで初めて利用できるようになるわけです。仮に cluster.routing.allocation.awareness.attributes で rack と指定していた場合、Forced Awarenss で利用可能なプロパティは cluster.routing.allocation.awareness.force.rack.values となります。

さて、ここで東京リージョンに構築した OpenSearch ドメインの cluster.routing.allocation.awareness.force.zone.values の設定を見てみましょう。このドメインでは、6 ノードが 3 つのアベイラビリティゾーンに分散配置されています。ap-northeast-1a ap-northeast-1c ap-northeast-1d の 3 つのアベイラビリティゾーンが入っていることが分かりますね。

GET _cluster/settings?include_defaults&filter_path=persistent.cluster.routing.allocation.awareness
{
  "persistent" : {
    "cluster" : {
      "routing" : {
        "allocation" : {
          "awareness" : {
            "force" : {
              "zone" : {
                "values" : "ap-northeast-1a,ap-northeast-1c,ap-northeast-1d"
              }
            }
          }
        }
      }
    }
  },
  "transient" : {
    "cluster" : {
      "routing" : {
        "allocation" : {
          "awareness" : {
            "force" : {
              "zone" : {
                "values" : "ap-northeast-1a,ap-northeast-1c,ap-northeast-1d"
              }
            }
          }
        }
      }
    }
  },
  "defaults" : {
    "cluster" : {
      "routing" : {
        "allocation" : {
          "awareness" : {
            "balance" : "false"
          }
        }
      }
    }
  }
}

ではノードに割り当てられた node.attr.zone はどうなっているかというと、Amazon OpenSearch Service では _cat/nodeattrs API に蓋をされていて確認できません。API を呼び出すと以下のエラーが返ってきます。

GET _cat/nodeattrs
{"Message":"Your request: '/_cat/nodeattrs' is not allowed."}

Forced Zone Awareness の動作検証

Amazon OpenSearch Service には任意のノードを停止するような機能も無いため、このままではノード停止時の動作検証も難しいですね。なので、ここからは EC2 インスタンス上に構築した OpenSearch クラスターに Amazon OpenSearch Service と同様の Forced Awareness 設定を追加して、ノード停止時の動作を確認していきます。

はじめに、各ノードの opensearch.yml に下記の設定を追加してからクラスターを起動します。

node.attr.zone: ap-northeast-1a # 自信が所属する AZ に応じて変更する
cluster.routing.allocation.awareness.attributes: zone
cluster.routing.allocation.awareness.force.zone.values: ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
cluster.routing.allocation.awareness.balance: false

クラスター内には Blog の構成例と同様、 3 AZ に 2 ノードずつ分散してノードを配置します。

現在のシャード割り当て:
シャードの総数:12 プライマリ + 24 レプリカ = 36 シャード
アベイラビリティーゾーンの数:3
ゾーンごとのシャード数 (zone awareness = true): 36/3 = 12
アベイラビリティーゾーンごとのノード数:2
ノードあたりのシャード数:12/2 = 6

無事クラスターが起動したところで、 _cat/nodes と _cat/nodeattrs の実行結果を見ておきましょう。

GET _cat/nodes
10.101.102.99  31 50 0 0.00 0.00 0.00 dimr cluster_manager,data,ingest,remote_cluster_client - i-03bb5f9150c79f81c
10.101.101.207 55 49 0 0.00 0.00 0.00 dimr cluster_manager,data,ingest,remote_cluster_client - i-07f5d670bfd16ffc1
10.101.102.88  30 50 0 0.04 0.01 0.00 dimr cluster_manager,data,ingest,remote_cluster_client - i-02ebcae499eb258f0
10.101.103.176 66 50 0 0.00 0.00 0.00 dimr cluster_manager,data,ingest,remote_cluster_client * i-04a9961360c6f509e
10.101.103.40  14 49 0 0.00 0.00 0.00 dimr cluster_manager,data,ingest,remote_cluster_client - i-08063724f7ad84b48
10.101.101.119 33 49 0 0.01 0.01 0.00 dimr cluster_manager,data,ingest,remote_cluster_client - i-03e0f54e75ae6b746
GET _cat/nodeattrs?v
node                host           ip             attr value
i-03bb5f9150c79f81c 10.101.102.99  10.101.102.99  zone ap-northeast-1c
i-07f5d670bfd16ffc1 10.101.101.207 10.101.101.207 zone ap-northeast-1a
i-02ebcae499eb258f0 10.101.102.88  10.101.102.88  zone ap-northeast-1c
i-04a9961360c6f509e 10.101.103.176 10.101.103.176 zone ap-northeast-1d
i-08063724f7ad84b48 10.101.103.40  10.101.103.40  zone ap-northeast-1d
i-03e0f54e75ae6b746 10.101.101.119 10.101.101.119 zone ap-northeast-1a

続いて、公式 Blog と同じシャード数、レプリカ数のインデックスを作っていきます

PUT sample-index
{
    "settings" : {
        "number_of_shards" : 12,
        "number_of_replicas" : 2
    }
}

作成したインデックスのシャード配置は以下のようになりました。

GET _cat/shards/sample-index?v
index        shard prirep state   docs store ip             node
sample-index 2     r      STARTED    0  208b 10.101.102.88  i-02ebcae499eb258f0
sample-index 2     p      STARTED    0  208b 10.101.103.40  i-08063724f7ad84b48
sample-index 2     r      STARTED    0  208b 10.101.101.119 i-03e0f54e75ae6b746
sample-index 8     r      STARTED    0  208b 10.101.101.207 i-07f5d670bfd16ffc1
sample-index 8     r      STARTED    0  208b 10.101.102.88  i-02ebcae499eb258f0
sample-index 8     p      STARTED    0  208b 10.101.103.40  i-08063724f7ad84b48
sample-index 5     r      STARTED    0  208b 10.101.102.99  i-03bb5f9150c79f81c
sample-index 5     p      STARTED    0  208b 10.101.101.207 i-07f5d670bfd16ffc1
sample-index 5     r      STARTED    0  208b 10.101.103.40  i-08063724f7ad84b48
sample-index 1     r      STARTED    0  208b 10.101.101.207 i-07f5d670bfd16ffc1
sample-index 1     r      STARTED    0  208b 10.101.102.88  i-02ebcae499eb258f0
sample-index 1     p      STARTED    0  208b 10.101.103.176 i-04a9961360c6f509e
sample-index 6     p      STARTED    0  208b 10.101.102.88  i-02ebcae499eb258f0
sample-index 6     r      STARTED    0  208b 10.101.103.176 i-04a9961360c6f509e
sample-index 6     r      STARTED    0  208b 10.101.101.119 i-03e0f54e75ae6b746
sample-index 3     r      STARTED    0  208b 10.101.102.99  i-03bb5f9150c79f81c
sample-index 3     r      STARTED    0  208b 10.101.103.40  i-08063724f7ad84b48
sample-index 3     p      STARTED    0  208b 10.101.101.119 i-03e0f54e75ae6b746
sample-index 4     p      STARTED    0  208b 10.101.102.99  i-03bb5f9150c79f81c
sample-index 4     r      STARTED    0  208b 10.101.101.207 i-07f5d670bfd16ffc1
sample-index 4     r      STARTED    0  208b 10.101.103.176 i-04a9961360c6f509e
sample-index 7     r      STARTED    0  208b 10.101.102.88  i-02ebcae499eb258f0
sample-index 7     p      STARTED    0  208b 10.101.103.176 i-04a9961360c6f509e
sample-index 7     r      STARTED    0  208b 10.101.101.119 i-03e0f54e75ae6b746
sample-index 9     r      STARTED    0  208b 10.101.102.99  i-03bb5f9150c79f81c
sample-index 9     r      STARTED    0  208b 10.101.103.40  i-08063724f7ad84b48
sample-index 9     p      STARTED    0  208b 10.101.101.119 i-03e0f54e75ae6b746
sample-index 11    r      STARTED    0  208b 10.101.102.99  i-03bb5f9150c79f81c
sample-index 11    p      STARTED    0  208b 10.101.101.207 i-07f5d670bfd16ffc1
sample-index 11    r      STARTED    0  208b 10.101.103.40  i-08063724f7ad84b48
sample-index 10    p      STARTED    0  208b 10.101.102.99  i-03bb5f9150c79f81c
sample-index 10    r      STARTED    0  208b 10.101.101.207 i-07f5d670bfd16ffc1
sample-index 10    r      STARTED    0  208b 10.101.103.176 i-04a9961360c6f509e
sample-index 0     p      STARTED    0  208b 10.101.102.88  i-02ebcae499eb258f0
sample-index 0     r      STARTED    0  208b 10.101.103.176 i-04a9961360c6f509e
sample-index 0     r      STARTED    0  208b 10.101.101.119 i-03e0f54e75ae6b746

sample-index のシャード数をノードごとに集計すると、 6 シャードずつ均等に分散されていることが分かります。

$ curl -s https://<ip-address>:9200/_cat/shards/sample-index -k -u <username>:<password> | awk '{print $7}' | sort | uniq -c
      6 10.101.101.119
      6 10.101.101.207
      6 10.101.102.88
      6 10.101.102.99
      6 10.101.103.176
      6 10.101.103.40

では、ノード障害時の動作を見ていきましょう。
まずは ap-northeast-1d 上の i-08063724f7ad84b48 を停止します。

停止後に _cat/nodes と _cat/nodeattrs を実行すると、5 ノード構成になることが確認できました。

GET _cat/nodes
10.101.102.99  35 50 0 0.12 0.05 0.02 dimr cluster_manager,data,ingest,remote_cluster_client - i-03bb5f9150c79f81c
10.101.101.207 58 49 0 0.00 0.00 0.00 dimr cluster_manager,data,ingest,remote_cluster_client - i-07f5d670bfd16ffc1
10.101.102.88  34 50 0 0.01 0.01 0.00 dimr cluster_manager,data,ingest,remote_cluster_client - i-02ebcae499eb258f0
10.101.103.176 17 50 0 0.00 0.00 0.00 dimr cluster_manager,data,ingest,remote_cluster_client * i-04a9961360c6f509e
10.101.101.119 37 49 0 0.08 0.02 0.01 dimr cluster_manager,data,ingest,remote_cluster_client - i-03e0f54e75ae6b746
GET _cat/nodeattrs

node                host           ip             attr value
i-03bb5f9150c79f81c 10.101.102.99  10.101.102.99  zone ap-northeast-1c
i-07f5d670bfd16ffc1 10.101.101.207 10.101.101.207 zone ap-northeast-1a
i-02ebcae499eb258f0 10.101.102.88  10.101.102.88  zone ap-northeast-1c
i-04a9961360c6f509e 10.101.103.176 10.101.103.176 zone ap-northeast-1d
i-03e0f54e75ae6b746 10.101.101.119 10.101.101.119 zone ap-northeast-1a

sample-index のシャードをノードごとに集計すると、ap-northeast-1d に残った一つのノードにシャードが集中していることが分かります。Forced Zone Awareness に則ってシャードの再配置が AZ 内に閉じていることが確認できますね。

$ curl -s https://<ip-address>:9200/_cat/shards/sample-index -k -u <username>:<password> | awk '{print $7}' | sort | uniq -c
      6 10.101.101.119
      6 10.101.101.207
      6 10.101.102.88
      6 10.101.102.99
     12 10.101.103.176

追加で、 ap-northeast-1d 上のもう一つのインスタンスである i-04a9961360c6f509e を停止します。結果、以下のように 12 のシャードが未割り当てとなりました。Forced Awareness によって、ゾーンをまたがってシャードが再配置されないことが確認できました。

index        shard prirep state      docs store ip             node
sample-index 4     p      STARTED       0  208b 10.101.102.99  i-03bb5f9150c79f81c
sample-index 4     r      STARTED       0  208b 10.101.101.207 i-07f5d670bfd16ffc1
sample-index 4     r      UNASSIGNED
sample-index 6     p      STARTED       0  208b 10.101.102.88  i-02ebcae499eb258f0
sample-index 6     r      STARTED       0  208b 10.101.101.119 i-03e0f54e75ae6b746
sample-index 6     r      UNASSIGNED
sample-index 2     p      STARTED       0  208b 10.101.102.88  i-02ebcae499eb258f0
sample-index 2     r      STARTED       0  208b 10.101.101.119 i-03e0f54e75ae6b746
sample-index 2     r      UNASSIGNED
sample-index 3     r      STARTED       0  208b 10.101.102.99  i-03bb5f9150c79f81c
sample-index 3     p      STARTED       0  208b 10.101.101.119 i-03e0f54e75ae6b746
sample-index 3     r      UNASSIGNED
sample-index 8     p      STARTED       0  208b 10.101.101.207 i-07f5d670bfd16ffc1
sample-index 8     r      STARTED       0  208b 10.101.102.88  i-02ebcae499eb258f0
sample-index 8     r      UNASSIGNED
sample-index 9     r      STARTED       0  208b 10.101.102.99  i-03bb5f9150c79f81c
sample-index 9     p      STARTED       0  208b 10.101.101.119 i-03e0f54e75ae6b746
sample-index 9     r      UNASSIGNED
sample-index 10    p      STARTED       0  208b 10.101.102.99  i-03bb5f9150c79f81c
sample-index 10    r      STARTED       0  208b 10.101.101.207 i-07f5d670bfd16ffc1
sample-index 10    r      UNASSIGNED
sample-index 5     r      STARTED       0  208b 10.101.102.99  i-03bb5f9150c79f81c
sample-index 5     p      STARTED       0  208b 10.101.101.207 i-07f5d670bfd16ffc1
sample-index 5     r      UNASSIGNED
sample-index 11    r      STARTED       0  208b 10.101.102.99  i-03bb5f9150c79f81c
sample-index 11    p      STARTED       0  208b 10.101.101.207 i-07f5d670bfd16ffc1
sample-index 11    r      UNASSIGNED
sample-index 7     p      STARTED       0  208b 10.101.102.88  i-02ebcae499eb258f0
sample-index 7     r      STARTED       0  208b 10.101.101.119 i-03e0f54e75ae6b746
sample-index 7     r      UNASSIGNED
sample-index 1     p      STARTED       0  208b 10.101.101.207 i-07f5d670bfd16ffc1
sample-index 1     r      STARTED       0  208b 10.101.102.88  i-02ebcae499eb258f0
sample-index 1     r      UNASSIGNED
sample-index 0     p      STARTED       0  208b 10.101.102.88  i-02ebcae499eb258f0
sample-index 0     r      STARTED       0  208b 10.101.101.119 i-03e0f54e75ae6b746
sample-index 0     r      UNASSIGNED

_cluster/allocation/explain API で未割当のシャードとなっている理由を確認すると、awareness の zone に基づき追加のレプリカは割り当てないと判断されたことが分かります。

GET _cluster/allocation/explain

(...)
    {
      "node_id" : "25k4V2eYT7uPjFBqJNDtTQ",
      "node_name" : "i-02ebcae499eb258f0",
      "transport_address" : "10.101.102.88:9300",
      "node_attributes" : {
        "zone" : "ap-northeast-1c",
        "shard_indexing_pressure_enabled" : "true"
      },
      "node_decision" : "no",
      "deciders" : [
        {
          "decider" : "same_shard",
          "decision" : "NO",
          "explanation" : "a copy of this shard is already allocated to this node [[sample-index][2], node[25k4V2eYT7uPjFBqJNDtTQ], [P], s[STARTED], a[id=QDwizhfoTWKS_CQKckekOw]]"
        },
        {
          "decider" : "awareness",
          "decision" : "NO",
          "explanation" : "there are too many copies of the shard allocated to nodes with attribute [zone], there are [3]total configured shard copies for this shard id and [3] total attribute values, expected the allocated shard count per attribute [2] to be less than or equal to the upper bound of the required number of shards per attribute [1]"
        }
      ]
    },
(...)

この後段階的に停止したノードを起動していくと、ノードごとのシャード割り当て数は停止前の状態に戻りました。

i-04a9961360c6f509e起動後

$ curl -s https://<ip-address>:9200/_cat/shards/sample-index -k -u <username>:<password> | awk '{print $7}' | sort | uniq -c
      6 10.101.101.119
      6 10.101.101.207
      6 10.101.102.88
      6 10.101.102.99
     12 10.101.103.176

i-08063724f7ad84b48起動後

$ curl -s https://<ip-address>:9200/_cat/shards/sample-index -k -u <username>:<password> | awk '{print $7}' | sort | uniq -c
      6 10.101.101.119
      6 10.101.101.207
      6 10.101.102.88
      6 10.101.102.99
      6 10.101.103.176
      6 10.101.103.40

疑問点

ここまでの検証で、公式 Blog でいう所の "Forced Zone Awareness" の大まかな動作を確認できました。

ただ、公式の解説によれば以下のように単一ノード障害でもシャードの再配置は行われないはずですね。

しかしながら、 Forced Awareness を設定するだけでは、単一ノード障害時にシャードの再配置が AZ 内で走ってしまいました。何故でしょうか? この結果には、次に説明する Load Aware Shard Allocation が関係しています。

Load Aware Shard Allocation

Load Aware Shard Allocation 概要

Load Aware Shard Allocation については公式 Blog で詳しく解説されていませんが、以下のソースコードに具体的なコンセプトが記載されています。

https://github.com/opensearch-project/OpenSearch/blob/2.3/server/src/main/java/org/opensearch/cluster/routing/allocation/decider/NodeLoadAwareAllocationDecider.java#L23-L51

ポイントは以下二点です。

  • NodeLoadAwareAllocationDecider は、シャードの過積載が無いようにシャードの割り当てをコントロールするコンポーネントである
  • シャードの割り当て上限は、以下より求められる
    • cluster.routing.allocation.overload_awareness.provisioned_capacity : クラスター内の設定上のノード数
    • cluster.routing.allocation.load_awareness.skew_factor: 過積載の許容係数 (%)
    • cluster.routing.allocation.load_awareness.flat_skew: 過積載の許容数 (実数)

実際の制限数については、以下の式から求めることができます。ceil は、小数点以下を切り上げる関数です。

ceil((クラスター上のシャード数合計 / provisioned_capacity) * (1 + skew_factor / 100.0)) + flat_skew

実際の処理は以下から確認可能です。

https://github.com/opensearch-project/OpenSearch/blob/2.3/server/src/main/java/org/opensearch/cluster/routing/allocation/decider/NodeLoadAwareAllocationDecider.java#L164-L191

さて、Amazon OpenSearch Service に設定されている load_awareness の設定を確認すると、skew_factor は 50.0 (%)、flat_skew はデフォルト値の 2 となっています。また、デフォルトでallow_unassigned_primaries が true になっていますが、これはプライマリシャードであれば、Load Aware Shard Allocation の判定処理を行わずに割り当てを許可するというものです。インデックスの状態、クラスターの状態が RED になり、検索結果が欠損することを防ぐことができるので、true にしておくのがよいですね。

GET _cluster/settings?include_defaults&filter_path=*.cluster.routing.allocation.awareness,*.cluster.routing.allocation.load_awareness
{
  "persistent" : {
    "cluster" : {
      "routing" : {
        "allocation" : {
          "awareness" : {
            "force" : {
              "zone" : {
                "values" : "ap-northeast-1a,ap-northeast-1c,ap-northeast-1d"
              }
            }
          },
          "load_awareness" : {
            "provisioned_capacity" : "6",
            "skew_factor" : "50.0"
          }
        }
      }
    }
  },
  "transient" : {
    "cluster" : {
      "routing" : {
        "allocation" : {
          "awareness" : {
            "force" : {
              "zone" : {
                "values" : "ap-northeast-1a,ap-northeast-1c,ap-northeast-1d"
              }
            }
          },
          "load_awareness" : {
            "provisioned_capacity" : "6",
            "skew_factor" : "50.0"
          }
        }
      }
    }
  },
  "defaults" : {
    "cluster" : {
      "routing" : {
        "allocation" : {
          "awareness" : {
            "balance" : "false"
          },
          "load_awareness" : {
            "allow_unassigned_primaries" : "true",
            "flat_skew" : "2"
          }
        }
      }
    }
  }
}

例えば、6 ノードクラスターに 120 シャード (Primary 40 + Replica 80) が割り当てられている場合、ノードごとのシャード割り当て上限数は以下の通りになります。

ceil((クラスター上のシャード数合計 / provisioned_capacity) * (1 + skew_factor / 100.0)) + flat_skew
= ceil((120 shards / 6 nodes) * (1 + 50.0 / 100.0)) + 2
= 32

この状態で 1 ノード減少し、5 ノードで 120 シャードを受け持つことになった場合、本クラスターは 3 AZ 構成であるため、Load Aware Shard Allocation が無い状況下では、以下のようなノード構成、シャード割り当て数となります

  • AZ-1: 2 ノード、各ノードのシャード割り当て数は 20
  • AZ-2: 2 ノード、各ノードのシャード割り当て数は 20
  • AZ-3: 1 ノード、各ノードのシャード割り当て数は 40

ここに Load Aware Shard Allocation を skew_factor = 50.0flat_skew = 2 で適用した場合、ノードごとのシャード割り当て数上限は 32 となります。従って、AZ-3 においては、1 ノードに上限の 32 シャードが割り当てられ、残り 8 シャードは未割り当てとなることが期待されます。

Load Aware Shard Allocation の動作検証

先ほど Forced Zone Awaraness の検証を行ったクラスターに、以下の設定を追記します。

PUT /_cluster/settings
{
  "persistent" : {
    "cluster.routing.allocation.load_awareness.provisioned_capacity": "6",
    "cluster.routing.allocation.load_awareness.skew_factor": "50.0"
  },
  "transient" : {
    "cluster.routing.allocation.load_awareness.provisioned_capacity": "6",
    "cluster.routing.allocation.load_awareness.skew_factor": "50.0"
  }
} 

ノード停止前の全体のシャード数は以下の通りです。

8   164kb 3.6gb 96.2gb 99.9gb 3 10.101.102.99  10.101.102.99  i-03bb5f9150c79f81c
7    73kb 3.7gb 96.2gb 99.9gb 3 10.101.103.40  10.101.103.40  i-08063724f7ad84b48
8 173.2kb 3.6gb 96.2gb 99.9gb 3 10.101.101.119 10.101.101.119 i-03e0f54e75ae6b746
8 200.8kb 3.6gb 96.2gb 99.9gb 3 10.101.102.88  10.101.102.88  i-02ebcae499eb258f0
7    73kb 3.7gb 96.2gb 99.9gb 3 10.101.103.176 10.101.103.176 i-04a9961360c6f509e
8   164kb 3.6gb 96.2gb 99.9gb 3 10.101.101.207 10.101.101.207 i-07f5d670bfd16ffc1

1 ノード停止後のシャード数は、以下のようになりました。未割当のシャード数は無いようですね。

GET _cat/allocation
 8 112.7kb 3.6gb 96.2gb 99.9gb 3 10.101.101.119 10.101.101.119 i-03e0f54e75ae6b746
 8   164kb 3.6gb 96.2gb 99.9gb 3 10.101.102.99  10.101.102.99  i-03bb5f9150c79f81c
13  74.3kb 3.6gb 96.2gb 99.9gb 3 10.101.103.176 10.101.103.176 i-04a9961360c6f509e
 8 126.6kb 3.6gb 96.2gb 99.9gb 3 10.101.102.88  10.101.102.88  i-02ebcae499eb258f0
 8   164kb 3.6gb 96.2gb 99.9gb 3 10.101.101.207 10.101.101.207 i-07f5d670bfd16ffc1

これは何故かというと、ノードごとのシャード数上限が 14 だからです。ちなみに、停止前と停止後でシャード総数が異なっていますが、これは一部のシステムインデックスがノード数に応じてレプリカ数を可変にしているからです。

ceil((クラスター上のシャード数合計 / provisioned_capacity) * (1 + skew_factor / 100.0)) + flat_skew
= ceil((46 shards / 6 nodes) * (1. 50.0 / 100.0)) + 2
= 14

ここでもう一つインデックスを追加してみましょう。

PUT sample-index-2                                            
{
    "settings" : {
        "number_of_shards" : 12,
        "number_of_replicas" : 2
    }
}

シャード数は下記のようになりました。

GET _cat/allocation?v
shards disk.indices disk.used disk.avail disk.total disk.percent host           ip             node
    14      165.3kb     3.6gb     96.2gb     99.9gb            3 10.101.101.207 10.101.101.207 i-07f5d670bfd16ffc1
    13       74.3kb     3.7gb     96.2gb     99.9gb            3 10.101.103.176 10.101.103.176 i-04a9961360c6f509e
    14      165.3kb     3.6gb     96.2gb     99.9gb            3 10.101.102.99  10.101.102.99  i-03bb5f9150c79f81c
    14      165.2kb     3.6gb     96.2gb     99.9gb            3 10.101.102.88  10.101.102.88  i-02ebcae499eb258f0
    14      151.3kb     3.6gb     96.2gb     99.9gb            3 10.101.101.119 10.101.101.119 i-03e0f54e75ae6b746
    13       74.1kb     3.7gb     96.2gb     99.9gb            3 10.101.103.40  10.101.103.40  i-08063724f7ad84b48

ここで ap-northeast-1d 上の 1 ノードを停止すると、シャード数は下記のようになりました。

GET
shards disk.indices disk.used disk.avail disk.total disk.percent host           ip             node
    14      165.3kb     3.6gb     96.2gb     99.9gb            3 10.101.102.99  10.101.102.99  i-03bb5f9150c79f81c
    14      165.1kb     3.6gb     96.2gb     99.9gb            3 10.101.102.88  10.101.102.88  i-02ebcae499eb258f0
    23       76.3kb     3.7gb     96.2gb     99.9gb            3 10.101.103.176 10.101.103.176 i-04a9961360c6f509e
    14      151.2kb     3.6gb     96.2gb     99.9gb            3 10.101.101.119 10.101.101.119 i-03e0f54e75ae6b746
    14      165.3kb     3.6gb     96.2gb     99.9gb            3 10.101.101.207 10.101.101.207 i-07f5d670bfd16ffc1
     2                                                                                         UNASSIGNED

IP が 10.101.103.176 のノードに 23 シャードが割り当てられているのは、対象のノードが ap-northeast-1d に残っている唯一のノードであるためです。停止した ap-northeast-1d 上のシャードが生き残っているノードに、Forced Zone Awaraness によってルートされた結果ですね。しかしながら Load Aware Shard Allocation によって 23 シャードが割り当て上限となっているため、2 シャードについては割り当てきれずに UNASSIGNED となっています。

ceil((クラスター上のシャード数合計 / provisioned_capacity) * (1 + skew_factor / 100.0)) + flat_skew
= ceil((81 shards / 6 nodes) * (1. 50.0 / 100.0)) + 2
= 23

_cluster/allocation/explain API で割り当てに失敗した理由を確認すると、Load Aware Shard Allocation によるものであることが explanation から分かります。

GET _cluster/allocation/explain
{
  "index" : "sample-index",
  "shard" : 10,
  "primary" : false,
  "current_state" : "unassigned",
  "unassigned_info" : {
    "reason" : "NODE_LEFT",
    "at" : "2023-01-13T13:08:20.527Z",
    "details" : "node_left [pkPacsbcTLGMYnxpr5D3dw]",
    "last_allocation_status" : "no_attempt"
  },
  "can_allocate" : "no",
  "allocate_explanation" : "cannot allocate because allocation is not permitted to any of the nodes",
  "node_allocation_decisions" : [
    {
      "node_id" : "25k4V2eYT7uPjFBqJNDtTQ",
      "node_name" : "i-02ebcae499eb258f0",
      "transport_address" : "10.101.102.88:9300",
      "node_attributes" : {
        "zone" : "ap-northeast-1c",
        "shard_indexing_pressure_enabled" : "true"
      },
      "node_decision" : "no",
      "deciders" : [
        {
          "decider" : "awareness",
          "decision" : "NO",
          "explanation" : "there are too many copies of the shard allocated to nodes with attribute [zone], there are [3] total configured shard copies for this shard id and [3] total attribute values, expected the allocated shard count per attribute [2] to be less than or equalto the upper bound of the required number of shards per attribute [1]"
        }
      ]
    },
    {
      "node_id" : "A1vnCvo3QKCgAZZd6l2Vig",
      "node_name" : "i-03bb5f9150c79f81c",
      "transport_address" : "10.101.102.99:9300",
      "node_attributes" : {
        "zone" : "ap-northeast-1c",
        "shard_indexing_pressure_enabled" : "true"
      },
      "node_decision" : "no",
      "store" : {
        "matching_size_in_bytes" : 208
      },
      "deciders" : [
        {
          "decider" : "same_shard",
          "decision" : "NO",
          "explanation" : "a copy of this shard is already allocated to this node [[sample-index][10], node[A1vnCvo3QKCgAZZd6l2Vig], [P], s[STARTED], a[id=dIaJEKZUSdOi7lC-G3Vpcw]]"
        },
        {
          "decider" : "awareness",
          "decision" : "NO",
          "explanation" : "there are too many copies of the shard allocated to nodes with attribute [zone], there are [3] total configured shard copies for this shard id and [3] total attribute values, expected the allocated shard count per attribute [2] to be less than or equalto the upper bound of the required number of shards per attribute [1]"
        }
      ]
    },
    {
      "node_id" : "cAeGTS3lR4KnosPkBlZ9ow",
      "node_name" : "i-07f5d670bfd16ffc1",
      "transport_address" : "10.101.101.207:9300",
      "node_attributes" : {
        "zone" : "ap-northeast-1a",
        "shard_indexing_pressure_enabled" : "true"
      },
      "node_decision" : "no",
      "store" : {
        "matching_size_in_bytes" : 0
      },
      "deciders" : [
        {
          "decider" : "same_shard",
          "decision" : "NO",
          "explanation" : "a copy of this shard is already allocated to this node [[sample-index][10], node[cAeGTS3lR4KnosPkBlZ9ow], [R], s[STARTED], a[id=-4JmSCGRQ7q7zCOtaeBvBw]]"
        },
        {
          "decider" : "awareness",
          "decision" : "NO",
          "explanation" : "there are too many copies of the shard allocated to nodes with attribute [zone], there are [3] total configured shard copies for this shard id and [3] total attribute values, expected the allocated shard count per attribute [2] to be less than or equalto the upper bound of the required number of shards per attribute [1]"
        }
      ]
    },
    {
      "node_id" : "dNDdh-TvQPSbfgl7zM0gpA",
      "node_name" : "i-03e0f54e75ae6b746",
      "transport_address" : "10.101.101.119:9300",
      "node_attributes" : {
        "zone" : "ap-northeast-1a",
        "shard_indexing_pressure_enabled" : "true"
      },
      "node_decision" : "no",
      "deciders" : [
        {
          "decider" : "awareness",
          "decision" : "NO",
          "explanation" : "there are too many copies of the shard allocated to nodes with attribute [zone], there are [3] total configured shard copies for this shard id and [3] total attribute values, expected the allocated shard count per attribute [2] to be less than or equalto the upper bound of the required number of shards per attribute [1]"
        }
      ]
    },
    {
      "node_id" : "zEnsTxmeQmOZ3nJT6UUObQ",
      "node_name" : "i-04a9961360c6f509e",
      "transport_address" : "10.101.103.176:9300",
      "node_attributes" : {
        "zone" : "ap-northeast-1d",
        "shard_indexing_pressure_enabled" : "true"
      },
      "node_decision" : "no",
      "deciders" : [
        {
          "decider" : "load_awareness",
          "decision" : "NO",
          "explanation" : "too many shards [23] allocated to this node, limit per node [23] considering overload factor [50.00] and flat skew [2] based on capacity [6]"
        }
      ]
    }
  ]
}

まとめ

実際の挙動は公式 Blog で例示したようにシンプルではありませんが、Forced Awaraness および Load Aware Shard Allocation によって、ゾーン障害時、ノード障害時の残存ノードへの影響は実際に抑えられることが確認できました。

特にノード数が少ない環境下でマルチ AZ 構成の Amazon OpenSearch Service を利用する場合は、ゾーン数とシャード数を考慮しつつ、ノード障害時の特定ゾーンにおけるシャード割り当ての状況を Cluster のヘルス状態 (Green, Yellow, Red) を元に監視していくことが重要になります。

補足

Blue/Green Deployment 中の Forced Awarenss の設定について

Blue/Green Deployment 中は Allocation Awareness に関する設定は一時的に削除されるようです。構成変更中はシャード割り当て上限の管理が複雑になるからだと考えられます。

$ aws opensearch describe-domain-change-progress --domain-name sample
{
    "ChangeProgressStatus": {
        "ChangeId": "130930ed-7e9f-40b0-80c8-3ec97723d3a8",
        "StartTime": "2022-12-28T17:10:53.472000+09:00",
        "Status": "PROCESSING",
        "PendingProperties": [
            "ClusterConfig.InstanceCount"
        ],
        "CompletedProperties": [],
        "TotalNumberOfStages": 5,
        "ChangeProgressStages": [
            {
                "Name": "Validation",
                "Status": "COMPLETED",
                "Description": "Validation Succeeded.",
                "LastUpdated": "2022-12-28T17:11:12.404000+09:00"
            },
            {
                "Name": "Creating a new environment",
                "Status": "COMPLETED",
                "Description": "Preparing resources.",
                "LastUpdated": "2022-12-28T17:17:32.918000+09:00"
            },
            {
                "Name": "Provisioning new nodes",
                "Status": "IN_PROGRESS",
                "Description": "Provisioning new nodes - 0 of 4 completed.",
                "LastUpdated": "2022-12-28T17:21:07.080000+09:00"
            },
            {
                "Name": "Copying shards to new nodes",
                "Status": "PENDING",
                "Description": "Copying shards to new nodes."
            },
            {
                "Name": "Deleting older resources",
                "Status": "PENDING",
                "Description": "Deleting resources associated with the old environment."
            }
        ]
    }
}
GET _cluster/settings?include_defaults&filter_path=*.cluster.routing.allocation.awareness

{
  "transient" : {
    "cluster" : {
      "routing" : {
        "allocation" : {
          "awareness" : { }
        }
      }
    }
  },
  "defaults" : {
    "cluster" : {
      "routing" : {
        "allocation" : {
          "awareness" : {
            "balance" : "false"
          }
        }
      }
    }
  }
}

Discussion