ASP.NET Core での HybridCache ライブラリ

重要

HybridCacheは現在プレビュー段階ですが、.NET 拡張機能の今後のマイナー リリース .NET 9.0 以降で完全にリリースされる予定です。

この記事では、ASP.NET Core アプリで HybridCache ライブラリを構成して使う方法について説明します。 このライブラリの概要については、キャッシュの概要についての記事の HybridCache に関するセクションをご覧ください。

ライブラリの入手

Microsoft.Extensions.Caching.Hybrid パッケージをインストールします。

dotnet add package Microsoft.Extensions.Caching.Hybrid --version "9.0.0-preview.7.24406.2"

サービスを登録する

AddHybridCache を呼び出して、HybridCache サービスを依存関係の挿入 (DI) コンテナーに追加します。

// Add services to the container.
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddAuthorization();

builder.Services.AddHybridCache();

上記のコードは、HybridCache サービスを既定のオプションで登録します。 登録 API では、オプションシリアル化を構成することもできます。

キャッシュ エントリを取得して格納する

HybridCache サービスで提供される GetOrCreateAsync メソッドには、キーと次のものを受け取る 2 つのオーバーロードがあります。

  • ファクトリ メソッド。
  • 状態とファクトリ メソッド。

このメソッドは、キーを使って、プライマリ キャッシュからオブジェクトの取得を試みます。 アイテムがプライマリ キャッシュ内に見つからない場合 (キャッシュ ミス)、セカンダリ キャッシュが確認されます (セカンダリが構成されている場合)。 そこにデータが見つからない場合 (別のキャッシュ ミス)、ファクトリ メソッドを呼び出してデータ ソースからオブジェクトを取得します。 その後、プライマリとセカンダリ両方のキャッシュにオブジェクトを格納します。 プライマリまたはセカンダリどちらかのキャッシュでオブジェクトが見つかった場合 (キャッシュ ヒット)、ファクトリ メソッドは呼び出されません。

HybridCache サービスは、特定のキーに対する同時呼び出し元の内 1 つだけがファクトリ メソッドを呼び出し、他のすべての呼び出し元はその呼び出しの結果を待機することを保証します。 GetOrCreateAsync に渡される CancellationToken は、すべての同時呼び出し元のまとめてのキャンセルを表します。

メインの GetOrCreateAsync オーバーロード

GetOrCreateAsync のステートレス オーバーロードは、ほとんどのシナリオに推奨されます。 それを呼び出すコードは比較的単純です。 次に例を示します。

public class SomeService(HybridCache cache)
{
    private HybridCache _cache = cache;

    public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
    {
        return await _cache.GetOrCreateAsync(
            $"{name}-{id}", // Unique key to the cache entry
            async cancel => await GetDataFromTheSourceAsync(name, id, cancel),
            cancellationToken: token
        );
    }

    public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
    {
        string someInfo = $"someinfo-{name}-{id}";
        return someInfo;
    }
}

代替の GetOrCreateAsync オーバーロード

代替オーバーロードを使うと、キャプチャした変数とインスタンスごとのコールバックによるオーバーヘッドが軽減される可能性がありますが、代わりにコードは複雑になります。 ほとんどのシナリオでは、コードの複雑さを上回るほどパフォーマンスは向上しません。 代替オーバーロードを使用する例を次に示します。

public class SomeService(HybridCache cache)
{
    private HybridCache _cache = cache;

    public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
    {
        return await _cache.GetOrCreateAsync(
            $"{name}-{id}", // Unique key to the cache entry
            (name, id, obj: this),
            static async (state, token) =>
            await state.obj.GetDataFromTheSourceAsync(state.name, state.id, token),
            cancellationToken: token
        );
    }

    public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
    {
        string someInfo = $"someinfo-{name}-{id}";
        return someInfo;
    }
}

SetAsync メソッド

多くのシナリオでは、必要な API は GetOrCreateAsync のみです。 ただし、HybridCache には、最初に取得を試みずにオブジェクトをキャッシュに格納する SetAsync もあります。

キーを使用してキャッシュ エントリを削除する

キャッシュ エントリの基になるデータが期限切れになる前に変化した場合は、そのエントリのキーを使用して RemoveAsync を呼び出すことでエントリを明示的に削除します。 オーバーロードでは、キー値のコレクションを指定できます。

エントリが削除される際には、プライマリとセカンダリ両方のキャッシュから削除されます。

タグを使用してキャッシュ エントリを削除する

タグを使用すると、キャッシュ エントリをグループ化し、それらをまとめて無効にすることができます。

次の例に示すように、GetOrCreateAsync を呼び出すときにタグを設定します。

public class SomeService(HybridCache cache)
{
    private HybridCache _cache = cache;

    public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
    {
        var tags = new List<string> { "tag1", "tag2", "tag3" };
        var entryOptions = new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromMinutes(1),
            LocalCacheExpiration = TimeSpan.FromMinutes(1)
        };
        return await _cache.GetOrCreateAsync(
            $"{name}-{id}", // Unique key to the cache entry
            async cancel => await GetDataFromTheSourceAsync(name, id, cancel),
            entryOptions,
            tags,
            cancellationToken: token
        );
    }
    
    public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
    {
        string someInfo = $"someinfo-{name}-{id}";
        return someInfo;
    }
}

タグ値を使用して RemoveByTagAsync を呼び出すことで、指定したタグのすべてのエントリを削除します。 オーバーロードでは、タグ値のコレクションを指定できます。

エントリが削除される際には、プライマリとセカンダリ両方のキャッシュから削除されます。

[オプション]

AddHybridCache メソッドを使って、グローバルな既定値を構成できます。 次の例では、使用可能なオプションの一部を構成する方法を示します。

// Add services to the container.
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization();

builder.Services.AddHybridCache(options =>
    {
        options.MaximumPayloadBytes = 1024 * 1024;
        options.MaximumKeyLength = 1024;
        options.DefaultEntryOptions = new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromMinutes(5),
            LocalCacheExpiration = TimeSpan.FromMinutes(5)
        };
    });

GetOrCreateAsync メソッドは、HybridCacheEntryOptions オブジェクトを受け取って、特定のキャッシュ エントリのグローバル既定値をオーバーライドすることもできます。 次に例を示します。

public class SomeService(HybridCache cache)
{
    private HybridCache _cache = cache;

    public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
    {
        var tags = new List<string> { "tag1", "tag2", "tag3" };
        var entryOptions = new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromMinutes(1),
            LocalCacheExpiration = TimeSpan.FromMinutes(1)
        };
        return await _cache.GetOrCreateAsync(
            $"{name}-{id}", // Unique key to the cache entry
            async cancel => await GetDataFromTheSourceAsync(name, id, cancel),
            entryOptions,
            tags,
            cancellationToken: token
        );
    }
    
    public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
    {
        string someInfo = $"someinfo-{name}-{id}";
        return someInfo;
    }
}

オプションについて詳しくは、ソース コードを参照してください。

制限

HybridCacheOptions の次のプロパティを使うと、すべてのキャッシュ エントリに適用される制限を構成できます。

  • MaximumPayloadBytes: キャッシュ エントリの最大サイズ。 既定値は 1 MB です。 このサイズを超えて値を保存しようという試みはログされ、その値はキャッシュに保存されません。
  • MaximumKeyLength: キャッシュ キーの最大長。 既定値は 1,024 文字です。 このサイズを超えて値を保存しようという試みはログされ、その値はキャッシュに保存されません。

シリアル化

セカンダリのアウトプロセス キャッシュを使用するには、シリアル化が必要です。 シリアル化は、HybridCache サービスの登録の一環として構成されます。 AddHybridCache 呼び出しからチェーンされた AddSerializer および AddSerializerFactory メソッドを介して、型固有シリアライザーと汎用シリアライザーを構成できます。 既定では、このライブラリは stringbyte[] を内部的に処理し、その他すべてに System.Text.Json を使用します。 HybridCache は、protobuf や XML など、他の種類のシリアライザーを使用することもできます。

次の例では、型固有の protobuf シリアライザーを使用するようにサービスを構成します。

// Add services to the container.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization();

builder.Services.AddHybridCache(options =>
    {
        options.DefaultEntryOptions = new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromSeconds(10),
            LocalCacheExpiration = TimeSpan.FromSeconds(5)
        };
    }).AddSerializer<SomeProtobufMessage, 
        GoogleProtobufSerializer<SomeProtobufMessage>>();

次の例では、多くの protobuf 型を処理できる汎用 protobuf シリアライザーを使用するようにサービスを構成します。

// Add services to the container.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization();

builder.Services.AddHybridCache(options =>
{
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromSeconds(10),
        LocalCacheExpiration = TimeSpan.FromSeconds(5)
    };
}).AddSerializerFactory<GoogleProtobufSerializerFactory>();

セカンダリ キャッシュには、Redis や SqlServer などのデータ ストアが必要です。 たとえば、Azure Cache for Redis を使用するには次の操作を行います。

  • Microsoft.Extensions.Caching.StackExchangeRedis パッケージをインストールします。

  • Azure Cache for Redis のインスタンスを作成します。

  • Redis インスタンスに接続するための接続文字列を取得します。 Azure portal の [概要] ページで [アクセス キーの表示] を選択して、接続文字列を見つけます。

  • 接続文字列をアプリの構成に格納します。 たとえば、ConnectionStrings セクションに接続文字列を含む次の JSON のようなユーザー シークレット ファイルを使用します。 <the connection string> を実際の接続文字列に置き換えます。

    {
      "ConnectionStrings": {
        "RedisConnectionString": "<the connection string>"
      }
    }
    
  • DI に、Redis パッケージで提供される IDistributedCache 実装を登録します。 そのためには、AddStackExchangeRedisCache を呼び出し、接続文字列を渡します。 次に例を示します。

    builder.Services.AddStackExchangeRedisCache(options =>
    {
        options.Configuration = 
            builder.Configuration.GetConnectionString("RedisConnectionString");
    });
    
  • これで、Redis IDistributedCache 実装をアプリの DI コンテナーから使用できるようになりました。 HybridCache は、その実装をセカンダリ キャッシュとして使用し、それ用に構成されたシリアライザーを使用します。

詳細については、HybridCache シリアル化サンプル アプリを参照してください。

キャッシュ ストレージ

既定では、HybridCache はプライマリ キャッシュ ストレージに MemoryCache を使います。 キャッシュ エントリはプロセス内で保存されるため、サーバーごとに、サーバー プロセスが再起動されるたびに失われる個別のキャッシュがあります。 Redis や SQL Server などのセカンダリ プロセス外ストレージの場合、HybridCache は、IDistributedCache の実装が構成されている場合はそれを使います。 ただし、IDistributedCache の実装がなくても、HybridCache サービスはプロセス内キャッシュとスタンピード保護を提供します。

パフォーマンスを最適化する

パフォーマンスを最適化するには、オブジェクトを再利用し、byte[] の割り当てが行われないように、HybridCache を構成します。

オブジェクトを再利用する

インスタンスを再利用することで、HybridCache は、呼び出しごとの逆シリアル化に関連する CPU とオブジェクトの割り当てのオーバーヘッドを削減できます。 この結果、キャッシュされたオブジェクトが大きいか頻繁にアクセスされるシナリオでパフォーマンスが向上する可能性があります。

IDistributedCache を使用する一般的な既存のコードでは、キャッシュからオブジェクトを取得するたびに、逆シリアル化が行われます。 この動作は、同時呼び出し元のそれぞれがオブジェクトの個別のインスタンスを取得し、それらは他のインスタンスとやり取りを行うことができないことを意味します。 この結果、同じオブジェクト インスタンスに同時に変更を加えるリスクがなくなるため、スレッド セーフになります。

HybridCache の使用の多くは既存の IDistributedCache コードを作り変えたものになるため、同時実行によるバグの発生を防ぐために HybridCache は既定でこの動作を引き継ぎます。 ただし、次の場合、オブジェクトは本質的にスレッドセーフです。

  • 不変型である。
  • コードでそれを変更しない。

このような場合、次の方法により、インスタンスを再利用しても安全であることを HybridCache に通知します。

  • 型を sealed としてマークします。 C# の sealed キーワードは、クラスを継承できないことを意味します。
  • その型に [ImmutableObject(true)] 属性を追加します。 [ImmutableObject(true)] 属性は、オブジェクトの作成後にその状態を変更できないことを示します。

byte[] の割り当てを行わない

HybridCache には、byte[] の割り当てを回避するため、IDistributedCache の実装に対するオプションの API も用意されています。 この機能は、Microsoft.Extensions.Caching.StackExchangeRedis および Microsoft.Extensions.Caching.SqlServer パッケージのプレビュー バージョンによって実装されます。 詳細については、IBufferDistributedCache を参照してください。パッケージをインストールする .NET CLI コマンドを次に示します。

dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis --prerelease
dotnet add package Microsoft.Extensions.Caching.SqlServer --prerelease

カスタム HybridCache の実装

HybridCache 抽象クラスの具象実装は、共有フレームワークに含まれており、依存関係の挿入によって提供されます。 ただし、開発者は API のカスタム実装を提供してかまいません。

互換性

HybridCache ライブラリでは、.NET Framework 4.7.2 および .NET Standard 2.0 までの古い .NET ランタイムがサポートされています。

その他のリソース

HybridCache について詳しくは、次のリソースをご覧ください。