現在、弊社にて提供しているサービスのほぼ全てにおいてAWS(Amazon Web Service)を利用しており、その中でも頻繁にアクセスが必要なデータについてはElasticCacheのRedis/Memcachedを利用しています。今回は、そのElasticCacheでRedisを利用する場合において、弊社の運用で発生してしまったトラブルと対応方法について紹介させていただきたいと思います。
TL;DR
AWS ElasticCacheのRedis ServerのDefaultのパラメータ設定では、Connection Timeoutの設定がされていないので、そのまま放置するとConnection数が溜まり続けて危険ですので、ConnectionのTimeout設定を行いましょうというお話になります。
今回発生した問題
Blue-Green Deploymentを多用して何度もデプロイをしながら運用していましたが、ある時に新しく起動したサーバーすべてが大量にエラーを吐き出すようになってしまい、処理が正常にされなくなりました。また、今まで動作していたサーバーも時おりエラーを吐き出すような状況に陥りました。
原因
調査したところRedis Serverに対してConnectionを確立しようとして失敗していることが原因でした。Redis側のConnection数を調べたところ上限の65000に到達し、それ以上にConnectionを生成しようとしている状況でエラーが発生し、RedisのConnectionのtimeoutの設定もされていない状態(AWS ElasticCacheのデフォルト設定)であったために、不要なConnectionが大量に残った状態となり、処理ができなくなっていました。
運用の仕組みの概要
- システム概要 データの格納にはMySQL Databaseを利用していましたが、利用頻度の高いデータ等はElasticCacheのRedisを利用しています。
- Redisへの接続 アプリケーション・サーバーからRedis Serverへの接続は、hiredis(https://github.com/redis/hiredis)を利用しています。
- デプロイ方法の概要 当時デプロイをする場合、Hotdeployだとdeployした直後のプロセスの切り替え時にシステムが安定しないという状況があり、 サービスを利用しているユーザーの方々に与える影響が大きいということで、Blue-Green Deploymentシステムを利用していました。 1系統が本番で使用中の場合、もう一方の系統にデプロイして準備ができたタイミングでElastic Loadbalancer配下のマシーンを切り替えるという方法です。 ※ 現状は問題を解消し、Hotdeployを取り入れています。
問題点
技術的な問題
- redisの問題
- ElasticCacheのCache Parameter Groupの認識不足 Elastic CacheのRedisのデフォルトの設定(Cache Parameter Groups)だと、Connectionのtimeoutが0に設定されています。サーバー起動時に明示的に指定しないとDefaultのパラメータを使用してサーバーを構築しますが、このParameter Groupのtimeoutの設定は0になっていますので注意が必要です。
- Connection Leakの認識、対応不足 Redisのconnection timeoutの設定が無効の状態になっていると、長時間Idle状態のConnectionであってもConnectionは確立されたままの状態となってしまいます。その状態で接続元のサーバーがConnectionを明示的にClose処理することなしにShutdownされてしまうと、
- hiredisの問題
- サーバーのShutdown時の問題 hiredisのclient libraryが、シグナルのハンドリングについては対応していないために、Redisとの間に確立されたConnectionは誰も使用されないのに存在した状態のまま残ってしまっていたために今回の問題が発生してしまいました。
運用の問題
- Blue-Green Deploymentの利用時の考慮不足 Blue-Green Deploymentを利用する場合、サーバーの起動/停止が頻発します。更にサーバーを停止する場合に、Load Balancerから切り離された後そのままマシーンを停止するという処理をすると思いますが、その際にRedisサーバーへの接続が正常にCloseされる処理がされていない状況となっていました。
- Connection数の監視、異常検出ができていなかった Cache ServerへのConnection数の監視が適切にされていなかったために、異常な数のConnectionが残存していることを検出できていませんでした。通常時であれば数万本の同時接続数が必要になることはないので、適正値を超えた場合の異常状態の通知をしっかりやるべきでした。
対応方法
- 一時的な対応 redis-cliなどのツールを使って現在接続中のConnectionのリストを取得して、不要なConnectionをkillして残存してしまっている不要なConnectionを消去し、本番サーバーからRedis Serverに接続できずにエラーになってしまっている現象を回避しました。
- 長期的な運用を考慮した対応
- Connection Timeoutを設定します。 Connectionが正常にCloseされていない場合でも、長時間Idle状態の不要と思われるConnectionがクローズされるように設定をしておきます。 Cache Parameter Groupを新規に作成し、そのグループの中にあるtimeoutパラメータがデフォルトでは0になっているので、これを適切な値(そのシステムの負荷度合、状況に応じて決定)に設定します。
- Server停止時にRedisサーバーのConnectionを何らかの方法でクローズします。 マシーンを停止する際に、何らかの形で(/etc/rc0.d, rc6.dなど)起動するScript等を利用して、接続済みのRedis ServerへのConnectionをクローズする処理します。 (※現状この方法のうまいやり方が確立できていないために、この方法は実現できておりません。)
- 異常状態の監視、検出する対応 CloudWatchで、ElasticCacheのConnection数の監視項目を追加し、しきい値が2万程度を超えた場合にAlertメールが来るような設定を行い、異常状況を早めに検出できるようにしました。
対応時の注意
- Redis/Memcached Serverを再起動すると、サーバー内に保存していたデータは消去されます。Cache Server上のデータが消えてしまうとまずい場合には、何らかの形でCache内のデータを別のところに退避させておく必要があります。
- 現在ではElasticCacheにもSnapshotの機能が用意されています。 http://aws.amazon.com/jp/blogs/aws/backup-and-restore-elasticache-redis-nodes/
- 別のRedis Serverを起動し、そのRedis Serverにデータをコピーしておき、再起動後にそのデータを利用して元に戻します。 ElasticCacheの場合、SAVEコマンドでディスクに直接保存ができないので、EC2等の別のマシーンのRedisからSLAVEOFなどでデータをレプリケーションしたあとにSAVEを行い、 それを利用してデータを戻します。
- Parameter Groupの設定自体は再起動なしに入れ替わりますが、パラメータの設定値自体は再起動が必要です。 ※ http://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/CacheParameterGroups.html
- Cache ServerをSnapshotから再構築する場合、Endpointの値に既にCache Clusterで使用中の名称と同一の名称を指定することはできないので注意が必要です。 ※ 既存のCache Clusterの名称を変更しておけば、新規に作成するCache Clusterの名前を再利用することは可能です。
- 今回のケースでは当てはまりませんでしたが、アプリケーション・サーバーの起動停止が頻発するAutoscaleのシステムを利用している場合にも起こりやすくなる可能性があります。 http://d.conma.me/entry/2013/03/21/114044
参考文献
- AWS ElasticCacheドキュメント http://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/CacheParameterGroups.Redis.html
- Redisドキュメント http://redis.io/documentation
- Blue-Green Deployment http://www.publickey1.jp/blog/14/blue-green_deployment.html
- hiredisドキュメント https://github.com/redis/hiredis