ElastiCacheのリーダエンドポイントを試してみた! #reinvent

こんにちは(U・ω・U)
AWS事業部の深澤です。

皆さん、ElastiCacheはお好きですか。僕は大好きです。

さて、僕はそんなラスベガスで開催されたre:Invent 2019にて「Whatʼs new with Amazon ElastiCache」というセッションに参加してきました。

今回はここで紹介されていたリーダエンドポイントについて、以下の観点で調査を行ってみました。

  • ロードバランシング
  • 接続中にノード障害が発生した場合の挙動
  • プライマリインスタンスに障害が発生した際に発生するリーダエンドポイントへの影響

まずは環境を準備した後、上記の観点について検証を行っていきたいと思います!!

リーダエンドポイントの特徴をおさらい

  • Redisのみ有効
  • クラスターモードを無効にして使用する
  • 全ての読み取り処理をエンドポイントに集約
  • リードレプリカ(ノード)間で接続をバランシングする
    • クライアントからは意識せず、ノードの追加/削除が行える

注意

今回はredis-cliを用いて検証を行いました。実際の運用でElastiCacheを利用される際にはクライアントツール(モジュール)以外に様々な要素が影響してくるかと思います。今回の検証結果を踏まえて実際に取り入れられる際には改めて実環境上で試験されることを推奨します。

環境準備

クライアントホスト

今回は同一VPC上にEC2インスタンスを作成して、redis-cliを用いることで検証を行いました。AMIには本記事を執筆した現在(2020/01/06)で最も最新のAmazon Linux 2 AMI (HVM), SSD Volume Type AMIを用いました。

redis-cliのインストール

以下のコマンドでインストールしました。

$ sudo yum --enablerepo=epel install redis

リーダーエンドポイントを持つRedisの作成

リーダーエンドポイントはクラスタモードを無効にしたRedisを立ち上げることで作成されます。今回は以下の画像のように「クラスターモードが有効」のチェックを外して作成しました。

実際にクラスターが立ち上がると、リーダーエンドポイントが作成されていることが分かります。

今回構築したクラスターノードの内訳は以下の通りです。

  • reader-endpoint-sample-001(primary)
  • reader-endpoint-sample-002(replica)
  • reader-endpoint-sample-003(replica)
  • reader-endpoint-sample-004(replica)

サンプルデータの挿入

先ほど立ち上げたEC2インスタンスにSSHでログイン、以下のコマンドを用いてサンプルデータの挿入を行いました。

$ echo -e "set key1 value1 \r\n set key2 value2 \r\n set key3 value3" | redis-cli -h reader-endpoint-sample.m6rlwr.ng.0001.apne1.cache.amazonaws.com
OK
OK
OK

これらがレプリカノードに保存されているかを確認します。

$ echo -e "get key1 \r\n get key2 \r\n get key3" | redis-cli -h reader-endpoint-sample-002.m6rlwr.0001.apne1.cache.amazonaws.com
"value1"
"value2"
"value3"
$ echo -e "get key1 \r\n get key2 \r\n get key3" | redis-cli -h reader-endpoint-sample-003.m6rlwr.0001.apne1.cache.amazonaws.com
"value1"
"value2"
"value3"
$ echo -e "get key1 \r\n get key2 \r\n get key3" | redis-cli -h reader-endpoint-sample-004.m6rlwr.0001.apne1.cache.amazonaws.com
"value1"
"value2"
"value3"

以上で準備は完了です。

リーダエンドポイントの確認

ロードバランシング

さて今回はこのようなshellスクリプトを用いて検証を行いました。

$ cat loop_connection.sh
#!/bin/bash

for ((i=0 ; i<900 ; i++))
do
  redis-cli -h reader-endpoint-sample-ro.m6rlwr.ng.0001.apne1.cache.amazonaws.com get key1
done

レプリカ数は3台なので900回レプリカエンドポイントに接続した場合、均等にバランシングされるのかを検証したものです。どのノードに着弾したのかはCloudwatchメトリクスの CacheHits を用いて確認を行いました。結果は以下のようになりました。


この内、reader-endpoint-sample-001はprimaryなので、リーダエンドポイントからのアクセスが着弾していないのは想定通りでしたがやや負荷がreader-endpoint-sample-004に偏っているように見えます。恐らくアクセスが早すぎて捌ききれなかったのかと想定し先ほどのスクリプトを以下のように修正して検証し直してみました。

$ cat loop_connection.sh
#!/bin/bash

for ((i=0 ; i<900 ; i++))
do
  redis-cli -h reader-endpoint-sample-ro.m6rlwr.ng.0001.apne1.cache.amazonaws.com get key1
  sleep 1
done

結果は次の通りです。
いい感じに負荷が分散されましたね。この結果から急激なアクセスがあった場合には片方のノードにアクセスが偏る可能性があることが分かりました。ちなみに先ほどのredis-cliコマンドですが、どのくらいの時間がかかるか計測すると次のような結果になりました。

$ time redis-cli -h reader-endpoint-sample-ro.m6rlwr.ng.0001.apne1.cache.amazonaws.com get key1
"value1"

real    0m0.120s
user    0m0.001s
sys 0m0.001s

120msで処理が完結し次の処理が回っていたので秒間10アクセスほどで分散の均衡が崩れる可能性があります。とはいえキャッシュへのアクセスはそれ程ノードに負担をかけないケースが一般的です。多少の偏りはありましたが実際に運用される際にはさほど問題にならないケースが多いのではと考えます。

接続中にノード障害が発生した場合の挙動

では次に接続中にノードに障害が起こった場合にはどうなるのでしょうか。まずは以下のようにエンドポイントに接続を行ってみます。

$ redis-cli -h reader-endpoint-sample-ro.m6rlwr.ng.0001.apne1.cache.amazonaws.com
reader-endpoint-sample-ro.m6rlwr.ng.0001.apne1.cache.amazonaws.com:6379>

しばらくすると、Cloudwatchメトリクスの CurrConnections に反応がありました。どうやらreader-endpoint-sample-004に接続したみたいです。

この接続を維持したまま、ノードを削除しました。すると一瞬接続が切れましたが、直ぐに接続が回復しデータの取得が行えるようになりました。

reader-endpoint-sample-ro.m6rlwr.ng.0001.apne1.cache.amazonaws.com:6379> get key1
"value1"
reader-endpoint-sample-ro.m6rlwr.ng.0001.apne1.cache.amazonaws.com:6379> get key1
Could not connect to Redis at reader-endpoint-sample-ro.m6rlwr.ng.0001.apne1.cache.amazonaws.com:6379: Connection refused
not connected> get key1
"value1"
reader-endpoint-sample-ro.m6rlwr.ng.0001.apne1.cache.amazonaws.com:6379> get key1
"value1"
reader-endpoint-sample-ro.m6rlwr.ng.0001.apne1.cache.amazonaws.com:6379> get key1
"value1"

以下のCacheHitsとCurrConnectionsの結果からみても、エンドポイント側で良しなに接続先のノードを切り替えてくれたのが分かります。

  • CacheHits

  • CurrConnections

この辺りはアプリケーション側でリトライ処理を用意しておけば心配いらなそうですね。

プライマリインスタンスに障害が発生した際に発生するリーダエンドポイントへの影響

プライマリインスタンスに障害があった場合にはレプリカノードが昇格してプライマリインスタンスになるはずですが、その際に接続が途切れてしまうことはないのでしょうか。ここでは先ほどのloop_connection.shを回しながらプライマリインスタンスがダウンしたケースを確認してみます。

$ cat loop_connection.sh
#!/bin/bash

for ((i=0 ; i<900 ; i++))
do
  redis-cli -h reader-endpoint-sample-ro.m6rlwr.ng.0001.apne1.cache.amazonaws.com get key1
  sleep 1
done

今回はプライマリをフェイルオーバーさせます。

その結果、CacheHitsに次のような反応が現れました。
reader-endpoint-sample-003へのキャッシュ利用が徐々にreader-endpoint-sample-002へ引き継がれて行っているのが分かります。ちなみに並行して書き込み処理も検証したのですが、書き込み側は当然ながらエラーとなってしまいました。ですがそのまましばらく待っている(手元で検証した結果5分ほどでした)と接続が回復することを確認しています。

OK
OK
Could not connect to Redis at reader-endpoint-sample.m6rlwr.ng.0001.apne1.cache.amazonaws.com:6379: Connection refused
Could not connect to Redis at reader-endpoint-sample.m6rlwr.ng.0001.apne1.cache.amazonaws.com:6379: Connection refused
〜〜〜
Could not connect to Redis at reader-endpoint-sample.m6rlwr.ng.0001.apne1.cache.amazonaws.com:6379: Connection refused
Could not connect to Redis at reader-endpoint-sample.m6rlwr.ng.0001.apne1.cache.amazonaws.com:6379: Connection timed out
OK

なお、今回のプライマリをフェイルオーバーした試験ですが、上記は3台数構成で行いましたが、2台構成(プライマリ1台、レプリカ1台)で行った際にも上記と同じ結果になることを確認しています。

最後に

今回はElastiCacheのリーダエンドポイントを検証してみました!結構良しなにやってくれるリーダエンドポイントにびっくりしております。これまでElastiCacheのRedisと言えば、クラスターモードを有効にして運用するのが当たり前でしたが、この新機能のお陰で選択肢が増えました。先日re:Growthでも登壇させていただきましたが、オンラインでのスケールアップ&ダウンにも対応したようなので高負荷時にも水平スケールに頼る必要はなさそうです。

今後ElastiCacheの導入を検討される際には是非、クラスターモード無効のリーダエンドポイント活用をご検討ください!

以上、深澤(@shun_quartet)でした!