必見の記事

Redisに耐久性が加わったAmazon MemoryDB for Redisが登場

2021.08.20

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

人気のインメモリデータベースRedisを使うと、読み書きをミリ秒未満で処理できるため、パフォーマンスが求められるシステムのキャッシュレイヤーで重宝されます。 一方で、AOFファイルのような永続化の仕組みは存在するものの、プライマリノードで障害が起こると、データロストが起こりうるという問題を抱えており、永続的なデータストアには向いていません。

AWSが提供する Amazon ElastiCache for Redis でも同じです。

この課題を解決するのが、今回発表された Amazon MemoryDB for Redis です。

Introducing Amazon MemoryDB for Redis – A Redis-Compatible, Durable, In-Memory Database Service | AWS News Blog

トランザクションログは複数AZに保存され、書き込みに成功したデータのみが読み取れるため、整合性のある読み込みをサポートします。 ノード障害やフェイルオーバーが発生しても、データロストすることはありません。

図は公式ページから

Amazon MemoryDB for Redis の特徴

  • Redis互換のAPI
  • 書き込み時のトランザクションログは複数AZに分散。データロストなし
  • レイテンシーは読み込みがミリ秒未満、書き込みが1桁ミリ秒(通常のRedisは書き込みもミリ秒未満)
  • 読み込みは 470K回/秒、書き込みは 100K回/秒
  • スループットは読み込みが 1GB/s、書き込みが100 MB/s

やってみた

Amazon MemoryDB For Redis を起動してみます。

非常に大事な点として、現時点では東京リージョンでは利用できず、以下のリージョンでしか提供されていません。

  • 北部バージニア
  • ダブリン
  • ムンバイ
  • サンパウロ

AWSコンソールから、「Amazon MemoryDB For Redis 」に移動します。

「Create cluster」からクラスターを作成します。

クラスターの構築方法は、ElastiCache とほぼ同じです。

MemoryDBはElastiCacheで言うところの「クラスターモード」しか存在しないため、シャード数とレプリカ数を指定します。

クラスターのステータスが"Available" になったら、 「Configuration endpoint」のエンドポイントに Redis クライアントから接続します。

今回はEC2にRedis CLIをインストールして接続しました。

$ sudo amazon-linux-extras install redis6
$ HOST=foo.xxx.clustercfg.memorydb.eu-west-1.amazonaws.com
$ redis-cli -h $HOST -c
172.31.10.170:6379> ping
pong
172.31.10.170:6379> set foo bar
172.31.10.170:6379> get foo
bar

ElastiCacheとMemoryDBのベンチマーク

2シャードx3ノード/シャード=合計6ノードのクラスター(r6g.large)を ElastiCache/MemoryDB それぞれで構築し、EC2内(t3.large)から redis-benchmark で簡易的にベンチマークを行いました($ redis-benchmark -q -n 100000 -h $HOST --cluster)。

ドキュメントの通り、ElastiCacheは読み書きともに1msec未満で高速なのに対して、MemoryDBは書き込みが非常に遅いです。

すべての書き込み系コマンドが遅いわけではなく、集合系(SADD/ZADD等)コマンドのように高速に動作するものもあります。 リスト操作(LPUSH/LPOP等)が苦手だったり、 INCR のようにインクレメントするだけのシンプルなコマンドも低速なのは興味深いですね。

ノードタイプ

現時点では、メモリ最適化のGraviton2の r6g 系だけが提供されています。

ノードタイプ vCPU メモリー (GiB) ネットワークパフォーマンス (Gbps)
db.r6g.large 2 13.07 〜10
db.r6g.xlarge 4 26.32 〜10
db.r6g.2xlarge 8 52.82 〜10
db.r6g.4xlarge 16 105.81 〜10
db.r6g.8xlarge 32 209.55 12
db.r6g.12xlarge 48 317.77 20
db.r6g.16xlarge 64 419.09 25

ノードタイプのプリフィックスは RDS/Aurora と同じく「db」です。 ElastiCacheの「cache」ではありません。

利用費

MemoryDB の利用費は

  • インスタンス
  • 書き込み量($0.20/GB) ※全リージョン固定

の2本立てです。

MemoryDB/ElastiCache/Aurora 間でバージニア北部のオンデマンド利用費を比較します。

インスタンスタイプ サービス $/Hour
cache.r6g.large Elasticache 0.206
db.r6g.large Aurora 0.260
db.r6g.large MemoryDB 0.309

MemoryDB はElastiCache の約1.5倍、Aurora の約1.2倍と若干高価です。

耐久性を重視するMemoryDBはElastiCacheで言うところの「クラスターモード」しか存在しないため、{シャード数} x {ノード数/シャード} x {インスタンス利用費} 分の利用費が発生する点にもご注意ください。

最後に

Redisを永続的なデータストアとしても使える Amazon MemoryDB for Redis が爆誕しました。

データ耐久性のトレードオフとして書き込み速度は低下したものの、読み取りの速さはまさにRedisです。

クライアントはRedisコマンドを投げるだけで、MemoryDBが良しなにやってくれるため、使い勝手が良さそうです。 今後はDynamodB+DAXの代替として検討したり、RDB+キャッシュRedisなシステムを MemoryDB に集約するといったケースが増えるかもしれません。

Amazon MemoryDB for Redis のネーミングから、MemoryDB シリーズはラインナップが今後増えていくと思われます。 次のデータベースエンジンが何なのか、気になるところです。

それでは。

参考

付録

redis-benchmark のベンチマーク結果です。

ElastiCache

$ redis-benchmark -q -n 100000 -h $EC --cluster
Cluster has 2 master nodes:

Master 0: 1d65e0ae87105d66acb8e619eb3f736a842a89f4 172.31.18.117:6379
ERROR: ERR unknown command `CONFIG`, with args beginning with: `GET`, `save`,
ERROR: failed to fetch CONFIG from 172.31.18.117:6379
WARN: could not fetch node CONFIG 172.31.18.117:6379
Master 1: de0e23d31a3a380ce0ae462c8ee315360dedea1a 172.31.8.77:6379
ERROR: ERR unknown command `CONFIG`, with args beginning with: `GET`, `save`,
ERROR: failed to fetch CONFIG from 172.31.8.77:6379
WARN: could not fetch node CONFIG 172.31.8.77:6379

PING_INLINE: 79681.27 requests per second, p50=0.271 msec
PING_MBULK: 79428.12 requests per second, p50=0.455 msec
SET: 79617.83 requests per second, p50=0.471 msec
GET: 79681.27 requests per second, p50=0.255 msec
INCR: 79808.46 requests per second, p50=0.247 msec
LPUSH: 99601.60 requests per second, p50=0.255 msec
RPUSH: 99700.90 requests per second, p50=0.271 msec
LPOP: 99601.60 requests per second, p50=0.271 msec
RPOP: 99601.60 requests per second, p50=0.255 msec
SADD: 66269.05 requests per second, p50=0.455 msec
HSET: 79744.82 requests per second, p50=0.255 msec
SPOP: 79617.83 requests per second, p50=0.327 msec
ZADD: 79681.27 requests per second, p50=0.447 msec
ZPOPMIN: 79491.26 requests per second, p50=0.319 msec
LPUSH (needed to benchmark LRANGE): 79681.27 requests per second, p50=0.407 msec
LRANGE_100 (first 100 elements): 44247.79 requests per second, p50=0.519 msec
LRANGE_300 (first 300 elements): 18921.48 requests per second, p50=1.359 msec
LRANGE_500 (first 500 elements): 12383.90 requests per second, p50=1.919 msec
LRANGE_600 (first 600 elements): 10441.68 requests per second, p50=2.247 msec
MSET (10 keys): 79681.27 requests per second, p50=0.463 msec

MemoryDB

$ redis-benchmark -q -n 100000 -h $MDB --cluster
Cluster has 2 master nodes:

Master 0: ae6fc4a323b891776433048ee44fcf728a097097 172.31.10.170:6379
ERROR: ERR unknown command `CONFIG`, with args beginning with: `GET`, `save`,
ERROR: failed to fetch CONFIG from 172.31.10.170:6379
WARN: could not fetch node CONFIG 172.31.10.170:6379
Master 1: c80783804e67afc9a25039fa26f77bb5773a5947 172.31.4.127:6379
ERROR: ERR unknown command `CONFIG`, with args beginning with: `GET`, `save`,
ERROR: failed to fetch CONFIG from 172.31.4.127:6379
WARN: could not fetch node CONFIG 172.31.4.127:6379

PING_INLINE: 66357.00 requests per second, p50=0.559 msec
PING_MBULK: 66401.06 requests per second, p50=0.543 msec
SET: 15923.57 requests per second, p50=2.999 msec
GET: 66445.18 requests per second, p50=0.551 msec
INCR: 15931.18 requests per second, p50=2.991 msec
LPUSH: 15951.51 requests per second, p50=2.999 msec
RPUSH: 15943.88 requests per second, p50=2.983 msec
LPOP: 15918.50 requests per second, p50=3.015 msec
RPOP: 15941.34 requests per second, p50=2.991 msec
SADD: 66313.00 requests per second, p50=0.551 msec
HSET: 15938.79 requests per second, p50=3.007 msec
SPOP: 66006.60 requests per second, p50=0.551 msec
ZADD: 66401.06 requests per second, p50=0.551 msec
ZPOPMIN: 66357.00 requests per second, p50=0.551 msec
LPUSH (needed to benchmark LRANGE): 15948.96 requests per second, p50=2.991 msec
LRANGE_100 (first 100 elements): 44228.22 requests per second, p50=0.719 msec
LRANGE_300 (first 300 elements): 18018.02 requests per second, p50=1.431 msec
LRANGE_500 (first 500 elements): 12402.33 requests per second, p50=2.063 msec
LRANGE_600 (first 600 elements): 10734.22 requests per second, p50=2.375 msec
MSET (10 keys): 14762.33 requests per second, p50=3.103 msec