Elasticache Redisのバージョンアップ時にノードはどう入れ替わる?

2018.11.20

Amazon ElastiCache for Redis のバージョンアップに伴うノードの入れ替え動作を、クラスターが有効・無効それぞれのケースで確認する機会がありましたので共有します。

ポイント

機能 非クラスターモード クラスターモード
利用するエンドポイント プライマリエンドポイント 設定エンドポイント
エンドポイントの向き先 プライマリノードのIPアドレス クラスター配下の全ノードのIPアドレス
フェイルオーバー時のDNSの書き換え 新プライマリノードのIPアドレスに変わる 変更なし
バージョンアップ順 レプリカノードから 同じ
プライマリ昇格対象のノード レプリケーション遅延が最も少ないノード 同じ
旧プライマリノードが更新コマンドを受けた時の挙動 エラー 別ノードにリダイレクト
プライマリ昇格時のコマンド失敗率 多め 少なめ
データロスト レプリカのプライマリ昇格の際、レプリケーションの遅延のために少量のデータが失われる可能性がある 同じ
置き換えノードのプロビジョン 同じAZ・IPアドレス 同じ
アップグレードの並列度 一度に1ノード 一度に1ノード(シャード単位)

クラスターを組んでいない Elasticache Redisの場合

バージョンアップの流れ

  1. バージョンアップを実行
  2. 全ノードの CacheClusterStatus が modifying になる
  3. レプリカノードが順次バージョンアップされる
  4. 全レプリカノードのバージョンアップが完了
  5. レプリカノードがプライマリに昇格し、プライマリエンドポイントはその新ノードのIPアドレスを返す
  6. 旧プライマリノードがバージョンアップされる
  7. 全ノードの CacheClusterStatus が available になる

Redis インスタンスの作成

  • クラスターモードを無効
  • Multi-AZ with Auto-Failover を有効
  • レプリカ数を1
  • Redis バージョンを 4.0.10

で作成します。

エンドポイント

非クラスターモードでは、書き込みはプライマリノード1台に対してのみ可能です。

このノードはプライマリエンドポイントでアクセスできます。

$ dig +short PRIMARY-ENDPOINT.REGION.cache.amazonaws.com
172.31.8.206 # IP address of primary node

1. 初期状態

まずは、デフォルトのノード状態を各ノードに対して INFO コマンドを投げて確認します。

primary node

$ redis-cli -h NODE_ENDPOINT info | egrep '(redis_ver|uptime_in_seconds|role|connected_slaves)'
redis_version:4.0.10
uptime_in_seconds:993
role:master
connected_slaves:1

replica node

$ redis-cli -h NODE_ENDPOINT info | egrep '(redis_ver|uptime_in_seconds|role|connected_slaves)'
redis_version:4.0.10
uptime_in_seconds:973
role:slave
connected_slaves:0

describe-cache-clusters 結果

$ aws elasticache describe-cache-clusters | jq -r '.CacheClusters[]|[.CacheClusterId, .CacheClusterStatus, .EngineVersion] | @csv'
"non-cluster-001","available","4.0.10"
"non-cluster-002","available","4.0.10"

2. レプリカノードからバージョンアップ

バージョンアップはレプリカノードから始まります。

複数のノードで並行してバージョンアップが走ることはありません。

primary node

変化はありません。

$ redis-cli -h NODE_ENDPOINT info | egrep '(redis_ver|uptime_in_seconds|role|connected_slaves)'
redis_version:4.0.10
uptime_in_seconds:1233
role:master
connected_slaves:1

replica node

バージョンアップ完了後の INFO 結果です。

$ redis-cli -h NODE_ENDPOINT info | egrep '(redis_ver|uptime_in_seconds|role|connected_slaves)'
redis_version:5.0.0
uptime_in_seconds:10
role:slave
connected_slaves:0

バージョンが 5.0.0 にあがっており、 uptime_in_seconds から起動したばかりであるとわかります。

旧ノードと同じ AZ に作成され、IP アドレスも同じです。

describe-cache-clusters 結果

$ aws elasticache describe-cache-clusters | jq -r '.CacheClusters[]|[.CacheClusterId, .CacheClusterStatus, .EngineVersion] | @csv'
"non-cluster-001","modifying","4.0.10"
"non-cluster-002","modifying","5.0.0"

アップグレードされたノードはバージョンが 5.0.0 に上がっています。 ノード単位でのバージョンアップは完了していても modifying ステータスのままです。

3. プライマリノードが切り替わる

全レプリカノードのバージョンアップが完了すると、既存のプライマリノードはレプリカに降格し、レプリカノードがプライマリに昇格します。 レプリケーション遅延が最も少ないノードが選ばれます。

このタイミングと前後して、プライマリエンドポイントは新しいプライマリノードの IP アドレスを返すようになります。

$ dig +short PRIMARY-ENDPOINT.REGION.cache.amazonaws.com
172.31.17.82

このフェイルオーバーに伴い、ダウンタイムが発生します。

例えば、更新系コマンドを投げたときに、以下の様なエラーが発生することがあります。

(error) READONLY you can't write against a read only slave.

(旧)primary node

レプリカに降格します。

$ redis-cli -h NODE_ENDPOINT info | egrep '(redis_ver|uptime_in_seconds|role|connected_slaves)'
redis_version:4.0.10
uptime_in_seconds:1354
role:slave
connected_slaves:0

role が slave に変わっています。 connected_slaves も 0 になっています。

(旧)replica node

プライマリに昇格します。

$ redis-cli -h NODE_ENDPOINT info | egrep '(redis_ver|uptime_in_seconds|role|connected_slaves)'
redis_version:5.0.0
uptime_in_seconds:130
role:master
connected_slaves:1

role が master に変わっています。 connected_slaves も 1 になっています。

describe-cache-clusters 結果

$ aws elasticache describe-cache-clusters | jq -r '.CacheClusters[]|[.CacheClusterId, .CacheClusterStatus, .EngineVersion] | @csv'
"non-cluster-001","modifying","4.0.10"
"non-cluster-002","modifying","5.0.0"

4. 旧プライマリノードのバージョンアップ

最後に旧プライマリノードがバージョンアップされます。

(旧)primary node

バージョンアップ完了後の INFO 結果です。

$ redis-cli -h NODE_ENDPOINT info | egrep '(redis_ver|uptime_in_seconds|role|connected_slaves)'
redis_version:5.0.0
uptime_in_seconds:5
role:slave
connected_slaves:0

バージョンが 5.0.0 にあがっており、uptime_in_seconds から起動したばかりであるとわかります。

(旧)replica node

変化はありません。

$ redis-cli -h NODE_ENDPOINT info | egrep '(redis_ver|uptime_in_seconds|role|connected_slaves)'
redis_version:5.0.0
uptime_in_seconds:310
role:master
connected_slaves:1

describe-cache-clusters 結果

旧プライマリノードのバージョンアップが完了した直後は、CacheClusterStatusmodifying です。

$ aws elasticache describe-cache-clusters | jq -r '.CacheClusters[]|[.CacheClusterId, .CacheClusterStatus, .EngineVersion] | @csv'
"non-cluster-001","modifying","5.0.0"
"non-cluster-002","modifying","5.0.0"

しばらくすると available になります。

$ aws elasticache describe-cache-clusters | jq -r '.CacheClusters[]|[.CacheClusterId, .CacheClusterStatus, .EngineVersion] | @csv'
"non-cluster-001","available","5.0.0"
"non-cluster-002","available","5.0.0"

クラスターを組んでいる Elasticache Redisの場合

バージョンアップの流れ

  1. バージョンアップを実行
  2. 全ノードの CacheClusterStatus が modifying になる
  3. (シャード単位)レプリカノードが順次バージョンアップされる
  4. (シャード単位)全レプリカノードのバージョンアップが完了
  5. (シャード単位)レプリカノードがプライマリに昇格
  6. (シャード単位)旧プライマリノードがバージョンアップされる
  7. (シャード単位)旧プライマリノードがバージョンアップされる
  8. 全ノードの CacheClusterStatus が available になる

Redis インスタンスの作成

  • クラスターモードを有効
  • Multi-AZ with Auto-Failover を有効
  • レプリカ数を1
  • シャード数を3
  • Redis バージョンを 4.0.10

で作成します。

エンドポイント

クラスターを有効にした Elasticache Redis ではエンドポイント Configuration Endpoint にコマンドを投げます。

このエンドポイントはクラスター配下の全ノードの IP アドレスを返します。

$ dig +short CONFIGURATION-ENDPOINT.REGION.cache.amazonaws.com
172.31.46.209
172.31.2.192
...

変なシャードやレプリカノードにコマンドが投げられたときはどうなるのでしょうか?

Redis クラスターにおいて、クライアントは任意のノードにコマンドを投げてもよく、コマンドを受け取ったノードが自身で処理できない場合は、処理可能なノードに対するリダイレクト(MOVED)をクライアントに返し、クライアントはリダイレクト命令に従ってコマンドを投げ返します。

A Redis client is free to send queries to every node in the cluster, including slave nodes. The node will analyze the query, and if it is acceptable (that is, only a single key is mentioned in the query, or the multiple keys mentioned are all to the same hash slot) it will lookup what node is responsible for the hash slot where the key or keys belong.

If the hash slot is served by the node, the query is simply processed, otherwise the node will check its internal hash slot to node map, and will reply to the client with a MOVED error, like in the following example:

GET x
-MOVED 3999 127.0.0.1:6381

The error includes the hash slot of the key (3999) and the ip:port of the instance that can serve the query. The client needs to reissue the query to the specified node's IP address and port.

https://redis.io/topics/cluster-spec

1. 初期状態

クラスターを組んでいないときと同じです。

2. レプリカノードからバージョンアップ

クラスターを組んでいないときと同じです。

3. プライマリノードが切り替わる

このフェーズがクラスターモードの有効・無効で大きく変わります。

全レプリカノードのバージョンアップが完了すると、既存のプライマリノードがレプリカノードに降格し、別のレプリカノードがプライマリノードに昇格します。 レプリケーション遅延が最も少ないノードが選ばれます。

非クラスターモードのときは、DNS でエンドポイントを切り替えていましたが、クラスターモードのときは、もともと全ノードのIPアドレスを返すため、DNS レコードの変更はありません。

0.2秒程度の間隔で更新系コマンドを投げ続ける限りでは、非クラスターモードのときのようなエラーは一切発生しませんでした。

Redis クラスターのリダイレクト処理(ACK/MOVED)が寄与しているものと思われます。

primary node

$ redis-cli -h NODE_ENDPOINT info | egrep '(redis_ver|uptime_in_seconds|role|connected_slaves)'
redis_version:4.0.10
uptime_in_seconds:920
role:slave
connected_slaves:0

role が slave に変わります。 connected_slaves も 0 になります。

replica node

$ redis-cli -h NODE_ENDPOINT info | egrep '(redis_ver|uptime_in_seconds|role|connected_slaves)'
redis_version:5.0.0
uptime_in_seconds:129
role:master
connected_slaves:1

role が master に変わります。 connected_slaves も 1(旧プライマリ) になります。

describe-cache-clusters 結果

$ aws elasticache describe-cache-clusters | jq -r '.CacheClusters[]|[.CacheClusterId, .CacheClusterStatus, .EngineVersion] | @csv'
"redis-0001-001","modifying","4.0.10"
"redis-0001-002","modifying","5.0.0"
"redis-0002-001","modifying","4.0.10"
"redis-0002-002","modifying","5.0.0"
"redis-0003-001","modifying","4.0.10"
"redis-0003-002","modifying","5.0.0"

4. 旧プライマリノードのバージョンアップ

クラスターを組んでいないときと同じです。

プライマリーノードで障害が発生した場合はどうなる?

バージョンアップに伴うノード入れ替えでは、ダウンタイムが最小になるように、ノードが置き換わります。

計画的なメンテナンスでクラスター全体のノードを入れ替わる際も、同じように振る舞うものと思われます。

一方で、ノードに障害が発生し、やむを得ず突発的にノード入れ替えが発生することもあります。

そのようなケース、例えば、プライマリノードの障害が発生すると、旧プライマリノードは切り離され、ACTIVE なレプリカにフェイルオーバーし、新しいノードがプロビジョンされます。 バージョンアップ時のように、プロビジョンが整うまで旧プライマリが一時的にレプリカになることはありません(障害のためできません)。

詳細は次のドキュメントを参照ください。

ダウンタイムの最小化: 自動フェイルオーバーでのマルチ AZ

最後に

クラスターモードの有効・無効それぞれのケースでバージョンアップ時のノードの入れ替え動作を確認しました。

レプリカノードがプライマリの昇格する方式が大きく異なり、DNS レコードを書き換える非クラスター版 Redis に対し、 DNS を書き換えず、クラスターレベルでリダイレクト機能が存在するクラスター版 Redis は、バージョンアップ中のリクエスト障害がより少ないという結果が得られました。

クラスターモードを有効にすると、その他にも

  • シャード数の調整でスケーリングが可能
  • シャードによるデータのパーティション化で、より多くのデータを扱える

といったメリットがあります。

一方で「シャード数 x シャードあたりのノード数」分の大きな利用費が発生します。

ビジネスの価値・運用費・機能を比較の上、慎重にクラスターモードを利用するか検討ください。

クラスタモードを検討する上では、次のドキュメントも有用です。

レプリケーション Redis (クラスターモードが無効) 対 Redis (クラスターモードが有効)

実際にバージョンアップする際は、移行計画時に実ワークロードに近いコマンドを元に、エラーの発生率や切り替わり完了までの時間を計測ください。 また、メンテナンスはワークロードの少ない時間帯に行ってください。

それでは。

参考