キャッシング
キャッシュの基本を学び、レイテンシを削減し、負荷を下げ、スケール時のバックエンド性能を向上させましょう。
キャッシュが存在する理由
キャッシュは、頻繁にアクセスされるデータをより高速なストレージに保存することで、システムのレイテンシとデータベース負荷を削減します。
- データベースは比較的遅く、繰り返しクエリするのはコストが高いです。
- キャッシュは、頻繁にアクセスされるデータをメモリのような高速なストレージに保存します。
- データがキャッシュに見つかれば、データベースを完全にスキップできます。
詳細
バックエンドシステムでは、ユーザープロフィール、商品一覧、設定値など、同じデータに対するリクエストが何度も発生します。毎回データベースを問い合わせると、不要なレイテンシが発生し、システム全体への負荷も増えます。
キャッシュは、頻繁にアクセスされるデータをより高速な層、通常はメモリに保存することでこれを解決します。リクエストが来たとき、システムはまずキャッシュを確認します。データがすでにそこにあれば、データベースに触れることなく即座に結果を返せます。
これにより応答時間が大幅に改善され、データベースへの負荷も軽減されます。データベースは通常、システム内で最も高価で制約の多いリソースの一つです。
高いレベルでは、キャッシュはショートカットのように機能します。同じデータを何度も再計算したり再取得したりする代わりに、システムは以前取得した結果を再利用します。
キャッシュヒットとキャッシュミス
すべてのキャッシュ検索は、ヒット(高速)かミス(データベースアクセスが必要)のどちらかになります。
- キャッシュヒットでは、データがキャッシュから即座に返されます。
- キャッシュミスでは、データベースを問い合わせてから結果を保存する必要があります。
- システムのパフォーマンスは、高いキャッシュヒット率を達成できるかに大きく依存します。
詳細
リクエストが届くと、システムはまず要求されたデータがキャッシュに存在するかを確認します。
データが見つかった場合、これは cache hit と呼ばれます。データベースへの問い合わせは非常に遅いため、それを避けられることでレスポンスはすばやく返されます。
データが見つからない場合、これは cache miss と呼ばれます。システムはデータベースを問い合わせ、データを取得してキャッシュに保存し、その後レスポンスを返さなければなりません。
このパターンには、2つの異なる経路があります。
キャッシュ → ヒット → 速いレスポンス
キャッシュ → ミス → データベース → 保存 → レスポンス
キャッシュの目的は、キャッシュヒットを最大化することです。ヒット率が高いほどシステムは高速になり、データベースへの負荷は低くなります。
Time-To-Live (TTL)
TTL は、データが古くなるのを防ぐために、キャッシュ内のデータが自動的に削除されるまでの時間を制御します。
- TTL はキャッシュされたエントリの有効期間を定義します。
- TTL が期限切れになると、そのデータはキャッシュから削除されます。
- 適切な TTL は、データの鮮度とパフォーマンス上の利点のバランスを取ります。
詳細
キャッシュされたデータは、元のデータが変化する可能性があるため、いつまでも保持できません。キャッシュが更新されないと、システムは古い情報や誤った情報を返す可能性があります。
Time-To-Live (TTL) は、各キャッシュエントリに有効期限を付けることでこれを解決します。TTL の期間が過ぎると、キャッシュは自動的にそのデータを削除します。
たとえば、商品カタログは 5 分間キャッシュし、頻繁に変化する API レスポンスは 60 秒だけキャッシュする、といった使い方があります。
適切な TTL を選ぶことはトレードオフです。TTL を長くするとキャッシュヒットが増えてパフォーマンスは向上しますが、古いデータを返すリスクがあります。TTL を短くするとデータの鮮度は保てますが、データベースへの負荷が増えます。
TTL は、キャッシュの正しさを維持するための最もシンプルで、最も広く使われている仕組みの 1 つです。
キャッシュの無効化
キャッシュの無効化は、基になるデータが変更されたときに、キャッシュされたデータが正しい情報源と一致し続けるようにします。
- データベース内のデータが変更されたら、関連するキャッシュエントリを更新または削除する必要があります。
- 無効化が正しく行われないと、古いデータや不整合なデータが返されます。
- 一般的な戦略には、更新後にキャッシュエントリを削除する、または再読み込みする方法があります。
詳細
キャッシュは、基になるデータが変更されたときに古くなる可能性があるという根本的な問題を生みます。システムが古いキャッシュデータを返し続けると、ユーザーは誤った情報を見ることになります。
キャッシュの無効化は、データベース内のデータが更新されたときに、対応するキャッシュエントリを削除または再読み込みすることで、この問題を解決します。
一般的な方法は、データベース更新の直後にキャッシュエントリを削除することです。次のリクエストではキャッシュミスが発生し、最新のデータがデータベースから取得され、更新された値がキャッシュに保存されます。
別の方法として、データベースの変更と同時にキャッシュを積極的に更新し、両者を同期させ続けるやり方があります。
難しいのは、どのキャッシュエントリを正確に無効化すべきかを特定することです。この処理のミスは、分散システムにおける一般的なバグの原因です。
キャッシュアサイドパターン
キャッシュアサイドパターンでは、アプリケーションがデータをいつキャッシュに読み込むかを制御します。
- アプリケーションは、データベースを問い合わせる前にまずキャッシュを確認します。
- キャッシュミスが発生した場合、データベースからデータを取得してキャッシュに保存します。
- このアプローチにより、キャッシュ動作を完全に制御できます。
詳細
キャッシュアサイドパターンでは、アプリケーションがキャッシュとデータベースの両方とやり取りする責任を持ちます。
リクエストが来ると、アプリケーションはまずデータがキャッシュに存在するかを確認します。存在する場合(キャッシュヒット)、データはすぐに返されます。
データがキャッシュにない場合(キャッシュミス)、アプリケーションはデータベースを問い合わせ、結果を取得してキャッシュに保存し、その後レスポンスを返します。
このアプローチは、シンプルで柔軟なため広く使われています。アプリケーションは、何をキャッシュするか、いつキャッシュするか、どのくらい保持するかを決められます。
ただし、キャッシュはデータベースと自動的に同期されないため、キャッシュの無効化と整合性を慎重に扱う必要があります。
ライトスルーキャッシング
ライトスルーキャッシングは、書き込みのたびにキャッシュとデータベースの両方を更新することで、両者を同期させます。
- すべての書き込みで、キャッシュとデータベースの両方が更新されます。
- これにより、キャッシュされたデータは常に最新の状態に保たれます。
- その代わり、書き込みのレイテンシーが増加します。
詳細
ライトスルーキャッシングでは、データが書き込まれるたびに、システムはまずキャッシュを更新し、その後同じデータをデータベースに書き込みます。これにより、両方の層が常に同じ値を保持することが保証されます。
キャッシュは即座に更新されるため、その後の読み取りではデータベースを確認する必要なく、最新のデータを返せます。これにより、読み取りロジックが सरलになり、古いデータの問題を避けられます。
その代わり、各書き込みを2回実行する必要があるため、書き込み操作は遅くなります。つまり、1回はキャッシュに、もう1回はデータベースに書き込む必要があります。これによりレイテンシーが増加し、書き込み負荷が高いワークロードではスループットが低下することがあります。
さらに、ライトスルーキャッシングでは両方のストレージ層を常に維持するため、より多くのリソースを消費することがあります。それでも、一貫性が重要で、予測可能な読み取り動作が必要なシステムでは有用です。
キャッシュ技術
さまざまなキャッシュ技術は、異なるレイヤーで動作してレイテンシを削減し、バックエンドシステムの負荷を軽減します。
- Redis は、TTL サポートと高度なデータ構造を備えた高速なインメモリストアです。
- Memcached は、高スループットのキー・バリューアクセスに最適化された軽量なキャッシュです。
- CDN は、ユーザーの近くでコンテンツをキャッシュして、グローバルなネットワークレイテンシを削減します。
詳細
キャッシュは、システム内のどこに配置されるかに応じて、さまざまな技術を使って実装されます。
Redis は、最も広く使われているキャッシュシステムの1つです。データをメモリ上に保存するため非常に高速で、TTL、リスト、セット、その他のデータ構造などの機能をサポートしています。アプリケーションレベルのキャッシュ、セッション、リアルタイムデータによく使われます。
Memcached もインメモリキャッシュですが、Redis よりシンプルです。純粋に高速なキー・バリュー保存に特化しており、高スループット向けに最適化されているため、基本的なキャッシュ用途に効果的です。
より高いレベルでは、CDN(Content Delivery Networks)は、画像、動画、HTML などの静的コンテンツを世界中に分散したサーバーでキャッシュします。これにより、データが移動する距離が短くなり、グローバルユーザーのパフォーマンスが向上します。
多くのシステムでは、これらの技術が組み合わせて使われます。リクエストはまず CDN に到達し、次に Redis のようなアプリケーションキャッシュに入り、必要な場合にのみデータベースへ到達します。
キャッシュが使われる場所
キャッシュは、レイテンシを減らし、より深いコンポーネントへの負荷を最小限に抑えるために、システム内の複数の層で適用されます。
- キャッシュはエッジ(CDN)、アプリケーション層、データベース層に存在します。
- 各層は、不要な下位層へのリクエストを防ぐことで、より高速な応答を提供します。
- キャッシュを重ねることで、システム全体のパフォーマンスとスケーラビリティが向上します。
詳細
キャッシュは1か所だけに限定されるものではなく、バックエンドシステムの複数の層で使われます。
エッジでは、CDNが静的コンテンツをユーザーの近くにキャッシュし、ネットワークのレイテンシを削減します。アプリケーション層では、RedisやMemcachedのようなツールが、ユーザーセッションやAPIレスポンスなど、頻繁にアクセスされるデータをキャッシュします。
一部のデータベースにはクエリキャッシュもあり、よく使われるクエリの結果を保存して再計算を避けます。
これらの層は次のような階層を形成します:
クライアント → CDN → アプリケーション キャッシュ → データベース
各層がリクエスト負荷の一部を吸収することで、データベースには必要なときだけアクセスされるようになります。このような階層的なアプローチは、システムを効率的にスケールさせるうえで重要です。
キャッシュのトレードオフ
キャッシュはパフォーマンスを大幅に向上させますが、データの一貫性と正確性に関する複雑さを伴います。
- キャッシュはレイテンシとデータベース負荷を減らし、システムのスケーラビリティを向上させます。
- 古いデータや一貫性の問題などのリスクを導入します。
- キャッシュの無効化を管理することが、システム設計における重要な課題になります。
詳細
キャッシュは、システムのパフォーマンスを向上させる最も効果的な方法の1つです。より高速なストレージからデータを提供することで、システムはより素早く応答でき、データベースへの負荷を抑えながら、はるかに多くのトラフィックを処理できます。
しかし、このパフォーマンス向上にはトレードオフがあります。最大の問題は古いデータです。キャッシュされた値が、もはやデータベースの最新状態を反映していない可能性があります。
キャッシュとデータベースの間で一貫性を保つには、追加の複雑さが生じます。開発者は、データを正確に保つために、無効化戦略を慎重に設計しなければなりません。
実際には、キャッシュはバランスです。システムは、速度とスケーラビリティを大きく向上させる代わりに、ある程度の一貫性と複雑さを受け入れます。
質問セクション
1 / 5
このレッスンはプレミアムコンテンツです
プレミアムにアップグレードしてぼかしを解除し、全文を読めるようにしましょう。