👏

AWS ElastiCache Redis のクラスターモード移行時のダウンタイム

に公開

AWS ElastiCache for Redis は、クラスターモードでの動作に対応している。また、元々クラスターモードで構築していなかった環境(非クラスターモード)でも、クラスターモードに移行をすることが可能となっている。

https://aws.amazon.com/jp/blogs/news/work-with-cluster-mode-on-amazon-elasticache-for-redis/

その際、AWS 的には「ダウンタイムなし」で移行が可能であると謳っているが、実際はダウンタイムが発生する。

ここではその原因と回避方法について、記載する。
なお、こちらは今日現在(2025/04/28)の情報で、今後 AWS 側のバグ修正などでこちらの事象が解消される可能性がある。

概要

  • 非クラスターモードからクラスターモード移行時に、数分間名前解決に失敗する
    • 互換モード → クラスターモード変更時に発生
      • 非クラスターモード → 互換モード変更時は瞬断は起こるが一瞬
    • 通信暗号化(tls接続)設定時に発生

Redis Cluster の挙動

前提として、ある程度 Redis Cluster がどのように動作するかを把握しておく必要があるため、概要を記載する。

Cluster Mode 時のクライアントからの接続の流れ

Cluster Mode で動作する Redis に接続したクライアントは、以下のような流れで接続処理を行う。各言語向けのライブラリにより実際の手順は様々だが、以下では抽象的に記載。

  1. クライアントが設定エンドポイントに接続
  • スロット情報/ノード情報を取得
  1. コマンド実行
  • 接続サーバーが該当コマンドを受け付けない場合はリダイレクトされる

設定エンドポイントに接続

ElastiCache for Redis のクラスターモードの場合、基本的に複数のノードでクラスターが構成されるが、構成するノードの全てが「設定エンドポイント( clustercfg )」という扱いになる

スロット情報を取得

接続しているノードのスロット情報を取得する。
あわせて Cluster を構成するノード情報を取得する。

取得するためのコマンドが同一(CLUSTER SHARDS)のため、こちらのコマンドを実行することにより情報の取得を行う。
https://redis.io/docs/latest/commands/cluster-shards/

clustercfg.some-redis.abcdef.apne1.cache.amazonaws.com:6379> CLUSTER SHARDS
1) 1) "slots"
   2) 1) (integer) 0
      2) (integer) 16383
   3) "nodes"
   4) 1)  1) "id"
          2) "44bcbc149b5ea4d296a44b47c17de2f8402871fc"
          3) "tls-port"
          4) (integer) 6379
          5) "ip"
          6) "172.16.10.132"
          7) "endpoint"
          8) "some-redis-0001-002.some-redis.abcdef.apne1.cache.amazonaws.com"
          9) "hostname"
         10) "some-redis-0001-002.some-redis.abcdef.apne1.cache.amazonaws.com"
         11) "role"
         12) "master"
         13) "replication-offset"
         14) (integer) 907697
         15) "health"
         16) "online"
      2)  1) "id"
          2) "bcd9775d08d3e1097a1832b87e289abbb15af1e4"
(snip.)
スロット情報

実行したいコマンドが、接続しているサーバーで実行可能かどうかについては、取得したスロット情報で判定する。

以下の値が返却されているとした場合、このサーバーは slot 番号が 0~16383 の間となるコマンドが実行対象となる

1) 1) "slots"
   2) 1) (integer) 0
      2) (integer) 16383

実行したいコマンドごとに slot 番号が定められており、以下のような形で計算をする

import crc16
key = "somekey"
slot = crc16.crc16xmodem(key.encode()) % 16384
print(slot)

ただし、この判定はマルチマスター形式で起動している時に意味があるようで、Master node が一つしかない場合は上述の通り 0~16383 となる(全ての key の処理を受け付ける)

実際の挙動としては、接続したノードにコマンド実行時に、そのノードがスロットを保持している場合はそのノードで処理が行われ、別ノードがスロットを保持している場合は MOVEDコマンドで自動的にリダイレクトされる。自前でCRCの値を計算し、自分で該当ノードに接続しに行く必要はない。
(HOP数が増えるのを嫌って、自分で計算をし直接サーバーに接続するようなクライアント実装はあり得る)

ノード情報

ノード情報について、フォーマットが独自のものになっているが、基本的にノードごとに以下の情報が返却されている模様。

key value
id ノードのID
tls-port TLS 接続する際の port 番号
ip ノードのIPアドレス
endpoint 接続先 EndpointのDNS情報。
hostname
role 役割。master or replica
replication-offset どこまでレプリケーションされているかの位置。MySQLのRelay_Log_Pos に近いもの?
health 生死確認。生きてる場合は online
コマンドの実行

接続したノードにコマンドを実行する。接続したノードがコマンド実行対象の場合は、そのまま処理が行われる。例えば書き込み処理(SETなど)をReplica上で実行したりした場合は、適切なノードへのリダイレクト処理が行われる。

replica:6379> GET "test.timestamp"
-> Redirected to slot [9137] located at master:6379
"test"

シングルマスター構成で複数のReplicaがぶら下がっている場合、基本的にコマンドの処理は全て同じ Master で処理がされる。そのためReplicaに接続時にコマンドを実行すると、上記のようにリダイレクトされる。

Redisの内部的には、MOVED という応答が返却されている。
https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/#moved-redirection

MOVED 9137 master:6379

MOVED 応答が来た場合は該当ノードに移動した上で、同じ処理(CLUSTER SHARDS 再取得)を行なった上で元々実行したかったコマンドを実行することになる。

これらの手順については、基本的にRedisのクライアントが制御をしてくれるため、実装時に実装者が意識する必要はないと思われる。

クライアント実装によっては、すでに取得済みのスロット番号やRole(Master/Replica)の情報をもとに、直接該当のサーバーに接続しコマンドを実行するケースもある(python の RedisCluster など。Movedが帰ってきた時にCLUSTER SHARDS を実行してキャッシュを更新)

ここまでのまとめ

クラスターモードとして構成されている Redis では CLUSTER SHARDS というコマンドにより、クラスターの構成状況について取得することができるようになっている。各種Redisクライアントは、クラスターモード動作時はこのコマンドの結果をもとに接続先の判断などを行っている。

そのため、もし CLUSTER SHARDS に記載されている内容と、実際のサーバーの状況に差異があると、Redisに正しく接続できなくなる可能性がある。

ダウンタイム発生の原因

端的に言うと、ElastiCache for Redis の CLUSTER SHARDS コマンドの結果と、実際の DNS の登録状況に乖離が発生していることがダウンタイム発生の原因となっている。

ElastiCache for Redis においては、クラスターモード向けに clustercfg と呼ばれる Endpoint が提供されている。
(e.g. clustercfg.some-redis.abcdef.apne1.cache.amazonaws.com)

実態としては、Master / Replica のノード全ての IP アドレスが A レコードで関連づけられている。

$ dig clustercfg.some-redis.abcdef.apne1.cache.amazonaws.com
(snip.)
;; ANSWER SECTION:
clustercfg.some-redis.abcdef.apne1.cache.amazonaws.com. 8 IN A	172.16.8.20
clustercfg.some-redis.abcdef.apne1.cache.amazonaws.com. 8 IN A	172.16.9.6
clustercfg.some-redis.abcdef.apne1.cache.amazonaws.com. 8 IN A	172.16.10.31

Master / Replica には上記とは別名で、ノード固有の DNS レコードも登録されている。
CLUSTER SHARDS コマンドで返却されるノード情報には、ノード固有のもの が返却される。
(e.g. some-redis-001.some-redis.abcdef.apne1.cache.amazonaws.com)

AWS ElastiCache では、互換モードからクラスターモード移行の際に、ノード固有の DNS レコードについて名前の変更が行われる(削除〜追加)。

互換モード例 クラスターモード例
some-redis-001.some-redis.abcdef.apne1.cache.amazonaws.com some-redis-0001-001.some-redis.abcdef.apne1.cache.amazonaws.com

DNSレコード側の変更(削除〜追加)が行われている期間、その変更内容が Redis の CLUSTER SHARDS の返却内容には反映されないため、情報の乖離が発生する。
情報が乖離している状態で Redis ノードに接続しようとしても、その情報はDNSに登録されていないので名前解決ができず、接続エラーとなる。

回避策

CLUSTER SHARDS のノード情報、ならびに MOVED 応答で返却されるノード情報が DNS レコードになっているため、名前解決の問題が発生する。
ただし、ElastiCache for Redis の挙動として、互換モード → クラスターモード移行時に、基本的にノードの作りかえは行われず、IPアドレスも変更されない。

なので、返却される情報が IP アドレスとなれば名前解決の問題は発生しないため、その対応を行うことで回避ができる。

cluster-preferred-endpoint-type の設定 (ip に変更)

Redis の Parameter のうち cluster-preferred-endpoint-type というパラメータを設定すると、以下の情報が IP アドレスで返却されるようになる。

  • CLUSTER SHARDS コマンドで返却されるノード情報
  • MOVED 応答で返却されるノード情報

https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/ParameterGroups.Engine.html#ParameterGroups.Redis

This value controls what endpoint is returned for MOVED/ASKING requests as well as the endpoint field for CLUSTER SLOTS and CLUSTER SHARDS. When the value is set to ip, the node will advertise its ip address. When the value is set to tls-dynamic, the node will advertise a hostname when encryption-in-transit is enabled and an ip address otherwise.

こちらの設定を行うことで、互換モードからクラスターモード移行時にダウンタイムが発生しなくなる。

なお、 Redis に対する TLS 接続を有効にしている場合は、このパラメータのデフォルト値は tls-dynamic となる。
TLS 接続を無効としている場合のデフォルト値は ip

このことが、 「通信暗号化(tls接続)設定時にダウンタイムが発生」 する理由となる。

変更手順

ここでは、非クラスターモード(クラスターモード無効)のRedisを、最終的にクラスターモードに移行をする前提で手順を記載する。

クラスターモードと非クラスターモードでは設定するパラメータグループについて別のものにする必要がある。

端的には以下の設定が行われているパラメータグループでないと、クラスターモードならびに互換モードの Redis には設定することができない。

name value
cluster-enabled yes

そのため、ここでは以下の手順を記載

  1. Redis Cluster Mode 向けの独自のパラメータグループを作成
  2. パラメータの設定を変更
  • cluster-enabled: yes
  • cluster-preferred-endpoint-type: ip
  1. 1,2で設定したパラメータグループを、非クラスターモード → 互換モード移行時に設定する

1. パラメータグループを作成

パラメータグループの名前は任意

aws elasticache create-cache-parameter-group \
    --cache-parameter-group-name "redis-cluster-parameter" \
    --cache-parameter-group-family "redis7" \
    --description "Redis parameter group"

2.パラメータの設定を変更

aws elasticache modify-cache-parameter-group \
    --cache-parameter-group-name "redis-cluster-parameter" \
    --parameter-name-values \
        ParameterName=cluster-enabled,ParameterValue=yes \
        ParameterName=cluster-preferred-endpoint-type,ParameterValue=ip

設定したパラメータグループを、非クラスターモード → 互換モード移行時に Redisに設定する

注意点

互換モード変換後、パラメータグループの差し替えは行えない。
(設定したパラメータグループのパラメータ変更は可能)

また、互換モード・クラスターモードの Redis には「cluster-enabled=yes」が設定されているパラメータグループのみ適用可能になる。

なので基本的には、クラスターモード用のパラメータグループを用意し、非クラスターモード→互換モード変換時にパラメータグループを差し替えるのが良いと思われる。

リスク

目立ったリスクは存在しないが、デフォルトで cluster-preferred-endpoint-type が tls-dynamic になっている理由は以下が考えられる。

  • ノードの IP アドレス変更時の影響
    • IPを直参照していると、ノードIP変更時にクライアント側のキャッシュを参照して接続→エラーが発生するリスクがある

クラスターモードへの移行作業が完了した際には cluster-preferred-endpoint-type を tls-dynamic に速やかに戻すのが無難と思われる。

まとめ

現行の AWS ElastiCache for Redis では、非クラスターモードからクラウスターモードに切り替えようとすると数分間の名前解決エラー(ダウンタイム)が発生する。

Redis のパラメータ cluster-preferred-endpoint-typeip に切り替えることで、移行時のダウンタイムを回避することができる。

Discussion