![](/images/20240124a/キャッシュ.png)
はじめに
こんにちは。TIG村瀬です。
動作が遅いサービスはストレスがたまるので利用したくないですよね。
例えばネットショッピングする際に商品ページの表示が遅かったり検索してもすぐに結果が表示されないとそのサービスを利用するのをやめてしまいたくなります。
ページの表示や検索結果を高速化する手段の1つとしてキャッシュがあります。ということで今回はぼくのかんがえたさいきょうのキャッシュ戦略に関して解説します。
キャッシュとは
キャッシュとは現金を意味するcashではなく、cacheであり、
一度読み込んだ情報を一時的に保存しておき、次回参照する際に素早く読み込めるようにする仕組み
です。
なぜキャッシュを使うのか
大別して3つ理由があります。
- システムにおける処理高速化のため
- RDBなどの負荷軽減のため
- コスト削減のため
1.システムにおける処理高速化のため
冒頭でも軽く説明した通り、何らかの処理の遅いサービスを高速化したい場合に、そのサービスを提供するために裏で動作しているシステムを高速化する必要があります。高速化の方法は一度処理した情報をキャッシュに保存し、次回参照する際に素早く読み込めるようにすることであり、書き込みや一度目の計算処理が高速化するわけではないのでそれを意識した上で設計することになります。
2.RDBなどの負荷軽減のため
よくあるシステムの構成としてバックエンドではRDBが利用されていることでしょう。RDBを利用していないシステムにおいては何か負荷の高い処理をするサーバと読み替えてください。システム全体においては、大量データを保持し、計算処理を実行するRDBの負荷が大きくなるかと思います。
負荷が大きくなると処理が遅延し、許容時間内にレスポンスが返却できなくなったり、ハングアップしてしまうことでしょう。システムの心臓部とも言えるRDBがこのような状態になってしまうとサービスの提供が困難になります。
サービスを提供し続ける為、計算済みの結果は一定時間キャッシュに保持し、計算済みの結果がある場合はRDBにアクセスすることなくキャッシュから結果を取得することでRDBに掛かる負荷を軽減することが可能です。
3.コスト削減
クラウドサービスを利用している場合、コンピューティングリソースを利用した分だけコストが掛かる料金体系になっているかと思います。
その場合にコストを低減するためにはコンピューティングに要する時間を短縮するのが効果的です。RDBの処理時間が長くクライアント側が待たされるようであれば、キャッシュにアクセスし短時間でレスポンスを受けられるようにすることでコスト削減が期待できます。
クラウドサービスを利用しキャッシュを実現するのであればそのキャッシュ自体のコストも考慮すべきではありますが、中・大規模システムであればまずキャッシュ自体のコストよりも削減できるコストやメリットの方が大きいことでしょう。
キャッシュするデータ
キャッシュするデータに相応しいのはどのようなものでしょうか?
キャッシュの特性と利用用途を踏まえると以下のようなものをキャッシュするのが良いでしょう。
- 変更がリアルタイムに必要ではないもの
- 計算処理に時間を要するもの
- 変更が発生しないまたは滅多に発生しないもの
- 参照頻度が高いもの
どこにキャッシュを配置すべきか
よくある構成を元にどこにキャッシュを配置すべきかを検討していきます。
![キャッシュ_構成.png](/images/20240124a/キャッシュ_構成.png)
クライアント
![キャッシュ_構成_クライアント.png](/images/20240124a/キャッシュ_構成_クライアント.png)
速度を最優先に考えた場合、クライアントにキャッシュを設けるのが最も効果的です。
クライアントにキャッシュがある場合にはネットワーク通信が不要のため、高速に結果を取り出すことが可能です。クライアントが何で構成されているかにもよりますが、ブラウザベースの構成でない限り、キャッシュ機能を何らかの形で用意する必要があり、キャッシュを用意しない場合と比較すると開発コストは増加します。
データをどれぐらいの期間保持するかのルールはWeb APIにてデータを取得する場合はHTTPにおけるCache-Controlに倣った保持期間するのがデファクトスタンダードかと思います。
常にキャッシュを表示するのではなく、ユーザによる簡単な操作(※)でクライアントのキャッシュを破棄し、最新情報を取得できるようにすると良いでしょう。
※スマホアプリであれば、更新ボタンのタップやリストを下にスワイプするなど
Web APIバックエンド
Web APIバックエンドにキャッシュを配置する場合、サーバの内、外の2パターンあるので分けて説明します。
Web APIサーバ内
![キャッシュ_構成_webapi1.png](/images/20240124a/キャッシュ_構成_webapi1.png)
代表的なサービスとしてはmemcachedやRedisがあります。
Web APIサーバと同一のサーバ内に設けるパターン。Web APIサーバからキャッシュまでのアクセスにおいてネットワークを介さないため高速にキャッシュから結果を取り出すことが可能です。
しかしながら一方で同一サーバ内に設けるため、Web APIサーバが複数存在する場合にキャッシュを共有できません。Web APIサーバが1台の構成であれば効率よく動作しますが、可用性を考慮すると進んで採用したい構成ではありません。またナウでヤングなコンテナを利用したい場合、コンテナの原則である1コンテナ1プロセスから逸脱するためおすすめできません。
Web APIサーバ外
![キャッシュ_構成_webapi2.png](/images/20240124a/キャッシュ_構成_webapi2.png)
Web APIサーバ外にキャッシュを設ける王道パターン。Web APIが複数台ある場合にキャッシュを共用できます。
他のキャッシュ配置と比較してキャッシュのコントロールが容易であり、例えばキーを指定した一部データの削除が行えます。
システムにキャッシュを配置するとなった際に一番最初に思いつくのがこのパターンかと思います。memcachedやRedisが利用でき、Web APIの開発チームがキャッシュをコントロールしたい場合に最適です。
RDB
![キャッシュ_構成_rdb.png](/images/20240124a/キャッシュ_構成_rdb.png)
大抵のRDBではキャッシュを内蔵しています。これにより初回のアクセスよりも2回目以降のアクセスの方がレスポンスを素早く返すことがあります。キャッシュのコントロールはRDB外からはできないため、キャッシュ設定はONにするけれど過信はしないようにしましょう。
CDN
![キャッシュ_構成_CDN.png](/images/20240124a/キャッシュ_構成_CDN.png)
クライアントでキャッシュを持たない場合に最も近い場所でキャッシュを提供できます。
サーバサイドとクライアントサイドを別のチームが開発する場合、サーバサイドのチームからするとクライアントサイドでキャッシュしてくれる保証はありません。クライアントサイドで適切なキャッシュがなされず、サーバサイドへのアクセス量が多くなることを想定するとCDNでキャッシュするのが有効です。
CDNの後方に位置するサーバサイドをクラウドサービスを利用して構築している場合、クラウドサービスに対するアクセスを低減でき、その結果クラウドサービスのコンピューティングリソースの利用量を低減できます。利用量が低減されるということはクラウドサービスのコストが低減できます。
一方でキャッシュキーに関しては慎重に取り扱う必要があり、Web APIにおいてどのキーの値が同一であれば同一のキャッシュキーとするか詳細に設計する必要があります。CDNのキャッシュは細かなコントロールが出来ず、一部のデータを削除できないのが大半かと思います。
で結局どこに配置すれば良いの?
複数の選択肢の中から1つのみ選択する必要はなく、適切な箇所を必要な分だけ選択するのが良いと思います。選択の際にはサービスの特性、費用対効果、開発者が調達できるかなど複数の観点で検討が必要になります。
複数個所にキャッシュを配置した場合、エンドユーザからするとストレスなく利用できるため嬉しい一方で運用者は大変になるかと思います。万能なものはありません。トレードオフを考慮し決定しましょう。
キャッシュアルゴリズムの選択
キャッシュは保持期間を指定するため、保持期間を過ぎると削除されていきますが、保持期間内であってもキャッシュ可能なサイズを超えた場合はキャッシュアルゴリズムに従い、何れかのデータが削除されます。
代表的なキャッシュアルゴリズムは以下の通りです。
- FIFO(First-In, First-Out): 最初にキャッシュされたデータを最初に削除する。
- LFU(Least Frequently Used): 最も使用頻度が低いデータを削除する。
- LIFO(Last-In, First-Out): 最後にキャッシュされたデータを最初に削除する。
- LRU(Least Recently Used): 最も長い間、使用されていないデータを削除する。
システムの特性を踏まえて選択すべきですが、一般的にはLFU(Least Frequently Used)かLRU(Least Recently Used)を選択するのが良いと思います。
コストとの兼ね合い次第ですが必要なデータが全て保存可能なサイズのキャッシュを用意できるのがキャッシュヒット率の観点では理想です。
注意点
キャッシュを利用する上で注意すべき点があります。
- 何らかの理由でキャッシュを全て削除した際にRDBに対する負荷が集中することになります。サービス停止にならないよう流量制御するなどの対策が必要になるかもしれません。
- 初回アクセス(正確に言うとキャッシュが作成されるまでの間に実行されたアクセス)はキャッシュにデータがないので遅くなります。最初にアクセスしたユーザはストレスに感じるかもしれません。ユーザにストレスを与えないようにキャッシュにデータを用意する仕組みが有効かもしれません。
- 変更が発生しないと思われるデータでも無期限にキャッシュするのはおすすめしません。設計当初は変更が発生しないと考えられるものであっても後々変更が発生したり、誤ってキャッシュしてしまった際に削除が困難にならないよう保持期間を設けましょう。最長でも1日とかで様子を見るところからスタートするのがよろしいかと思います。
- 個人情報のキャッシュは慎重に扱う必要があります。ユーザ自身の名前や電話番号などの情報はRDBから取得するにしても計算処理時間は短いためキャッシュしなくても良いでしょう。一方でネットショッピングであれば購入履歴など計算処理に時間を要するものがありキャッシュしたくなります。CDNの仕様理解不足などにより誤って他者に閲覧可能な状態になると個人情報漏洩インシデントになりかねません。キャッシュするのであればクライアントにキャッシュすることを優先的に検討したいところです。とはいえCDNの仕様理解不足によりCDNに意図せずキャッシュされてしまったり、Web APIの不具合により他者の情報を誤って返却してしまうことがありえます。システム全体で複数人で同時期にアクセスした際に非機能要件面だけではなく機能要件面でのテストを十分実施することで個人情報漏洩リスクを低減できることでしょう。
さいごに
性能要件が緩いサービスであればキャッシュを設けない方がシンプルな設計となり、開発や保守も楽でしょう。しかしながら高い性能要件が求められるサービスになると話が別です。
Amazonの調査によると、サイト表示が0.1秒遅くなると、売り上げが1%減少し、1秒高速化すると10%の売上が向上するとのことなのでクライアントへ如何に早く表示するか・レスポンスを返すかが重要となり、その対応の1つとしてキャッシュが必要となります。
冒頭で、キャッシュとは現金を意味するcashではなく、cacheであると言う説明をしましたが、cacheを利用することでcashを増やすことが可能です。この記事を参考にしていただき、世の中から遅いサービスが減り、ユーザのストレスが低減できれば幸いです。
以上、ぼくのかんがえたさいきょうのキャッシュ戦略でした。