bootjpのメモ帳

https://bootjp.me で書くほどではないことを

AWS提供の耐久性のあるRedis互換KVSのMemoryDBについての論文「Amazon MemoryDB: A fast and durable memory-first cloud database」のメモ

www.amazon.science

内容の正確性には十分な注意を払っていますが、正確性を保証するものではありません。

誤りがあればこちらよりご指摘いただけますと幸いです。

どんなもの?

  • Amazon MemoryDBはRedis互換のインメモリデータベース(KVS)
  • 低レイテンシーかつ高可用性を実現しつつ耐久性と強い一貫性にフォーカスしたサービス
  • 今までのRedisではデータの耐久性(永続化)と一貫性に課題があり、そこを改善したもの

先行研究と比べてどこがすごい?

Redis/Redis Clusterの課題

  • Redisでは非同期レプリケーションを用いており、プライマリーノードのクラッシュ後のフェイルオーバー時にデータが消える可能性がある

    • プライマリーノードからレプリケーションする場合に、分散合意などは行わず非同期レプリケーションを行っている
    • フェイルオーバー後プライマリーに選出されるノードがプライマリーがもっているすべてのデータを持っている保証がない
    • スナップショットとAOF(Append Only File)で障害対策はしているが、現実的な複数ノードによる構成の場合、冗長化されない
      • 複数ノードでフェイルオーバー後のプライマリーノードが以前のプライマリーノードのすべてのデータを持つ保証がないため
  • 前述の理由により、Redisでは耐久性と一貫性が欠如しており、キャッシュ以外の用途で使うことができない

  • キャッシュの構成も、耐久性があるストレージに書いたあとにキャッシュデータをRedisに書き込むという処理が必要
    • 他にはDynamoDB Streamからパイプライン経由でRedisにロードするような複雑な構成が必要となる

技術や手法のキモはどこ?

Auroraのようなスタックの分割

  • インメモリ実行エンジンと耐久性層(マルチAZトランザクションログサービス)の分離している
  • MemoryDBではAuroraのようにトランザクションログを管理するコンポーネントなどをスタックごとに複数のレイヤーに分割している
    • RedisのOSS版のソースコードをインメモリ実行とストレージエンジンとしてのみ使っている
    • RedisのレプリケーションストリームをマルチAZのトランザクションログストレージにリダイレクトすることで耐久性を確保している
      • 書き込みには複数AZに書き込みが行われるまでブロックされる
      • なんらかの理由でトランザクションログストレージに書き込みができなかった場合はエラーが返される
        • ネットワーク分断など
  • 変更を伴う命令はトラッカーにkeyが保存されたうえで、トランザクションログストレージにコミットされるまでブロックされる
    • 変更中であっても非変化系の操作はブロックされない
    • しかし、非変化系の操作であってもトラッキングされている(変更中の)keyを含む場合はトランザクションログストレージがあるまでブロックされる

Redisの非決定論的な操作に対するアプローチ

  • Redis はSPOPのような集合(SET)からランダムな要素を削除するような処理がある
  • Lua Scriptのような実行後に操作が確定するものもある
  • そのような非決定論的な操作を決定論的な操作としてトランザクションログに保存する必要があった
  • MemoryDBではWAL (Write-Ahead Log)ではなくWBL(Write-Behind Log)を採用した
    • これによりRedisのエンジンに操作を適用した結果を決定論的なトランザクションログとして出力することが可能になった。

トランザクションストレージを用いたリーダー選出

  • トランザクションログストレージ上に構成されたリーダー選出とリースシステムによる故障時の強い一貫性の保証
  • リーダーの獲得にはトランザクションログストレージに特定のログエントリを書き込む必要がある
    • リーダーの獲得のエントリに書き込みを行うには、トランザクションログストレージにあるデータをすべて持っている必要がある
      • この制約を課すことで十分なデータを持たないノードがリーダーに昇格することを防いでいる
      • 仮にネットワーク分断から復帰したノードがリーダーになろうとしても失敗する
  • リーダーを持つプライマリーノードはトランザクションログストレージにリースに関する書き込みを行う
  • リースに関する書き込みを観測したレプリカノードはその時間を超えるバックオフ時間を持ち、その間はリーダー選出を行わない
  • バックオフ期間を超えてもリースに関する書き込みが観測されなければ、リーダー選出を開始する

オフボックス・スナップショットシステム

  • RedisではforkによるCoWを用いつつ、別のプロセスでスナップショット(RDB)を作成する
  • しかし、これは顧客にとって次のデメリットがある
    • fork時にはメモリ使用量が増大し、通常の2倍のメモリ容量が必要になる
    • スナップショット作成中はIO性能が低下するため、レイテンシーが悪化する
    • IO以外にもスナップショット作成にはCPUリソースを使用する
  • MemoryDBではこの課題に対する対応として、顧客に見えない一時的なクラスタを追加しスナップショットを作成することで解決
  • スナップショットはS3に保存される
  • 新たなノードの追加時にはS3のスナップショットとトランザクションログストレージにあるスナップショットからの差分を用いることで高速にノード追加される

どうやって有効だと判断した?

MemoryDB の耐久性にかかるオーバーヘッドを評価することを目的に評価を行った

評価手法

  • 読み取り専用ワークロード:各クライアントがバックツーバックでGETリクエストをRedisサーバに送信(パイプライニングなし)
  • 書き込み専用ワークロード:SETコマンドを使用する
  • 読み取り・書き込み混合ワークロード:リクエストの80%がGET、20%がSET

評価結果

スループット

  • 読み取り専用ワークロード
    • r7g.2xlarge未満ではRedis/MemoryDBとも約200K Op/sを実現
    • 2xlarge以上ではMemoryDBが約500K Op/s、Redisは最大約330K Op/sに留まる
  • 書き込み専用ワークロード
    • Redisは全インスタンスで約300K Op/sを実現
    • MemoryDBはマルチAZトランザクションログコミットにより約185K Op/sに留まる
    • 条件次第では単一シャードで最大100 MB/s到達例もあり
  • 混合読み書きワークロード
    • 読み取りスループットに近い性能を維持
  • オフボックス方式
    • スナップショット中もメインクラスターのスループットへの影響は観測されない

レイテンシー

  • 読み取り専用ワークロード
    • r7g.16xlargeで中央値サブミリ秒、p99は2 ms未満
  • 書き込み専用ワークロード
    • Redis:中央値サブミリ秒、p99約3 ms
    • MemoryDB:中央値約3 ms、p99約6 ms
  • 混合読み書きワークロード
    • 両者とも中央値サブミリ秒、Redis p99約2 ms、MemoryDB p99約4 ms
  • オフボックス方式
    • 平均レイテンシ約1 ms、最大10–20 msにとどまり安定

検証

  • TLA+/P言語による形式手法および線形化チェッカーPorcupineを用いて、システム全体の正確性と信頼性を裏付けた

議論はある?

  • トランザクションログストレージに永続化する際のレイテンシーが載るため書き込み専用ワークロードに関してはレイテンシーが高くなる

次に読むべき論文は?

他のメモ

コントロールプレーン

  • 顧客のプロビジョニング要求に応じ、EC2インスタンスやマルチAZトランザクションログのプロビジョニング、ノードの構成を自動化
  • 各ノードを顧客のVPCにアタッチし、安定したDNSエンドポイントの提供、必要に応じたTLS証明書の発行、アクセス制御リスト(ACL)のプッシュなどとクラスタ全体の管理

監視

  • 外部のモニタリングサービスが各ノードに対して定期的なポーリングを実施し、クラスタ全体の接続状況や状態を管理
  • 各ノードは内部ゴシッププロトコルで相互のヘルスチェックを行い、それによって迅速な故障検出やネットワーク状態の把握を行っている
  • 故障が発見された場合には、適切なリカバリー措置(ノードの再起動やハードウェア交換など)が自動的に実行される

ノードのアップグレード(ローリングN+1アップグレード)

  • アップグレードは直接旧ノードを停止するのではなく、新バージョンのノードを新たにプロビジョニングすることで、常に全ノードがトラフィックを処理できる状態を維持
  • アップグレード時、まずレプリカが新バージョンにアップグレードされ、その後リーダー(プライマリ)が最後に切り替わるため、読み取りスループットや可用性に対する影響を最小限に抑制
  • さらに、アップグレード中のバージョン混在による不整合のリスクに対しては、エンジンが生成するレプリケーション・ストリームにバージョン情報を付与し、古いバージョンのノードが新しいバージョンのストリームを誤って解釈しないよう保護機構を適用