ちょっと話題の記事

[システム運用] Spring Boot + ElastiCache + DynamoDBで構成されたシステムのメンテナンス -問題編-

2018.05.26

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

はじめに

こんにちは。こむろ@札幌です。

4月から部署異動し、ここ数年の大規模システムのアプリケーションからインフラまでの運用と開発経験を活かし、Prismatix の開発と運用の狭間をブリッジする役目として開発に参加しています。

周囲はとんでもなく優秀なプログラマー等がたくさんいるため、この年齢になっても新たなことを学べる環境というのは非常にありがたい次第です。自分も運用経験で気づいたことや学んだことを開発側へフィードバックすることでより安定したサービスをサポートできるよう頑張っていく所存であります(ここまで挨拶)

ということで、事業開発部では大規模なサービスの開発から運用までやっていきたいエンジニアを鋭意募集しております。

複数のServiceが絡み合うピタゴラスイッチ

様々なAWSのサービスを組み合わせることで、複雑なシステムを構成することができます。しかもそれぞれのサービスは自前でサーバーを管理する必要がないなど、非常に簡単に利用することができますね。また弊社社員はAWSサービスへの勉強に余念がありませんので、当然それぞれのサービスはどのような制限があって、どのように利用できるかは当然把握しています。

しかし、アプリケーション内部でサービスが利用されて結合されている場合、アプリケーションが利用しているサービスの制限やエラーがどのように絡み合って影響しているかを把握するのはとても難しくなっています。

それはまるで巨大で複雑なピタゴラスイッチのようになっており、あるサービスの制限が全く想像していなかった箇所で顕在化したり、サービスのエラーがアプリケーションに致命的な障害を発生させるなど、インフラ側だけではなく、アプリケーション内部でどのようにサービスを組み合わせているかを把握する必要があります。アプリケーション開発・運用側はそれぞれどのようなサービスを利用しているか、そしてそのサービスに障害が発生した場合にアプリケーションにどのような影響があるのかを、事前に把握しておく必要があります。出来れば実際にサービスの障害をシミュレートできているととても良いのですが、現実そのようなことはなかなか難しいのではないでしょうか。

現在の状況

現状を簡単に説明しておきます。アプリケーションは以下のような構成です。DynamoDBをマスターに、Spring Data RedisのCache機能を利用し、ElastiCache(エンジンはRedis)に作成したデータのキャッシュを持っている状態です。

DynamoDBに大量に登録されているアイテムにはそれぞれTTLが設定されています。そのため、このTTLに伴いElastiCacheに登録されているアイテムも同じ値でTTLが設定されています。

ここ半年にアプリケーションのAcitveユーザーの増加に伴いアイテム数が急増しています。さらに悪いことに、実はこの急増しているユーザーが生成するアイテムのTTLの期間は過去の経緯から異常に長い時間が設定されていました。

そのため、本来すでに必要なくなったアイテムはExpireされず、開放されるべき容量が解放されていません。つまりTTLが長期間であるがゆえにアイテムの入れ替えが正常に行われておらず、Activeユーザー数=Cacheのストレージ消費量となっています。そのためActiveユーザー数が増えれば増えるほど線形で消費ストレージの量が増加するという状況です。これらのデータは更新はなく、Create, Delete, ReadのAPIのみアプリケーションから提供されています(Bulkでのバッチ処理のようなAPIは提供されていません。)

すでに過去2回、ストレージ枯渇を起因とするElastiCacheサービスの無応答状態が発生しており、早急に根本的な解決が急務です。

現時点で登録されているアイテム数は160万強にのぼります。また cache.r4.2xlarge で利用可能なメモリ(50.47GB)の大半を使い切っています(ギリギリ?)

ElastiCacheを使いこなしている皆さんに改めて説明する必要もないかもしれませんが、Cacheデータの使用量がCacheクラスタで利用可能なメモリの限界に到達するとSwapが発生し、それに伴いエンジンのCPU使用率が跳ね上がります。Redisはシングルスレッドモデルであるため、スワップ等の処理が強制的に走ると、エンジンのCPU使用率が100%に到達し、処理が完了するまでそれ以外の仕事ができなくなります。当然Springアプリケーションからの接続にも応答できなくなるため、一時的にアプリケーションが不安定になります。

負荷MAX *1

本来こうしたい

急増しているアイテムのTTLを正常な値に戻してやる必要があります。本来、このサービスでは2サイズ下の cache.r4.large (12.3GB) で安定稼働していたため、このインスタンスサイズでの安定稼働することが理想です。しかし稼働当初に比べ、アクセス数は数倍に膨れ上がっているので様子を見て1サイズ下あたりがちょうどよいかもしれません。 *2

確認するメトリクスは Bytes Used for Cache になります。こちらのメトリクスで検出した値が、そのインスタンスの使用可能メモリ量の上限値まで達するとCPU負荷による無応答等が発生する可能性がありますので、こちらになるべく抵触しないある程度余裕を持った容量を確保する必要があります。

大量に作成されるデータのマスタ情報に適切なTTLを設定することで、本来キャッシュする必要のないアイテムの入れ替えが行われ、Cacheのストレージをより効率よく利用することができるようになります。結果、ElastiCacheのインスタンスサイズを必要以上に大きくする必要がなくなります。

どのように対応するか

難しいのは無停止を実現するための作業手順です。分間数万単位のアクセスがあるアプリケーションのため、DynamoDB, ElastiCacheへのアクセスはひっきりなしに行われています。本来メンテナンスの時間を確保し、停止できればよいのですが諸事情からなかなか難しいため、稼働状態のまま無停止でのメンテナンスを考えます。

元栓(つまりデータのマスタの大本)から締めていくイメージでデータを修正しないと、修正しては新しいデータが作られ・・・といったいたちごっこになりかねません。また今回はCacheも捜査対象であるため、DynamoDBの当該テーブルへのへのRead Capacity Unitをきちんと考慮する必要があります。。

データの関係性のイメージです。

これをもとに戦略を立てていきます。

  1. ユーザーのマスタTTL定義を更新する
  2. DynamoDBの中の以前のTTL定義で作成されたデータをふっとばす
  3. ElastiCacheの以前のTTL定義で作成されたデータをふっとばす

単純に考えるとこのような感じです。しかし、ElastiCacheはSpring Data Redisで利用されておりDynamoDBのマスタデータのCacheとして機能しています。そのため、DynamoDBのデータを先にクリアした場合どのような動作になるかがいまいちわかりません。(アプリケーション側の仕様としては不定)

ここで問題

さて戦略としていくつかの方法が取れると思います。前提条件として以下です。

  • データ数は200万件以下
  • 複雑な多段構成は持っていないが、1KB程度のJSON
  • DynamoDBのパーティションKeyは1つ、ソートキーはない
  • ElastiCacheのエンジンはRedis 2.8系を利用
  • ユーザーへの影響は報告済み
  • 無停止(5xxエラーが発生しない)

戦略その1

  1. DynamoDBのマスタTTL定義を更新する
  2. 以前のTTL定義で作成されたデータの格納されたテーブルのアイテムを、DynamoDB SDKを用いてスクリプトを組んで160万件DELETEする
  3. ElastiCacheのデータは2の手順によってすべて勝手に削除される(はず)

戦略その2

  1. DynamoDBのマスタTTL定義を更新する
  2. 以前のTTL定義で作成されたデータの格納されたテーブルを複製し、全く同じスキーマ・設定で空のテーブルを作成する
  3. アプリケーションのテーブルの向き先を空のテーブルへ向け直す
  4. Redis(ElastiCache)の対象DBを FLUSHDB で削除する

戦略その3

  1. DynamoDBのマスタTTL定義を更新する
  2. 以前のTTL定義で作成されたデータの格納されたテーブルを複製し、全く同じスキーマ・設定で空のテーブルを作成する
  3. パラメータグループやエンジンバージョンが同じRedisのクラスタを新たに作成する
  4. アプリケーションのCacheクラスタ、テーブルの向き先を一回で一気に変更する

戦略その4

その他

次回に続く

クラウドで構築されたスケールする大規模システムのメンテナンスで難しいのは、インフラ層のみの知識だけでなんとかなるかというとそういう事はほとんどなく、システム全体としてインフラ層、ミドルウェア層、アプリケーション層それぞれすべての影響を考慮する必要があります。要素が複雑に絡んでいるため、思わぬところでエラーが発生する可能性が非常に高いため、事前に調査すべきことが膨大です。しかしながら、これらをすべて知識を持っていてそれぞれ完マスしている人がいるならば問題ないのですが、フルスタックエンジニアと呼ばれるウィザード級ハッカーはそうそういません。そのため、安定した稼働のためには、それぞれの専門知識を持ったメンバーが集まり、チームとしてセクショナリズムにとらわれることなく、協力し合うことは必須だと思います *3

シェアいただける時には、ぜひとも自分なりの回答と懸念・必ず実施すべき事項等を記載の上シェアいただければと思います。 弊社社員につきましては 確実に要望を満たす正解を答えていただけるものと期待が高まるばかりです。回答とこの手順でやった結果こうなった、という知見の共有編は次回になります。

それでは皆様ごきげんよう。

脚注

  1. このグラフはSwapではありません
  2. 定常時で分速1万ほどのアクセス
  3. うちは出来てるのか?出来てますよね?出来てない訳がない。