目的
画像サーバの負荷軽減と台数削減。
前提条件
現在、サーバは物理のみで構成されておりクラウドは利用できない。
※社内事情により、インフラのクラウド移行はもう少し先になる
社内事情により、CDNの利用ができない。
現在の状況
オリジンサーバ:13台
ピーク時全体平均CPU使用率90%越え
プロキシサーバの検討
varnishとnginxを検討してみる。単純にプロキシサーバとしての基本機能は変わらなさそう。
varnishは、ESI(部分キャッシュ)や細かいpurge機能が魅力であるが、今回はURLをキーとしたバイナリファイルのキャッシュのみの利用であるため、キャッシュに関しては多機能である必要はない。
プロキシサーバとしてリクエストをさばく能力や実績としてはnginxの方が良いと感じnginxを採用。
キャッシュ方法
1、nginxのproxy_cache
キャッシュをストレージへの保存(HDD or SSD)となる。
しかし、iowaitの発生でCPU負荷orロードアベレージが上げる懸念がある。
また、SSDが利用できない社内事情がある。
せっかく実装を終えても負荷が高くなり、メモリにキャッシュするという方針に変わることを想定すると
はじめからメモリにキャッシュする方法を選択したほうが良い。
メモリに保存したい場合は、nginxにtmpfsというメモリ上に仮想的なファイルシステムを実現できる機能がある。
キャッシュを明示的に削除する方法が必要だが、proxy_cacheに対するpurgeモジュールを利用することで
指定のキャッシュを削除することができる。
懸念点
当社システムの事情で、元画像の更新が日々頻繁に行われる。(同じ画像ではなく別の各画像が更新される)
複数ユーザーが同時に大量の画像を更新することを想定すると、アプリ側からキャッシュの削除指示をhttpで行うことはプロキシサーバへのコストが高いと考えられる。
また、今後プロキシサーバを複数台のスケールアウトすることも考えた場合、キャッシュ使用効率や整合性を保ちやすいようキャッシュを共有できた方が良い。
そのため、プロキシサーバ内にキャッシュ保存することは避けたい。
2、KVSでバイナリキャッシュ
memcachedかredisを利用する方法がある。
KVSは、キャッシュサーバの再起動が必要となった際にキャッシュが消えてしまう問題がある
Redisでは永続化ができるが、障害などでサーバが停止して起動するまでの間に元画像の更新があった場合、リストアしたキャッシュとの整合性が取れなくなる。
キャッシュ期間分だけ整合性がとれない状況が生まれ問題となる。
元画像の更新と同時にキャッシュを削除しなければいけない要件があるため、永続化は不要となる。
また、一時的にキャッシュが消えることでバーストする問題もあるが、先ほど述べた整合性の問題は無視できないため致し方ないと考える。
(キャッシュ生成まで一時的にプロキシサーバが高負荷になるが仕方ない)。合わせて、画像サーバを削減した後で、キャッシュが動作しないことでサイトが高負荷な状態となるため、この件についてはプロキシキャッシュサーバを2台にするなどを検討中である。
redisとmemcachedどちらでも良いといえば良いのだが、下記理由でmemcachedを採用した。
・単純なset/getのみであればredisよりmemcachedの方がパフォーマンスが良いというベンチマークの記事が多い。
・永続化不要
・単純なバイナリキャッシュのみの利用
・マルチスレッド設定が簡単にできる(redisでは同じmasterをマルチプロセスで見る場合、クラスタ化する必要がある)
1の懸念点に対して2で解決できそう?
・削除指示をhttpリクエスト → TCPコネクション
コネクションプールするなどして接続コストを抑えれば、大量の削除に対するコストが抑えられそう。
少なくともnginxへの負担はなくなる。
2の場合であれば、元画像更新時にキャッシュの削除ではなく、キャッシュの更新を行うことが可能。
1の場合だと元画像更新時に削除するリクエストとキャッシュを生成するためのリクエストの2回処理が必要となる。
また、社内事情から画像の種類によってキャッシュ期間を細かく設定するケースも出てくる可能性もあり、
KVS利用の方が何かとキャッシュをコントロールする上で利便性があると感じた。
キャッシュの削除
1、アプリ内からキャッシュの更新
元画像の更新処理が実行される際に、memcachedのキャッシュデータを更新する。
当社のレガシーシステムではアプリ側がperlで動作しているためCPANモジュールの「Cache::Memcached」を利用する。
2、URLから削除
整合性が取れないなど、キャッシュをピンポイントで意図的に削除したい場合に備え、削除用のURLを叩けばキャッシュが削除できるようにする。ただし、社内の人間からのみ削除可能するためIP制限をする。
3、LRUでの削除
CACHESIZEを超えた時点で古いものから自動削除される。
nginxとmemcachedの連携
ngx_http_memcached_moduleを利用する。
導入後の改善結果
ピーク時のCPU負荷が抑えられ、平均負荷も下がりました
アクセス頻度の高い画像のみをキャッシュ対象としているため、対象外の画像のリクエストはバックエンドに流れていますが、ピークタイムに効果が発揮できました。
ピーク時
CPU使用率: 92.2% → 41.5%
プロキシキャッシュサーバの負荷
ピーク時
CPU使用率:1.0%
CPU負荷はほとんどない状態でリクエストを捌けています。nginxもmemcachedも負荷は余裕でした。
最終的に、バックエンドにある(オリジン)画像サーバは7台削減できました。