次の方法で共有


キャッシュ アサイド パターン

Azure マネージド再配布

このパターンでは、データ ストアからキャッシュにオンデマンドでデータを読み込みます。 このパターンを使用して、パフォーマンスを向上させ、キャッシュ内のデータと基になるデータ ストア内のデータの整合性を維持します。

コンテキストと問題

アプリケーションはキャッシュを使用して、データ ストア内の情報に繰り返しアクセスするためのパフォーマンスを向上させます。 ただし、キャッシュされたデータは、データ ストアと常に一貫性を保つとは限りません。 アプリケーションでは、データを可能な限り最新の状態に保つためのキャッシュ戦略を実装する必要があります。 また、この戦略では、キャッシュされたデータが古くなった場合も検出し、適切に処理する必要があります。

解決策

多くの商用キャッシュ システムでは、読み取りおよび書き込みまたは書き込みの操作が提供されます。 これらのシステムでは、アプリケーションはキャッシュを参照してデータを取得します。 データがキャッシュにない場合、アプリケーションはデータ ストアからデータを取得し、キャッシュに追加します。 システムは、キャッシュされたデータに加えられた変更をデータ ストアに自動的に書き込みます。

この機能を提供しないキャッシュの場合、キャッシュを使用するアプリケーションはデータを維持する必要があります。

アプリケーションは、Cache-Aside パターンを実装することで、読み取りスルー キャッシュの機能をエミュレートできます。 この戦略では、オンデマンドでデータをキャッシュに読み込みます。 次の図では、Cache-Aside パターンを使用してキャッシュにデータを格納します。

Cache-Aside パターンを使用してデータを読み取り、キャッシュに格納する方法を示す図。

  1. アプリケーションは、キャッシュから読み取ろうとして、項目が現在キャッシュに存在するかどうかを判断します。

  2. 項目がキャッシュ内にない場合 (キャッシュ ミスとも呼ばれます)、アプリケーションはデータ ストアから項目を取得します。

  3. アプリケーションによってキャッシュに項目が追加され、呼び出し元に返されます。

アプリケーションが情報を更新する場合は、データ ストアに変更を加え、キャッシュ内の対応する項目を無効にすることで、書き込みスルー戦略に従うことができます。

項目が再び必要になると、Cache-Aside パターンはデータ ストアから更新されたデータを取得し、キャッシュに追加します。

問題と考慮事項

このパターンを実装する方法を決定するときは、次の点を考慮してください。

  • キャッシュされたデータの有効期間: 多くのキャッシュでは、有効期限ポリシーを使用してデータを無効にし、一定の期間アクセスされていない場合はキャッシュから削除します。 キャッシュアサイドを有効にするには、有効期限ポリシーがデータを使用するアプリケーションのアクセス パターンと一致していることを確認します。 有効期限が早すぎると、アプリケーションがデータ ストアからデータを継続的に取得してキャッシュに追加する可能性があるため、有効期限を短くしないでください。 同様に、キャッシュされたデータが古くなるほど有効期限を設定しないでください。 キャッシュは、アプリケーションが頻繁に読み取る比較的静的なデータまたはデータに最適です。

  • データの削除: ほとんどのキャッシュのサイズは、データが生成されるデータ ストアと比較して限られています。 キャッシュがサイズ制限を超えると、データが削除されます。 ほとんどのキャッシュでは、削除する項目を選択するために最も最近使用されたポリシーが採用されていますが、カスタマイズが許可されるものもあります。

  • 構成: キャッシュ動作は、グローバルに構成することも、キャッシュされた項目ごとに構成することもできます。 1 つのグローバル削除ポリシーが、すべての項目に適していない場合があります。 項目の取得コストが高い場合は、キャッシュ項目を個別に構成します。 このような状況では、低コストの項目よりもアクセス頻度が低い場合でも、項目をキャッシュに保持することは理にかなっています。

  • キャッシュの準備: 多くのソリューションでは、スタートアップ処理の一環としてアプリケーションが必要とする可能性が高いデータをキャッシュに事前設定します。 Cache-Aside パターンは、このデータの一部が期限切れになったり削除されたりした場合に役立ちます。

  • 一貫性: Cache-Aside パターンでは、データ ストアとキャッシュの間の整合性は保証されません。 たとえば、外部プロセスでは、データ ストア内の項目をいつでも変更できます。 この変更は、項目が再び読み込まれるまでキャッシュに表示されません。 データ ストア間でデータをレプリケートするシステムでは、同期が頻繁に行われると、一貫性が困難になる可能性があります。

  • ローカル キャッシュ: キャッシュは、アプリケーション インスタンスに対してローカルにして、メモリ内に格納できます。 キャッシュアサイドは、アプリケーションが同じデータに繰り返しアクセスする場合に、この環境で適切に機能します。 ただし、ローカル キャッシュはプライベートであるため、異なるアプリケーション インスタンスがそれぞれ同じキャッシュ データのコピーを持つことができます。 このデータはキャッシュ間ですぐに矛盾する可能性があるため、プライベート キャッシュ内のデータの有効期限を切り、より頻繁に更新することが必要になる場合があります。 これらのシナリオでは、共有キャッシュまたは分散キャッシュ メカニズムの使用を検討してください。

  • セマンティック キャッシュ: 一部のワークロードでは、正確なキーではなく、セマンティックの意味に基づいてキャッシュ取得を行うことでメリットがあります。 この方法により、言語モデルに送信される要求とトークンの数が減ります。 セマンティック キャッシュを使用するのは、データがセマンティック等価性をサポートし、関連のない応答を返すリスクがない場合、およびプライベートデータと機密データが含まれていない場合のみです。 たとえば、"年収は何ですか" は、意味的に "年単位の持ち帰り給与とは何か" と似ています。しかし、異なるユーザーがこれらの質問をする場合は、回答が異なる必要があります。 また、この機密データをキャッシュに含めてはいけません。

このパターンを使用する状況

このパターンは次の状況で使用します。

  • キャッシュはネイティブのリードスルーおよびライトスルー操作を提供しません。

  • リソースの需要は予測できません。 このパターンを使用すると、アプリケーションはオンデマンドでデータを読み込むことができます。 アプリケーションで事前に必要なデータは想定していません。

このパターンは、次の場合に適さない場合があります。

  • データは機密またはセキュリティ関連です。 キャッシュにデータを格納することは不適切な場合があります。特に、複数のアプリケーションまたはユーザーがキャッシュを共有する場合です。 常にプライマリ ソースからこの種類のデータを取得します。

  • キャッシュされたデータ セットは静的です。 データが使用可能なキャッシュ領域に収まる場合は、起動時にデータを使用してキャッシュを準備し、データの期限切れを防ぐポリシーを適用します。

  • ほとんどの要求では、キャッシュ ヒットは発生しません。 このような状況では、キャッシュをチェックしてデータを読み込むオーバーヘッドが、キャッシュの利点を上回る可能性があります。

  • セッション状態情報は、Web ファームでホストされている Web アプリケーションにキャッシュします。 この環境では、クライアントとサーバーのアフィニティに基づいて依存関係が導入されないようにします。

ワークロード設計

ワークロードの設計で Cache-Aside パターンを使用して、 Azure Well-Architected Framework の柱で説明されている目標と原則に対処する方法を評価します。 次の表は、このパターンが各柱の目標をサポートする方法に関するガイダンスを示しています。

このパターンが柱の目標をサポートする方法
信頼性設計の決定は、故障に対するワークロードの回復性を高め、障害の発生後にワークロードを完全な機能状態に回復させるために役立ちます。 キャッシュによってデータがレプリケートされます。 限られた方法で、配信元データ ストアが一時的に使用できなくなった場合に、頻繁にアクセスされるデータの可用性を維持できます。 キャッシュが正常に動作しない場合、ワークロードは配信元データ ストアにフォールバックする可能性があります。

- RE: 05冗長性
パフォーマンス効率 は、スケーリング、データ、およびコードの最適化を通じて、ワークロード の需要を効率的に満たすのに役立ちます。 キャッシュを使用すると、頻繁に変更されない読み取り負荷の高いデータのパフォーマンスが向上し、一部の制約が許容されます。

- PE:08 データパフォーマンス
- PE: 12 パフォーマンスの継続的な最適化

このパターンによって柱内にトレードオフが生じる場合は、他の柱の目標に照らして検討してください。

Azure Managed Redis を使用して、複数のアプリケーション インスタンスが共有できる分散キャッシュを作成することを検討してください。

次の例では、.NET 用に記述された Redis クライアント ライブラリである StackExchange.Redis クライアントを使用します。 Azure Managed Redis インスタンスに接続するには、静的 ConnectionMultiplexer.Connect メソッドを呼び出し、接続文字列を渡します。 このメソッドは、接続を表す ConnectionMultiplexer を返します。

アプリケーションで ConnectionMultiplexer インスタンスを共有する方法の 1 つは、次の例のように、接続されたインスタンスを返す静的プロパティを持つことです。 この方法では、接続された 1 つのインスタンスだけがスレッドセーフな方法で初期化されます。

private static ConnectionMultiplexer Connection;

// Redis connection string information
private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
    string cacheConnection = ConfigurationManager.AppSettings["CacheConnection"].ToString();
    return ConnectionMultiplexer.Connect(cacheConnection);
});

public static ConnectionMultiplexer Connection => lazyConnection.Value;

次の例の GetMyEntityAsync メソッドは、Cache-Aside パターンの実装を示しています。 このメソッドは、読み取りスルーアプローチを使用して、キャッシュからオブジェクトを取得します。

このメソッドは、キーとして整数 ID を使用してオブジェクトを識別します。 このキーを使用して、キャッシュから項目を取得しようとします。 キャッシュに一致する項目が含まれている場合、その項目が返されます。 キャッシュに一致するものが含まれていない場合、 GetMyEntityAsync メソッドはデータ ストアからオブジェクトを取得し、キャッシュに追加して返します。 この例では、データ ストアからデータを読み取るコードを省略します。これは、そのロジックがデータ ストアに依存するためです。 キャッシュされた項目は、別のサービスまたはプロセスによって更新された場合に古くなるのを防ぐために、期限切れになります。

// Set five minute expiration as a default
private const double DefaultExpirationTimeInMinutes = 5.0;

public async Task<MyEntity> GetMyEntityAsync(int id)
{
  // Define a unique key for this method and its parameters.
  var key = $"MyEntity:{id}";
  var cache = Connection.GetDatabase();

  // Try to get the entity from the cache.
  var json = await cache.StringGetAsync(key).ConfigureAwait(false);
  var value = string.IsNullOrWhiteSpace(json)
                ? default(MyEntity)
                : JsonConvert.DeserializeObject<MyEntity>(json);

  if (value == null) // Cache miss
  {
    // If there's a cache miss, get the entity from the original store and cache it.
    // Code has been omitted because it is data store dependent.
    value = ...;

    // Avoid caching a null value.
    if (value != null)
    {
      // Put the item in the cache with a custom expiration time that
      // depends on how critical it is to have stale data.
      await cache.StringSetAsync(key, JsonConvert.SerializeObject(value)).ConfigureAwait(false);
      await cache.KeyExpireAsync(key, TimeSpan.FromMinutes(DefaultExpirationTimeInMinutes)).ConfigureAwait(false);
    }
  }

  return value;
}

注意

この例では、Azure Managed Redis を使用してストアにアクセスし、キャッシュから情報を取得します。 詳細については、 Azure Managed Redis インスタンスの作成.NET Core での Azure Managed Redis の使用に関するページを参照してください。

次の UpdateEntityAsync メソッドは、アプリケーションが値を変更したときにキャッシュ内のオブジェクトを無効にする方法を示しています。 このコードでは、元のデータ ストアを更新した後、キャッシュ項目をキャッシュから削除します。

public async Task UpdateEntityAsync(MyEntity entity)
{
    // Update the object in the original data store.
    await this.store.UpdateEntityAsync(entity).ConfigureAwait(false);

    // Invalidate the current cache object.
    var cache = Connection.GetDatabase();
    var id = entity.Id;
    var key = $"MyEntity:{id}"; // The key for the cached object.
    await cache.KeyDeleteAsync(key).ConfigureAwait(false); // Delete this key from the cache.
}

注意

ステップの順序が重要です。 項目をキャッシュから削除する "前に" データ ストアを更新します。 キャッシュされた項目を最初に削除した場合、データ ストアが更新される前に、クライアントがアイテムをフェッチする時間が少しあります。 この状況では、項目がキャッシュ内にないため、フェッチによってキャッシュ ミスが発生します。 キャッシュ ミスにより、アプリケーションはデータ ストアから古い項目を取得し、キャッシュに追加し直します。 このシーケンスは、キャッシュ内の古いデータにつながります。

次のステップ

  • データ整合性入門: この入門では、分散データ間の整合性に関する問題について説明します。 また、アプリケーションが最終的な整合性を実装してデータの可用性を維持する方法についても説明します。 クラウド アプリケーションは、通常、複数のデータ ストアと場所にまたがってデータを格納します。 この環境では、特にコンカレンシーと可用性の問題が発生する可能性があるため、データの整合性を効率的に管理および維持する必要があります。

  • セマンティック キャッシュとして Azure Managed Redis を使用する: このチュートリアルでは、Azure Managed Redis を使用してセマンティック キャッシュを実装する方法について説明します。

  • Reliable Web App パターン: このパターンは、クラウド内の Web アプリケーションに Cache-Aside パターンを適用します。

  • キャッシュ ガイダンス: このガイダンスでは、クラウド ソリューションにデータをキャッシュする方法と、キャッシュを実装するときに考慮すべき問題について詳しく説明します。