キャッシュは、システムのパフォーマンスとスケーラビリティを改善することを目的とする一般的な手法です。 キャッシュでは、頻繁にアクセスされるデータが、元のソースよりもアプリケーションに近いストレージに一時的にコピーされます。 このアプローチでは、データをより迅速に提供することで、クライアント アプリケーションの応答時間を大幅に向上させることができます。
キャッシュは、特に次のすべての条件が元のデータ ストアに適用される場合に、クライアント インスタンスが同じデータを繰り返し読み取る場合に最も効果的です。
- データ ストアは比較的静的なままです。
- キャッシュの速度に比べて遅い。
- 高レベルの競合が発生する可能性がある。
- クライアントから十分に離れているため、ネットワークの待ち時間が著しく長くなっています。
分散アプリケーションでのキャッシュ
分散アプリケーションは、通常、データをキャッシュするときに、次の戦略のいずれかまたは両方を実装します。
プライベート キャッシュを使用します。このキャッシュでは、アプリケーションまたはサービスを実行するコンピューター上でデータをローカルに保持します。
共有キャッシュを使用します。これは、複数のプロセスとマシンがアクセスできる共通のソースとして機能します。
どちらの場合も、クライアント側とサーバー側でキャッシュを実行できます。 Web ブラウザーやデスクトップ アプリケーションなどのシステムのユーザー インターフェイスを提供するプロセスは、クライアント側のキャッシュを処理します。 サーバー側キャッシュは、ビジネス サービスを提供するプロセスによってリモートで実行されます。
プライベート キャッシュ
キャッシュの最も基本的な種類は、メモリ内ストアです。 これは 1 つのプロセスのアドレス空間に保持され、そのプロセスで実行されるコードはキャッシュに直接アクセスします。 この種類のキャッシュにすばやくアクセスできます。 また、少量の静的データを格納するための効果的な手段を提供することもできます。 コンピューターで使用できるメモリの量は、通常、キャッシュのサイズを制限します。
メモリ内で物理的に可能なよりも多くの情報をキャッシュする必要がある場合は、キャッシュされたデータをローカル ファイル システムに書き込むことができます。 ファイル システムからデータにアクセスするには、データがメモリに格納されている場合よりも時間がかかりますが、ネットワーク経由でデータを取得するよりも高速で信頼性が高いはずです。
このモデルを使用するアプリケーションの複数のインスタンスが同時に実行されている場合、各アプリケーション インスタンスには、データの独自のコピーを保持する独自の独立したキャッシュがあります。
キャッシュは、過去のある時点の元のデータのスナップショットと考えてください。 このデータが静的でない場合は、異なるアプリケーション インスタンスが異なるバージョンのデータをキャッシュに保持している可能性があります。 そのため、次の図に示すように、これらのインスタンスによって実行されたのと同じクエリによって異なる結果が返される可能性があります。
共有 SQL データベースに接続されている複数のアプリケーション インスタンス間のキャッシュの不整合を示す図。 右側には、シリンダーとして表される SQL データベースがあります。 左上にはアプリケーション インスタンス A というラベルが付いた大きな円があり、左下にはアプリケーション インスタンス B というラベルが付いた同じサイズの円があります。アプリケーション インスタンス A の内部には、アプリケーション プロセスを表す歯車アイコンがあります。 歯車アイコンの下には、メモリ内キャッシュを表すグリッド テーブルがあります。 境界外には、X 時のデータのスナップショットであるラベル読み取りキャッシュがあります。アプリケーション インスタンス B の内部には、別の歯車アイコンがあります。 歯車アイコンの下には、メモリ内キャッシュを表すグリッド テーブルがあります。 境界外には、時間 Y のデータのスナップショットであるラベル読み取りキャッシュがあります。SQL データベースから、行はアプリケーション インスタンス A に向かって拡張され、ラベル付けされたアプリケーション インスタンス A は X 時にデータを取得し、メモリ内にキャッシュします。 2 行目は SQL データベースからアプリケーション インスタンス B に拡張され、ラベル付けされたアプリケーション インスタンス B は時刻 Y でデータを取得し、メモリにキャッシュします。 これら 2 つの注釈行の間には、時刻 X と時刻 Y の間でデータベース内の情報を読み取るメモがあります。
共有キャッシュ
共有キャッシュを使用することで、すべてのアプリケーション インスタンスにキャッシュされたデータの同じビューが表示されるようにすることができます。 キャッシュは別の場所に配置されます。これは通常、次の図に示すように、別のサービスの一部としてホストされます。
共有キャッシュ サービスが、SQL データベースに接続されている複数のアプリケーション インスタンス間でキャッシュの不整合を解決する方法を示す図。 右端には、シリンダーとして表される SQL データベースがあります。 中央には、共有キャッシュを表すグリッド テーブルを含む共有キャッシュ サービスというラベルが付いたボックスがあります。 左端には、アプリケーション インスタンス A というラベルが付いた歯車アイコンがあります。その下には、アプリケーション インスタンス B というラベルが付いた 2 つ目の歯車アイコンがあります。黒い矢印が SQL データベースから共有キャッシュ サービス グリッドを指し示し、データベースによって共有キャッシュが設定されることを示します。 共有キャッシュ サービスから、2 つの青い矢印が外側に広がり、1 つはアプリケーション インスタンス A に向かって、1 つはアプリケーション インスタンス B に向かってアングリングします。左端では、曲線ブラケットが 2 つのアプリケーション インスタンス間で垂直方向にまたがり、隣接するラベルに "両方のアプリケーション インスタンスが同じキャッシュされたデータを表示する" というラベルが表示されます。 このラベルと角かっこは、共有キャッシュ アーキテクチャの主な利点を強調しています。メモリ内のインスタンスごとのキャッシュとは異なり、アプリケーション インスタンス A と B の両方が同じ集中型キャッシュからデータを取得します。
共有キャッシュ アプローチの重要な利点は、スケーラビリティです。 多くの共有キャッシュ サービスは、サーバーのクラスターを使用して実装され、ソフトウェアを使用してクラスター全体に透過的にデータを分散します。 アプリケーション インスタンスがキャッシュ サービスに要求を送信します。 基になるインフラストラクチャによって、クラスター内のキャッシュされたデータの場所が決まります。 サーバーを追加することで、キャッシュを簡単にスケーリングできます。
共有キャッシュアプローチには、主に次の 2 つの欠点があります。
- キャッシュは各アプリケーション インスタンスでローカルに保持されなくなったため、アクセスに時間がかかります。
- 別のキャッシュ サービスを実装すると、ソリューションが複雑になる可能性があります。
キャッシュの使用に関する考慮事項
次のセクションでは、キャッシュの設計と使用に関する考慮事項について説明します。
データをキャッシュするタイミングを決定する
キャッシュにより、パフォーマンス、スケーラビリティ、可用性が大幅に向上します。 データが多いほど、このデータにアクセスする必要があるユーザーの数が多いほど、キャッシュの利点が大きくなります。 キャッシュにより、元のデータ ストアでの大量の同時要求の処理に関連する待機時間と競合が減少します。
たとえば、データベースでは、限られた数の同時接続がサポートされている場合があります。 ただし、基になるデータベースではなく、共有キャッシュからデータを取得すると、使用可能な接続の数が現在使い果たされている場合でも、クライアント アプリケーションはこのデータにアクセスできます。 さらに、データベースが使用できなくなった場合、クライアント アプリケーションはキャッシュ内のデータを使用して続行できる可能性があります。
頻繁に読み取られますが、変更頻度の低いデータをキャッシュすることを検討してください。 たとえば、読み取り操作の割合が書き込み操作よりも高いデータをキャッシュします。 ただし、重要な情報の権限のあるストアとしてキャッシュを使用しないでください。 代わりに、すべての重要な変更を永続的なデータ ストアに保存します。 キャッシュが使用できない場合でも、アプリケーションは引き続きデータ ストアを使用して動作でき、重要な情報は失われません。
データを効果的にキャッシュする方法を決定する
キャッシュを効果的に使用するには、キャッシュする最も適切なデータを決定し、適切なタイミングでキャッシュします。 アプリケーションが初めてデータを取得するときに、キャッシュにデータを追加できます。 アプリケーションはデータ ストアから 1 回だけデータをフェッチする必要があり、キャッシュを使用して後続のアクセスを満たすことができます。
または、一般的にアプリケーションの起動時に、キャッシュにデータを事前に部分的または完全に設定することもできます。 このアプローチは シード処理と呼ばれます。 ただし、この方法では、アプリケーションの実行開始時に元のデータ ストアに突然高い負荷がかかる可能性があるため、大規模なキャッシュのシード処理を実装することはお勧めしません。
使用パターンを分析して、キャッシュを完全に事前設定するか部分的に事前設定するかを決定し、キャッシュするデータを選択します。 たとえば、アプリケーションを毎日使用する顧客に対しては静的ユーザー プロファイル データをキャッシュにシードできますが、週に 1 回だけアプリケーションを使用する顧客にはシード処理できません。
通常、キャッシュは、変更できないデータや変更頻度の低いデータに適しています。 たとえば、eコマース アプリケーションの製品や価格情報などの参照情報や、構築にコストがかかる共有静的リソースなどがあります。 リソースの需要を最小限に抑え、パフォーマンスを向上させるために、このデータの一部またはすべてをアプリケーションの起動時にキャッシュに読み込みます。 参照データを最新の状態に保つために、キャッシュ内のデータを定期的に更新するバックグラウンドプロセスが必要になる場合もあります。 または、参照データが変更されたときに、バックグラウンド プロセスでキャッシュを更新できます。
キャッシュは動的データにはあまり役に立ちませんが、いくつかの例外があります。 詳細については、この記事で後述する「 動的データのキャッシュ」 セクションを参照してください。 元のデータが定期的に変更されると、キャッシュされた情報はすぐに古くなるか、キャッシュを元のデータ ストアと同期するオーバーヘッドによってキャッシュの有効性が低下します。
キャッシュには、エンティティの完全なデータを含める必要はありません。 たとえば、データ項目が、名前、住所、口座残高を持つ銀行顧客などの複数値オブジェクトを表す場合、これらの要素の一部は、名前や住所など静的なままになることがあります。 口座残高などの他の要素は、より動的な場合があります。 このような状況では、データの静的な部分をキャッシュし、必要に応じて残りの情報のみを取得 (または計算) すると便利です。
パフォーマンス テストと使用状況分析を実行して、キャッシュの事前入力またはオンデマンド読み込み、またはその両方の組み合わせが適切かどうかを判断します。 データのボラティリティと使用パターンに基づいて決定します。 キャッシュ使用率とパフォーマンス分析は、負荷が高く、拡張性が高い必要があるアプリケーションで重要です。 たとえば、高度にスケーラブルなシナリオでは、キャッシュをシードして、ピーク時にデータ ストアの負荷を軽減できます。
キャッシュを使用して、アプリケーションの実行中に計算が繰り返されないようにすることもできます。 操作がデータを変換したり、複雑な計算を実行したりすると、操作の結果をキャッシュに保存できます。 後で同じ計算が必要な場合、アプリケーションはキャッシュから結果を取得できます。
アプリケーションは、キャッシュ内のデータを変更できます。 ただし、キャッシュは、いつでも消える可能性がある一時的なデータ ストアと考えてください。 キャッシュにのみ貴重なデータを格納しないでください。元のデータ ストアにも情報が保持されていることを確認します。 この方法により、キャッシュが使用できなくなった場合にデータが失われる可能性が最小限に抑えられます。
書き込み時にキャッシュの整合性を維持する
アプリケーションがキャッシュするデータも変更する場合は、キャッシュがレコードシステムとの整合性を維持する方法を決定します。 2 つの方法が一般的です。
書き込み時に無効にする: 変更をデータ ストアに書き込み、影響を受けるキャッシュ エントリを削除します。 次の読み取りでは、データ ストアからキャッシュが再作成されます。 この方法により、書き込みパスは単純になりますが、リーダーはキャッシュ ミスが発生したり、書き込みの直後に古いデータを短時間見ることができます。 Cache-Aside パターンでは、この方法について説明します。
ライトスルー: データ ストアとキャッシュを同じ書き込み操作の一部として更新し、両方の更新が成功した後にのみ書き込み応答を返します。 この方法では、書き込みが成功した直後に更新された値が読者に提供されます。これにより、書き込みの待機時間が長くなり、調整ロジックが増えます。
Azure Functionsを使用して、Azure SQL Databaseおよび Azure Managed Redis への書き込みスルー更新を調整するエンド ツー エンドの例については、「Azure Managed Redis と Azure SQL Database を使用したライトスルー キャッシュ」を参照してください。
書き込みスルーは、書き込み後すぐに最新データを読み取れることが必要な、読み取り主体のアクセス パスにのみ使用してください。 書き込まれた後にほとんど読み取られないデータや、絶えず変更されるデータの場合は、書き込み時に無効にするか、キャッシュをバイパスします。
非常に動的なデータをキャッシュする
急速に変化する情報を永続的なデータ ストアに格納すると、システムにオーバーヘッドが発生する可能性があります。 たとえば、状態やその他の測定値を継続的に報告するデバイスを考えてみましょう。 キャッシュされた情報が通常古くなっていることに基づいて、アプリケーションがこのデータをキャッシュしないことを選択した場合、データ ストアからこの情報を格納および取得する際にも同じ考慮事項が当てはまる可能性があります。 保存と取得の間にこのデータが変更される可能性があります。
このような状況では、永続的なデータ ストアではなく、動的な情報をキャッシュに直接格納する利点を考慮してください。 データが重要でなく、監査を必要としない場合は、不定期の変更が失われるかどうかは関係ありません。
キャッシュ内のデータの有効期限を管理する
ほとんどの場合、キャッシュには、元のデータ ストアからのデータのコピーであるデータが保持されます。 元のデータ ストア内のデータはキャッシュ後に変更される可能性があり、キャッシュされたデータが古くなる可能性があります。 多くのキャッシュ システムでは、データの有効期限が切れるようキャッシュを構成できます。これにより、データが古くなっている期間が短縮されます。
キャッシュされたデータの有効期限が切れると、キャッシュによって削除されます。 その後、アプリケーションは元のデータ ストアから新しいデータを取得し、キャッシュ内の期限切れのデータを置き換えることができます。 キャッシュを構成するときに、既定の有効期限ポリシーを設定できます。 多くのキャッシュ サービスでは、プログラムを使用してキャッシュに格納するときに、個々のオブジェクトの有効期限を指定することもできます。 一部のキャッシュでは、有効期限を絶対値として指定するか、指定した時間内に項目にアクセスしない場合はスライディング値として指定できます。 この設定は、指定されたオブジェクトに対してのみ、キャッシュ全体の有効期限ポリシーをオーバーライドします。
Note
キャッシュの有効期限と、キャッシュに含まれるオブジェクトを慎重に検討してください。 短すぎると、オブジェクトの有効期限が早くなりすぎて、キャッシュを使用する利点が減ります。 期間が長すぎると、データが古くなるリスクがあります。
データを長期間キャッシュに残すことを許可すると、キャッシュがいっぱいになる可能性があります。 この場合、キャッシュに新しい項目を追加する要求が発生すると、キャッシュは削除と呼ばれるプロセスの一部の項目を強制的に削除する可能性があります。 キャッシュ サービスは通常、最も使用頻度の低い (LRU) ベースでデータを削除しますが、通常は、このポリシーをオーバーライドして項目が削除されないようにすることができます。 ただし、この方法を採用すると、キャッシュで使用可能なメモリを超えるリスクがあります。 完全キャッシュに項目を追加しようとするアプリケーションは、例外で失敗します。
キャッシュ実装によっては、他の削除ポリシーが提供される場合があります。 削除ポリシーの種類は次のとおりです。
- 最近使用されたポリシー: データが再び必要にならないことを想定して、最近使用した項目をキャッシュから削除します。
- 先入れ先出しポリシー: 最初にキャッシュから最も古いデータを削除します。
- 明示的な削除ポリシー: 変更中のデータなど、トリガーイベントに基づいてキャッシュから項目を削除します。
クライアント側キャッシュ内のデータを無効にする
クライアント側キャッシュに保持されているデータは、クライアントにデータを提供するサービスの制御外と見なされます。 サービスは、クライアント側キャッシュに対して情報の追加または削除をクライアントに直接強制することはできません。
この制限は、適切に構成されていないキャッシュを使用するクライアントが、古い情報を引き続き使用できることを意味します。 たとえば、キャッシュの有効期限ポリシーが適切に実装されていない場合、クライアントは、元のデータ ソースの情報が変更されたときにローカルにキャッシュされる古い情報を使用する可能性があります。
HTTP 接続経由でデータを提供する Web アプリケーションを構築する場合は、ブラウザーや Web プロキシなどの Web クライアントに、最新の情報をフェッチするように暗黙的に強制できます。 リソースを更新するたびに URI を変更することで、この更新をトリガーできます。 Web クライアントは通常、リソースの URI をクライアント側キャッシュのキーとして使用するため、URI が変更された場合、Web クライアントはリソースの以前にキャッシュされたバージョンを無視し、代わりに新しいバージョンをフェッチします。
キャッシュ内のコンカレンシーを管理する
多くの場合、アプリケーションの複数のインスタンスによって共有されるようにキャッシュを設計します。 各アプリケーション インスタンスはキャッシュ内のデータを読み取って変更できるため、共有データ ストアで発生する同じコンカレンシーの問題もキャッシュに適用されます。 アプリケーションがキャッシュに保持されているデータを変更する必要がある場合は、アプリケーションの 1 つのインスタンスによって行われた更新によって、別のインスタンスによって行われた変更が上書きされないようにする必要がある場合があります。
データの性質と競合の可能性に応じて、コンカレンシーには次の 2 つの方法のいずれかを採用します。
オプティミスティック: アプリケーションは、データを更新する前に、キャッシュ内のデータが取得されてから変更されたかどうかを確認します。 データがまだ同じ場合は、アプリケーションによって変更が行われます。 それ以外の場合、アプリケーションはそれを更新するかどうかを決定します。 この決定を推進するビジネス ロジックは、アプリケーション固有です。 この方法は、更新が頻繁に行われない状況や、競合が発生する可能性が低い状況に適しています。
ペシミスティック: アプリケーションは、データを取得すると、キャッシュ内のデータをロックして、別のインスタンスがデータを変更できないようにします。 このプロセスにより、競合は発生しませんが、同じデータを処理する必要がある他のインスタンスをブロックすることもできます。 ペシミスティック コンカレンシーはソリューションのスケーラビリティに影響を与える可能性があり、有効期間の短い操作にのみ推奨されます。 この方法は、特にアプリケーションがキャッシュ内の複数の項目を更新し、これらの変更が一貫して適用されるようにする必要がある場合に、競合の可能性が高い状況に適している可能性があります。
高可用性とスケーラビリティを実装し、パフォーマンスを向上させる
データのプライマリ リポジトリとしてキャッシュを使用しないでください。 キャッシュが設定された元のデータ ストアがこのロールを果たします。 元のデータ ストアにより、データの永続化が保証されます。
ソリューションに共有キャッシュ サービスの可用性に重大な依存関係を導入しないように注意してください。 共有キャッシュが使用できない場合、アプリケーションは引き続き機能できる必要があります。 キャッシュ サービスが再開されるのを待っている間、アプリケーションが応答しなくなったり、失敗したりしないようにする必要があります。
そのため、キャッシュ サービスの可用性を検出し、キャッシュにアクセスできない場合は元のデータ ストアにフォールバックするようにアプリケーションを準備する必要があります。 Circuit-Breaker パターンは、このシナリオの処理に役立ちます。 キャッシュを提供するサービスを復旧でき、使用可能になった後は、 Cache-Aside パターンなどの戦略に従って、元のデータ ストアからデータが読み取られるとキャッシュを再作成できます。
ただし、キャッシュが一時的に使用できないときにアプリケーションが元のデータ ストアにフォールバックすると、システムのスケーラビリティが影響を受ける可能性があります。 キャッシュの復旧中は、元のデータ ストアにデータの要求が含まれる可能性があり、その結果、タイムアウトと接続の失敗が発生する可能性があります。
すべてのアプリケーション インスタンスがアクセスする共有キャッシュと共に、アプリケーションの各インスタンスにローカルのプライベート キャッシュを実装することを検討してください。 アプリケーションは、項目を取得するときに、最初にローカル キャッシュ、次に共有キャッシュ、最後に元のデータ ストアで確認できます。 ローカル キャッシュは、共有キャッシュ内のデータを使用するか、共有キャッシュが使用できない場合はデータベース内で設定できます。
この方法では、ローカル キャッシュが共有キャッシュに関して古くなりすぎないように、慎重な構成が必要です。 ただし、次の図に示すように、共有キャッシュに到達できない場合、ローカル キャッシュはバッファーとして機能します。
比較的有効期間の長いデータを保持する大規模なキャッシュをサポートするために、一部のキャッシュ サービスには、キャッシュが使用できなくなった場合に自動フェールオーバーを実装する高可用性オプションが用意されています。 通常、この方法では、プライマリ キャッシュ サーバーに格納されているキャッシュされたデータをセカンダリ キャッシュ サーバーにレプリケートし、プライマリ サーバーで障害が発生した場合や接続が失われた場合にセカンダリ サーバーに切り替える必要があります。
複数の宛先への書き込みによる待機時間を短縮するために、プライマリ サーバー上のキャッシュにデータが書き込まれるときに、セカンダリ サーバーへのレプリケーションが非同期的に行われる場合があります。 この方法により、障害が発生した場合にキャッシュされた情報の一部が失われる可能性がありますが、キャッシュの全体的なサイズと比較して、このデータの割合は小さくする必要があります。
共有キャッシュが大きい場合は、キャッシュされたデータをノード間でパーティション分割して競合の可能性を減らし、スケーラビリティを向上させることが有益な場合があります。 多くの共有キャッシュでは、ノードを動的に追加および削除したり、パーティション間でデータを再調整したりする機能がサポートされています。 このアプローチでは、ノードのコレクションが単一のキャッシュとしてクライアント アプリケーションに提示されるクラスタリングが含まれる場合があります。 ただし、内部的には、負荷を均等に分散する定義済みの分散戦略に従って、ノード間でデータが分散されます。 詳細については、 シャーディング パターンを参照してください。
クラスタリングを使用すると、キャッシュの可用性を高めることもできます。 ノードが失敗した場合でも、キャッシュの残りの部分にアクセスできます。 クラスタリングは、レプリケーションとフェールオーバーでよく使用されます。 各ノードをレプリケートでき、ノードに障害が発生した場合はレプリカをすばやくオンラインにすることができます。
多くの読み取り操作と書き込み操作には、単一のデータ値またはオブジェクトが含まれる可能性があります。 ただし、大量のデータを迅速に格納または取得することが必要になる場合があります。 たとえば、キャッシュのシード処理には、数百または数千の項目をキャッシュに書き込む必要があります。 アプリケーションでは、同じ要求の一部として、多数の関連項目をキャッシュから取得する必要がある場合もあります。
多くの大規模なキャッシュでは、これらの目的のためにバッチ操作が提供されます。 この機能により、クライアント アプリケーションは大量の項目を 1 つの要求にパッケージ化できるため、多数の小さな要求を実行するオーバーヘッドが軽減されます。
キャッシュと最終的な整合性
Cache-Aside パターンを機能させるには、キャッシュを設定するアプリケーションのインスタンスが、最新かつ一貫性のあるバージョンのデータにアクセスできる必要があります。 最終的な整合性 (レプリケートされたデータ ストアなど) を実装するシステムでは、この条件が正しくない可能性があります。
アプリケーションの 1 つのインスタンスは、データ項目を変更し、その項目のキャッシュされたバージョンを無効にすることができます。 アプリケーションの別のインスタンスがキャッシュからこの項目を読み取ろうとすると、キャッシュ ミスが発生する可能性があります。 次に、データ ストアからデータを読み取り、キャッシュに追加します。 ただし、データ ストアが他のレプリカと完全に同期されていない場合、アプリケーション インスタンスはキャッシュの読み取りと古い値の設定を行うことができます。
分散キャッシュでは、この問題に別のレイヤーが導入されます。 CAP 定理では、分散システムは、整合性、可用性、パーティションの許容度という 3 つの保証のうち、最大 2 つを提供できることを示しています。 ネットワーク パーティションはクラウド環境では避けられないため、整合性と可用性のどちらかを選択する必要があります。 Redis を含むほとんどの分散キャッシュは、厳密な整合性よりも可用性とパーティションの許容度に優先順位を付けます。 この優先順位は、キャッシュ レプリカからの読み取りによって、ネットワーク パーティション中または別のノードへの書き込みの直後に古いデータが返される可能性があることを意味します。 キャッシュ戦略を設計するときは、アプリケーションが許容できる制約の量を決定し、それに応じて Time to Live (TTL) 値を設定します。 最新である必要があるデータの場合は、短い TTL を使用するか、キャッシュ全体をバイパスしてソース データ ストアから読み取る必要があります。
分散システムでのデータ整合性の処理の詳細については、「 マイクロサービスのデータに関する考慮事項」を参照してください。
キャッシュされたデータを保護する
使用するキャッシュ サービスに関係なく、キャッシュ内のデータを未承認のアクセスから保護する方法を検討してください。 主な懸念事項は次の 2 つです。
- キャッシュ内のデータのプライバシー。
- キャッシュとキャッシュを使用しているアプリケーションの間を流れるデータのプライバシー。
キャッシュ内のデータを保護するために、キャッシュ サービスは、アプリケーションで次の詳細を指定する必要がある認証メカニズムを実装する場合があります。
- キャッシュ内のデータにアクセスできる ID。
- これらの ID に許可されている、読み取りおよび書き込み操作。
データの読み取りと書き込みに関連するオーバーヘッドを軽減するために、ID にキャッシュへの書き込みアクセスまたは読み取りアクセス権が付与された後、その ID はキャッシュ内の任意のデータを使用できます。
キャッシュされたデータのサブセットへのアクセスを制限する必要がある場合は、次のいずれかの方法を使用します。
異なるキャッシュ サーバーを使用して、キャッシュをパーティションに分割します。 使用を許可する必要があるパーティションの ID へのアクセス権のみを付与します。
異なるキーを使用して、各サブセット内のデータを暗号化します。 暗号化キーは、各サブセットにアクセスできる必要がある ID にのみ提供します。 クライアント アプリケーションは引き続きキャッシュ内のすべてのデータを取得できますが、キーを持つデータのみを復号化できます。
また、キャッシュに入り出す際にデータを保護する必要もあります。 クライアント アプリケーションがキャッシュへの接続に使用するネットワーク インフラストラクチャによって提供されるセキュリティ機能に依存します。 クライアント アプリケーションをホストするのと同じ組織内のオンサイト サーバーを使用してキャッシュを実装する場合、ネットワーク自体の分離では、さらに手順を実行する必要がない可能性があります。 キャッシュがリモートに配置され、インターネットなどのパブリック ネットワーク経由で TCP または HTTP 接続が必要な場合は、TLS の実装を検討してください。
Azure Managed Redis を使用してキャッシュを実装する
この記事の残りのセクションでは、Azure Managed Redis を使用してキャッシュ パターンを実装する方法について説明します。 Azure Managed Redis は、アプリケーション インスタンス間で共有キャッシュとして使用できるマネージド Redis サービスです。 キーと値のキャッシュ、セット、ソートされたセット、リストなどのデータ構造に対応しており、再起動後のデータ保持のためにオプションで永続性をサポートしています。
使用可能なレベル、容量計画、ネットワーク、機能の詳細については、 Azure Managed Redis のドキュメントを参照してください。
クライアント アプリケーションの接続と構成
Redis は、多くのプログラミング言語でクライアント アプリケーションをサポートしています。 .NET アプリケーションでは、複数のクライアント ライブラリを使用でき、それぞれ異なる Redis ワークロードに適しています。 ライブラリの選択は、Redis をキャッシュとして厳密に使用しているか、マルチモデル データ プラットフォームとして使用しているかによって異なります。
Redis サーバーに接続するには、Connect クラスの静的ConnectionMultiplexer メソッドを使用します。 このメソッドによって作成される接続は、クライアント アプリケーションの有効期間を通じて使用できるように構築されています。 複数の同時実行スレッドで同じ接続を使用できます。 Redis 操作を実行するたびに再接続や切断を行わないと、パフォーマンスが低下する可能性があるためです。
言語固有の接続の例については、「 .NET Core で Managed Redis Azure使用するを参照してください。
.NET クライアント ライブラリを選択する
キャッシュに Azure Managed Redis を使用する場合は、次の.NET ライブラリを使用します。
StackExchange.Redis: 高いパフォーマンスを提供する低レベルの Redis クライアント。 Redis コマンド、アトミック操作、トランザクション、パイプライン処理、または Lua スクリプトに直接アクセスする必要がある場合に使用します。
Microsoft。Extensions.Caching.StackExchangeRedis:
IDistributedCache統合を ASP.NET Coreに提供します。 値が不透明なバイト配列として格納される単純なキー値キャッシュに使用します。 この抽象化では、高度な Redis データ構造は公開されません。
これらのライブラリは、一般的なキャッシュ パターンを構築するために必要なプリミティブを提供しますが、アプリケーションはキャッシュ ロジック自体を実装する必要があります。
キャッシュ パターンを実装する
キャッシュに Redis を使用する最も簡単な方法は、キー値モデルを使用してキーの下に値を格納することです。 値は任意の長さの文字列またはバイナリ データである可能性があるため、Redis はシリアル化されたオブジェクト、構成データ、セッション状態、または事前計算された結果のキャッシュに適しています。
キースペースを慎重に設計し、意味がありながら冗長でないキーを使用しましょう。 たとえば、ID が 100 の顧客のキーを表すには、 customer:100 ( 100ではなく) などの構造化キーを使用します。 このスキームを使用すると、異なるデータ型を格納する値を区別できます。 たとえば、キー orders:100 を使用して、ID が 100 の注文のキーを表すこともできます。
文字列は最も一般的なキャッシュ方法ですが、Redis では、ハッシュ、リスト、セット、並べ替えられたセット、ストリームなどの豊富なネイティブ データ型のセットがサポートされています。これにより、より柔軟なキャッシュ パターンが可能になります。 Redis データ型の詳細については、データ型に関する Redis のドキュメントを参照 してください。
Cache-Aside パターンを実装する
「 データを効果的にキャッシュする方法の決定」で説明されているように、一般的な方法は、必要に応じてキャッシュにデータを読み込む方法です。 次の例では、最初にキャッシュをチェックし、ミス時にデータ ソースからフェッチし、後続の要求の結果を格納します。
var config = new ConfigurationOptions();
// ... configure endpoint, credentials, TLS, etc.
ConnectionMultiplexer redisHostConnection = ConnectionMultiplexer.Connect(config);
IDatabase cache = redisHostConnection.GetDatabase();
async Task<string> RetrieveItemAsync(string itemKey)
{
// Attempt to retrieve the item from the Redis cache
string itemValue = await cache.StringGetAsync(itemKey);
// If the value returned is null, the item was not found in the cache
// So retrieve the item from the data source and add it to the cache
if (itemValue is null)
{
itemValue = await GetItemFromDataSourceAsync(itemKey);
await cache.StringSetAsync(itemKey, itemValue);
}
return itemValue;
}
アトミック操作とバッチ操作を実行する
複数のクライアントまたはアプリケーション インスタンスがキャッシュを共有する場合は、同時更新によるデータの破損を防ぐ必要があります。 一般的なコンカレンシー戦略については、この記事で前述した キャッシュでのコンカレンシーの管理 に関するページを参照してください。 Redis には、これらの戦略を実装するいくつかのメカニズムが用意されています。
アトミックな単一キー操作: コマンドを使用すると、1 つのステップで値が更新され、
GETとSETが個別に発行されたときに発生する競合状態が解消されます。INCR、INCRBY、DECR、DECRBYは数値をアトミックにインクリメントまたはデクリメントします。 StackExchange.Redis で、IDatabase.StringIncrementAsyncとIDatabase.StringDecrementAsyncを使用します。 これらのコマンドは、複数のクライアントが同じキーを同時に更新するカウンター、レート リミッタ、クォータ追跡に役立ちます。GETSETは、キーを新しい値にアトミックに設定し、前の値を返します。 StackExchange.Redis で、IDatabase.StringGetSetAsyncを使用します。string oldValue = await cache.StringGetSetAsync("data:counter", 0);
マルチキー操作:
MGETMSET複数の文字列値を 1 回のラウンドトリップで読み書きできるため、一度に複数のキーを操作する必要がある場合のネットワーク オーバーヘッドが軽減されます。IDatabase.StringGetAsyncメソッドとIDatabase.StringSetAsyncメソッドは、この機能をサポートするためにオーバーロードされています。// Create a list of key-value pairs var keysAndValues = new KeyValuePair<RedisKey, RedisValue>[] { new("data:key1", "value1"), new("data:key99", "value2"), new("data:key322", "value3") }; // Store the list of key-value pairs in the cache await cache.StringSetAsync(keysAndValues); ... // Find all values that match a list of keys RedisKey[] keys = ["data:key1", "data:key99", "data:key322"]; // Values should contain { "value1", "value2", "value3" } RedisValue[] values = await cache.StringGetAsync(keys);トランザクション (オプティミスティック コンカレンシー):
WATCHMULTI/でトランザクションを開始する前に、EXECコマンドを使用して 1 つ以上のキーを監視できます。 トランザクションが開始される前に監視キーが変更された場合、Redis はトランザクションを破棄し、クライアントは再試行できます。 StackExchange ライブラリは、ITransactionインターフェイスを介したトランザクションのサポートを提供します。ITransactionメソッドを使用して、IDatabase.CreateTransactionオブジェクトを作成します。ITransactionオブジェクトによって提供されるメソッドを使用して、トランザクションに対してコマンドを呼び出します。ITransactionインターフェイスは、すべてのメソッドが非同期である点を除き、IDatabaseインターフェイスによってアクセスされるメソッドに似た一連のメソッドへのアクセスを提供します。 これは、ITransaction.Executeメソッドが呼び出されたときにのみ実行されることを意味します。ITransaction.Executeメソッドが返す値は、トランザクションが正常に作成された (true) か、失敗した (false) かを示します。次のコード スニペットは、同じトランザクションの一部として 2 つのカウンターをインクリメントおよびデクリメントする例を示しています。
ITransaction transaction = cache.CreateTransaction(); var tx1 = transaction.StringIncrementAsync("data:counter1"); var tx2 = transaction.StringDecrementAsync("data:counter2"); bool result = await transaction.ExecuteAsync(); Console.WriteLine($"Transaction {(result ? "succeeded" : "failed")}"); if (result) { long increment = await tx1; long decrement = await tx2; Console.WriteLine($"Result of increment: {increment}"); Console.WriteLine($"Result of decrement: {decrement}"); }Redis トランザクションは、リレーショナル データベースのトランザクションとは異なります。
Executeメソッドは、実行するトランザクションを構成するすべてのコマンドをキューに入れます。コマンドが無効な場合、トランザクションは停止します。 すべてのコマンドが正常にキューに入っている場合、各コマンドは非同期的に実行されます。 コマンドが失敗しても、他のコマンドは引き続き処理を続行します。 コマンドが正常に完了したことを確認する必要がある場合は、前の例に示すように、対応するタスクのResultプロパティを使用して結果をフェッチします。Lua スクリプト作成 複数のキー間でアトミックである必要がある複数ステップの更新の場合は、サーバー上で Lua スクリプトを実行できます。 Redis は、他のコマンドをインターリーブすることなく、スクリプト全体を 1 つの操作として実行します。
Note
クラスター化されたデプロイでは、トランザクションまたは Lua スクリプトに関係するすべてのキーが同じハッシュ スロットに存在する必要があります。 関連するキーを併置するには、
customer:{123}:nameやcustomer:{123}:emailなどのハッシュ タグを使用します。
「ファイア アンド フォーゲット」方式のキャッシュ操作を実行する
キャッシュの更新が、ビュー カウンターのインクリメントや重要でない統計情報の更新など、アプリケーションの正確性に影響しない場合は、サーバーの応答の待機をスキップできます。 fire-and-forget キャッシュ操作では、アプリケーションはバックグラウンドタスクを開始し、その完了を待たずに次の処理に進みます。 Redis では、コマンド フラグを使用して、クライアントのラウンド トリップ待機時間を短縮する、ファイア アンド フォーゲット操作がサポートされています。
await cache.StringSetAsync("data:key1", 99);
...
cache.StringIncrement("data:key1", flags: CommandFlags.FireAndForget);
自動的に期限切れのキーを指定する
キャッシュ内のデータの有効期限の管理に関するページで説明されている有効期限戦略は、キーごとの TTL を使用して Redis に実装されます。 Redis Cache に項目を格納する場合は、タイムアウトを指定して、その後、項目が自動的に削除されるようにすることができます。
TTL コマンドを使用して、キーの有効期限が切れるまでの時間を照会することもできます。 このコマンドは、 IDatabase.KeyTimeToLive メソッドを使用して StackExchange アプリケーションで使用できます。
次のコード スニペットは、キーの有効期限を 20 秒に設定し、キーの残りの有効期間を照会する方法を示しています。
// Add a key with an expiration time of 20 seconds
await cache.StringSetAsync("data:key1", 99, TimeSpan.FromSeconds(20));
...
// Query how much time a key has left to live
// If the key has already expired, the KeyTimeToLive function returns null
TimeSpan? expiry = cache.KeyTimeToLive("data:key1");
EXPIREAT メソッドとして StackExchange ライブラリで使用できる KeyExpireAsync コマンドを使用して、有効期限を特定の日時に設定することもできます。
DateTime パラメーターを受け取ります。
await cache.StringSetAsync("data:key1", 99);
await cache.KeyExpireAsync("data:key1",
new DateTime(2026, 9, 1, 0, 0, 0, DateTimeKind.Utc));
Tip
DEL メソッドとして StackExchange ライブラリから使用できる IDatabase.KeyDeleteAsync コマンドを使用して、キャッシュから項目を手動で削除できます。
Redis は、メモリ制限に達すると、構成された削除ポリシーに従ってキーを削除します。 既定のポリシーは volatile-lruであり、TTL が設定されている最も最近使用されたキーが削除されます。 その他のポリシーには、 allkeys-lru、 volatile-random、 noeviction があります (メモリがいっぱいになると書き込み操作が失敗します)。 アプリケーションで TTL を一貫して使用するかどうか、および有効期限のないキーを保護するかどうかに基づいて、削除ポリシーを選択します。 詳細については、「 メモリ管理」を参照してください。
キャッシュされた項目を相互に関連付ける
関連する項目をキャッシュする場合、多くの場合、主キーのみでではなくリレーションシップで検索する必要があります。 たとえば、ブログ投稿をキャッシュし、"どの投稿がタグ Y を共有しているか" や "どのタグが投稿 X に属しているか" などのクエリに回答する必要がある場合があります。
Azure Managed Redis では、 RedisJSON と RediSearch を使用することをお勧めします。 キャッシュされた各項目をメタデータを含む JSON ドキュメントとして格納し、クエリする必要があるフィールドに対して RediSearch インデックスを作成します。 RediSearch は、逆引き参照、タグベースのフィルター処理、範囲クエリ、フルテキスト検索を処理します。アプリケーションで個別のインデックス構造を維持する必要はありません。
より単純なシナリオでは、Redis Sets を使用して、前方インデックスと逆インデックスを手動で作成することもできます。 投稿ごとのセット (タグを含む) とタグごとのセット (投稿 ID を含む) を格納します。
foreach (BlogPost post in posts)
{
string postTagsKey = $"blog:posts:{post.Id}:tags";
await cache.SetAddAsync(
postTagsKey, post.Tags.Select(s => (RedisValue)s).ToArray());
foreach (var tag in post.Tags)
{
await cache.SetAddAsync($"tag:{tag}:blog:posts", post.Id);
}
}
その後、 SetMembersAsyncを使用して投稿のタグを照会したり、 SetCombineAsync(SetOperation.Intersect, ...)を使用して投稿全体で共通のタグを検索したり、特定のタグのすべての投稿を検索したりできます。 トレードオフは、アプリケーションで前方セットと逆セットの両方を維持する必要があり、リレーションシップの数が増えるにつれて複雑さが増すということです。
最近アクセスしたアイテムを検索する
多くのアプリケーションでは、最近アクセスまたは表示されたアイテムを追跡する必要があります。 たとえば、ブログ サイトでは、戻ってくる訪問者に対して最近読んだ投稿が表示される場合があります。 Redis Lists は、最近性ベースのキャッシュパターンを効率的に実装する方法を提供します。 項目は、 LPUSH または RPUSHを使用してリストの末尾にプッシュし、 LPOP または RPOPを使用して削除できます。 リストの長さを制限し、無制限のメモリの増加を防ぐには、 LTRIM を使用します。
ランキングを実装する
Redis Sorted Sets (ZSET) は、各要素を数値スコアに関連付けることで順序付けされたランク付けを維持します。 Redis は順序を自動的に維持します。
ZADD は O(log N)で、 ZRANGE や ZREVRANGE などの範囲クエリは O(log N + M)であり、M は返される要素の数であるため、並べ替えられたセットは大きな項目数でも効率的です。
ランキングに項目を追加する
次の例では、ZADDを使用して SortedSetAddAsync コマンドを使用して、ブログ投稿とそのスコアをランキングに追加する方法を示します。
var db = connection.GetDatabase();
string redisKey = "blog:post_rankings";
BlogPost blogPost = ...; // The blog post being ranked
await db.SortedSetAddAsync(redisKey, blogPost.Title, blogPost.Score);
ランク付けされたアイテムを取得する
次の SortedSetRangeByRankWithScoresAsyncを使用して、項目を昇順で取得できます。
var entries = await db.SortedSetRangeByRankWithScoresAsync(redisKey);
foreach (var entry in entries)
{
Console.WriteLine($"{entry.Element}: {entry.Score}");
}
Note
SortedSetRangeByRankAsync は、スコアではなくメンバー値のみを返します。
top-N 項目を取得する
上位 10 件の投稿など、スコアが最も高い項目を取得するには、降順を使用します。
foreach (var post in await cache.SortedSetRangeByRankWithScoresAsync(
redisKey, 0, 9, Order.Descending))
{
Console.WriteLine(post);
}
スコア範囲で項目を取得する
ランクではなく、スコアの境界に基づいてアイテムのクエリを実行することもできます。
foreach (var post in await cache.SortedSetRangeByScoreWithScoresAsync(
redisKey, 5000, 100000))
{
Console.WriteLine(post);
}
ランキングが無期限に増加しないようにするには、 SortedSetRemoveRangeByRankAsync を使用して古いエントリを削除するか、時間スコープのキー (毎日または毎週のランキングなど) を使用します。
SortedSetIncrementAsync (ZINCRBY) を使用して、スコアをアトミックに更新できます。
キャッシュ セッションの状態と HTML 出力
Azure Managed Redis を使用して、ASP.NET Coreおよび ASP.NET アプリケーションのセッション状態と出力キャッシュ データを格納できます。 セッション データとレンダリングされた出力を共有 Redis ベースのキャッシュに保持すると、Azure App Service、Azure Kubernetes Service (AKS)、Azure Container Appsなど、複数のインスタンスで実行されるアプリケーションまたは仮想マシン スケール セットは、サーバー アフィニティを必要とせずに一貫したユーザー エクスペリエンスを維持できます。
Tip
パフォーマンスを最大限に高めるには、アプリケーションと Azure Managed Redis インスタンスを同じ Azure リージョンにデプロイします。
ASP.NET Core
ASP.NET Core アプリケーションでは、 IDistributedCache 抽象化とセッション ミドルウェアが使用されます。 Azure Managed Redis は、IDistributedCache パッケージを通じてMicrosoft.Extensions.Caching.StackExchangeRedisと統合されます。
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "<your-cache-name>.<region>.redis.azure.net:10000";
options.InstanceName = "app-cache:";
});
builder.Services.AddSession();
ASP.NET Core出力キャッシュ ミドルウェアでは、Redis を分散バッキング ストアとして使用することもできます。これにより、アプリケーションはすべてのインスタンスでレンダリングされたフラグメントまたはページを共有できます。 詳細については、「 Redis のコア出力キャッシュ プロバイダー ASP.NET」を参照してください。
.NET Aspire の統合
.NET Aspire アプリケーションでは、 Aspire.Hosting.Azure.Redis パッケージを使用して、アプリ ホストで Azure Managed Redis リソースを宣言できます。 コンシューミング プロジェクトは、依存性注入を通じて接続設定を自動的に受け取るため、サービス間での接続文字列の手動構成が不要になります。
// App host: declare the Azure Managed Redis resource
var cache = builder.AddAzureManagedRedis("cache");
builder.AddProject<Projects.ProductService>()
.WithReference(cache);
サービスを使用すると、他の IDistributedCache プロバイダーと同じ方法で分散キャッシュが登録されます。 詳細については、「 Redis 統合の概要」を参照してください。
高可用性、スケーラビリティ、パーティション分割
各 Azure Managed Redis インスタンスは、プライマリ/レプリカ レプリケーションを使用します。 サービスはノードの正常性を監視し、プライマリが失敗した場合にレプリカを自動的に昇格させます。 レプリケーションは非同期であるため、予期しないフェールオーバー中に、最近書き込まれた少量のデータが失われる可能性があります。 レプリケーション、フェールオーバー、および階層型キャッシュの背後にある一般的な戦略については、 高可用性とスケーラビリティの実装に 関する記事を参照し、この記事の前半のパフォーマンスを向上させます。
ローカルのメモリ内キャッシュと Azure Managed Redis を組み合わせて待機時間を短縮し、共有キャッシュに一時的に到達できない場合はフォールバックを提供できます。 Circuit-Breaker パターンと Cache-Aside パターンは、この階層化されたアプローチを管理するのに役立ちます。
1 つのノードの容量を超えるワークロードの場合、Azure Managed Redis では、複数の Redis ノード間でのデータのパーティション分割 (シャーディング) がサポートされます。 どちらのクラスタリング ポリシーでも、キーからシャードへのハッシュ、自動フェールオーバーと再同期、オンラインリシャーディング (スケールアウトとスケールイン) を使用して、ノード間でデータが自動的にシャード化されます。 Azure Managed Redis では、次の 2 つのクラスタリング ポリシーがサポートされています。
OSS クラスタリング ポリシー (既定): クライアントは適切なシャードと直接通信し、MOVED リダイレクトや ASK リダイレクトを含む OSS Redis クラスター セマンティクスに従います。 StackExchange.Redis などのクラスター対応クライアントは、これらのリダイレクトを自動的に処理します。 このポリシーは、ルーティングのオーバーヘッドを最小限に抑えます。
Redis エンタープライズ クラスタリング ポリシー: プロキシは、単一のエンドポイントを介して透過的なルーティングを提供します。 クライアントは、クラスター対応のロジックを実装したり、MOVED/ASK 応答を処理したりする必要はありません。 このポリシーを使用すると、クライアント統合が簡単になりますが、ルーティングのオーバーヘッドが少し発生します。
Azure Managed Redis ではnonclustered モードもサポートされています。このモードでは、シャーディングのない単一のプライマリ/レプリカ ペアが使用されます。 このモードは、水平方向のスケールアウトを必要としない小規模なワークロードに適しています。
Note
クライアント側ハッシュや非Microsoft プロキシなどのカスタム パーティション 分割モデルは、通常、仮想マシンまたは Kubernetes 上の自己管理 Redis デプロイでのみ必要です。 Azure Managed Redis クラスタリングでは、ルーティング、フェールオーバー、再シャーディングが自動的に処理されます。
アクティブ ジオレプリケーション
複数リージョンの可用性を確保するために、Azure Managed Redis ではアクティブ geo レプリケーションがサポートされています。アクティブ geo レプリケーションは、Azure リージョン間のインスタンスを 1 つのレプリケーション グループにリンクします。 各インスタンスは読み取りと書き込みを処理でき、変更は自動的に同期されます。 アプリケーションは、リージョン障害時に正常なインスタンスにトラフィックをリダイレクトする役割を担います。 詳細については、「アクティブ geo レプリケーションの構成」を参照してください。
データの永続化
既定では、Azure Managed Redis のキャッシュされたデータはメモリに保持され、ノードが再起動またはフェールオーバーすると失われる可能性があります。 ソース データ ストアからのキャッシュの再構築が遅いかコストがかかるワークロードの場合、Azure Managed Redis ではオプションのデータ永続化がサポートされます。
RDB (Redis データベース) スナップショットでは、 マネージド ディスクに保存された定期的なポイントインタイム スナップショットが作成されます。 RDB は通常の操作中にパフォーマンスへの影響を最小限に抑えますが、最後のスナップショット以降に書き込まれたデータは失われる可能性があります。
AOF (Append-Only ファイル) は、ディスクへのすべての書き込み操作をログに記録します。 AOF を使用すると、潜在的なデータ損失が約 1 秒の書き込みに減りますが、より大きなファイルが生成され、書き込みスループットが低下する可能性があります。
RDB と AOF の両方を一緒に使用できます。 Redis は起動時に RDB スナップショットを読み込み、ほぼ完全な復旧のために AOF ログを再生します。
Important
永続化により、ノードの障害に対する持続性が向上しますが、バックアップやディザスター リカバリーのメカニズムではありません。 重要なデータの場合は、常にソース データ ストアで権限のあるコピーを維持し、 Cache-Aside パターン を使用してキャッシュを再作成します。
構成の詳細については、「 データ永続化の構成」を参照してください。
Azure Managed Redis でキャッシュされたデータを保護する
「キャッシュされたデータの保護」のガイダンスでは、アクセス制御と転送中のデータに関する問題について説明します。 Azure Managed Redis は、次の方法でこれらの問題に対処するのに役立ちます。
プライマリ アクセス制御メカニズムとして Microsoft Entra ID 認証 を使用し、アクセスを許可する場合は最小特権の原則に従います。
トラフィックがパブリック インターネットを通過しないように、 プライベート エンドポイント を使用してネットワーク アクセスを制限します。
Azure Managed Redis は、TLS を使用して転送中のデータを暗号化し、保存データを暗号化します。
シリアル化に関する考慮事項
.NET オブジェクトを文字列値として Redis に格納する場合は、それらをシリアル化する必要があります。 シリアル化形式を選択する場合は、パフォーマンス、相互運用性、バージョン管理、ペイロード サイズのトレードオフを考慮してください。 すべてのシナリオで最速のシリアライザーは 1 つもありません。 ベンチマークはコンテキストに大きく依存しており、実際のワークロードを反映していない可能性があります。
Azure Managed Redis レベルで RedisJSON がサポートされている場合は、値全体を逆シリアル化せずに、オブジェクトをネイティブ JSON ドキュメントとして格納し、個々のフィールドにクエリを実行できます。
public static class RedisJsonExtensions
{
public static async Task<T?> GetAsync<T>(
this IDatabase cache,
string key,
string path = "$")
{
var result = await cache.ExecuteAsync("JSON.GET", key, path);
if (result.IsNull)
return default;
return JsonSerializer.Deserialize<T>(result!);
}
public static async Task SetAsync<T>(
this IDatabase cache,
string key,
T value,
TimeSpan? expiry = null,
string path = "$")
{
var json = JsonSerializer.Serialize(value);
// Store JSON document
await cache.ExecuteAsync("JSON.SET", key, path, json);
// Apply TTL if provided
if (expiry.HasValue)
{
await cache.KeyExpireAsync(key, expiry);
}
}
public static async Task<bool> ExpireAsync(
this IDatabase cache,
string key,
TimeSpan expiry)
{
return await cache.KeyExpireAsync(key, expiry);
}
}
代わりに値を Redis 文字列としてシリアル化する場合、一般的な形式オプションは次のとおりです。
JSON - 広範なクロスプラットフォーム サポートを備えた人間が判読できる形式。 最もコンパクトな形式ではありませんが、キャッシュされた項目が HTTP クライアントに直接返される場合は、余分な逆シリアル化と再シリアル化の手順が回避されるため、適切な選択肢です。
MessagePack - スキーマ要件のないコンパクトなバイナリ形式。 シリアル化オーバーヘッドが低い JSON よりも小さいペイロードを生成します。
プロトコル バッファー (protobuf) - コンパクトなペイロードを生成するスキーマ ベースのバイナリ形式。 言語固有のコードを生成するには、
.proto定義ファイルとコンパイル 手順が必要です。BSON - 日付や生のバイナリ データなど、より多くの型で JSON を拡張するバイナリ形式。 ペイロードのサイズは JSON と同等です。 アプリケーションで他の場所 (MongoDB など) で BSON を既に使用している場合は、実用的な選択肢です。
関連するリソース
アプリケーションでキャッシュを実装する場合、次のパターンが関連する場合があります。
Cache-Aside パターン: このパターンでは、データ ストアからキャッシュにオンデマンドでデータを読み込む方法について説明します。 また、キャッシュ内のデータと元のデータ ストア内のデータの整合性を維持するのにも役立ちます。
シャーディング パターン: このパターンは、大量のデータを格納してアクセスするときのスケーラビリティを向上させるために役立つ水平パーティション分割の実装に関する情報を提供します。