次の方法で共有


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

Azure Cache for Redis

オンデマンドでデータをデータ ストアからキャッシュに読み込みます。 これにより、パフォーマンスが向上するだけでなく、キャッシュに保持されているデータと基になるデータ ストアのデータの間で整合性が維持されます。

コンテキストと問題

アプリケーションは、キャッシュを使用して、データ ストアに保持されている情報への繰り返されるアクセスを向上させています。 ただし、キャッシュされたデータが常にデータ ストアと一致することを期待するのは現実的ではありません。 アプリケーションでは、キャッシュ内のデータが可能な限り up-to-date であることを確認するのに役立つ戦略を実装する必要があります。 また、この戦略では、キャッシュされたデータが古くなった場合を検出し、適切に処理できる必要があります。

解決策

多くの市販のキャッシュ システムで、リード スルーおよびライト スルー/ライト ビハインド操作が提供されています。 これらのシステムでは、アプリケーションはキャッシュを参照してデータを取得します。 データがキャッシュにない場合、アプリケーションはデータ ストアからデータを取得し、キャッシュに追加します。 キャッシュに保持されているデータに対する変更は、データ ストアにも自動的にライトバックされます。

キャッシュがこの機能を備えていない場合、キャッシュを使用してデータを維持するアプリケーションがその役割を担います。

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

キャッシュにデータを読み取って格納するための Cache-Aside パターンの使用を示すスクリーンショット。

  1. アプリケーションは、キャッシュからの読み取りを試みることで、項目が現在キャッシュに保持されているかどうかを判断します。
  2. 項目がキャッシュ内に存在しない場合 (キャッシュ ミス)、アプリケーションはデータ ストアから項目を取得します。
  3. アプリケーションによってキャッシュに項目が追加され、呼び出し元に返されます。

アプリケーションは、情報を更新したら、データ ストアに変更を加え、キャッシュ内の対応する項目を無効にすることによって、ライト スルー戦略に従うことができます。

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

問題と注意事項

このパターンの実装方法を決めるときには、以下の点に注意してください。

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

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

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

キャッシュの準備: 多くのソリューションでは、アプリケーションが起動処理の一環として必要とする可能性の高いデータをキャッシュに事前に配置します。 キャッシュ アサイド パターンは、このようなデータの一部が期限切れになったり、削除されたりした場合にも役立ちます。

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

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

セマンティック キャッシュ。 一部のワークロードでは、正確なキーではなく、セマンティックの意味に基づいてキャッシュ取得を行うことでメリットがあります。 これにより、言語モデルに送信される要求とトークンの数が減ります。 キャッシュされるデータはセマンティック等価性の利点を持ち、関連のない応答を返すリスクや、プライベートデータと機密データを含むリスクがないことを確認してください。 たとえば、"年収は何ですか" は意味的に "毎年の自宅支払いとは何ですか" と似ていますが、2 人の異なるユーザーから質問された場合、答えが同じでなく、この機密データをキャッシュに含める必要もありません。

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

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

  • キャッシュが、ネイティブのリード スルーおよびライト スルー操作を提供していない場合。
  • リソースの需要は予測できません。 このパターンを使用すると、アプリケーションはオンデマンドでデータを読み込むことができます。 アプリケーションが事前に必要とするデータに関する前提はありません。

このパターンが適していないと考えられる状況は次のとおりです。

  • データが機密またはセキュリティに関連している場合。 キャッシュが複数のアプリケーションまたはユーザー間で共有されている場合は特に、キャッシュに格納するのが不適切な場合があります。 常にデータのプライマリ ソースに移動します。
  • キャッシュされたデータ セットが静的な場合。 データが使用可能なキャッシュ領域に収まる場合は、起動時にデータを使用してキャッシュを準備し、データの期限切れを防ぐポリシーを適用します。
  • ほとんどの要求でキャッシュ ヒットが発生しない場合。 このような状況では、キャッシュをチェックしてデータを読み込むオーバーヘッドが、キャッシュの利点を上回る可能性があります。
  • Web ファームでホストされている Web アプリケーションでセッション状態情報をキャッシュする場合。 この環境では、クライアント/サーバー アフィニティに基づく依存関係の導入を避ける必要があります。

ワークロード設計

アーキテクトは、設計で Cache-Aside パターンを使用して、 Azure Well-Architected Framework の柱で説明されている目標と原則に対処する方法を評価する必要があります。 次に例を示します。

重要な要素 このパターンが柱の目標をサポートする方法
信頼性設計の決定により、ワークロードが誤動作に対して復元力を持ち、障害発生後も完全に機能する状態に回復することができます。 キャッシングはデータレプリケーションを作成し、オリジン データストアが一時的に使用できない場合、限られた方法で頻繁にアクセスされるデータのアベイラビリティを維持できます。 さらに、キャッシュに不具合がある場合は、ワークロードが元のデータストアにフォールバックすることもあります。

- RE: 05冗長性
パフォーマンスの効率化は、スケーリング、データ、コードを最適化することによって、ワークロードが効率的にニーズを満たすのに役立ちます。 キャッシュ CAB を使用すると、変更頻度が低く、一部の制約を許容できる読み取り負荷の高いデータのパフォーマンスが向上します。

- 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 メソッドは、キャッシュ アサイド パターンの実装を示しています。 このメソッドは、リード スルー手法を使用してキャッシュからオブジェクトを取得します。

オブジェクトは、整数 ID をキーとして使用して識別されます。 GetMyEntityAsync メソッドは、このキーを持つ項目をキャッシュから取得することを試みます。 一致する項目が見つかった場合、キャッシュはそれを返します。 キャッシュに一致するものがない場合、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 を使用してストアにアクセスし、キャッシュから情報を取得します。 詳細については、「.NET Core での Azure Managed Redis の作成Azure 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.
}

注意

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

このパターンを実装するときは、次の情報を参考にしてください。

  • 信頼性の高い Web アプリ パターンでは、クラウドに収束する Web アプリケーションにキャッシュ アサイド パターンを適用する方法を示します。

  • キャッシュ ガイダンス。 クラウド ソリューションでデータをキャッシュする方法と、キャッシュを実装する際に考慮すべき問題に関する追加情報を提供します。

  • Data consistency primer (データ整合性入門)。 クラウド アプリケーションは、通常、複数のデータ ストアと場所にまたがってデータを格納します。 この環境でのデータ整合性の管理と維持は、システムの重要な側面であり、特にコンカレンシーと可用性の問題が発生する可能性があります。 この入門書では、分散データの整合性に関する問題について説明し、アプリケーションがデータの可用性を維持するために最終的な整合性を実装する方法の概要を説明します。

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