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"
サービスを登録する
HybridCache
を呼び出して、 サービスをAddHybridCache
コンテナーに追加します。
// 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 つだけがファクトリ メソッドを呼び出し、他のすべての呼び出し元はその呼び出しの結果を待機することを保証します。 CancellationToken
に渡される GetOrCreateAsync
は、すべての同時呼び出し元のまとめてのキャンセルを表します。
メインの 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 クラス。
- HybridCacheEntryOptions クラス。
制限
HybridCacheOptions
の次のプロパティを使うと、すべてのキャッシュ エントリに適用される制限を構成できます。
- MaximumPayloadBytes: キャッシュ エントリの最大サイズ。 既定値は 1 MB です。 このサイズを超えて値を保存しようという試みはログされ、その値はキャッシュに保存されません。
- MaximumKeyLength: キャッシュ キーの最大長。 既定値は 1,024 文字です。 このサイズを超えて値を保存しようという試みはログされ、その値はキャッシュに保存されません。
シリアル化
セカンダリのアウトプロセス キャッシュを使用するには、シリアル化が必要です。 シリアル化は、HybridCache
サービスの登録の一環として構成されます。 AddSerializer
呼び出しからチェーンされた AddSerializerFactory
および AddHybridCache
メソッドを介して、型固有シリアライザーと汎用シリアライザーを構成できます。 既定では、このライブラリは string
と byte[]
を内部的に処理し、その他すべてに 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 の [概要] ページで [アクセス キーの表示] を選択して、接続文字列を見つけます。
接続文字列をアプリの構成に格納します。 たとえば、 セクションに接続文字列を含む次の JSON のような
ConnectionStrings
を使用します。<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
サービスはインプロセス キャッシュと スタンピード保護を提供します。
手記
キーまたはタグによってキャッシュ エントリを無効にすると、現在のサーバーとセカンダリアウトプロセス ストレージで無効になります。 ただし、他のサーバーのメモリ内キャッシュは影響を受けません。
パフォーマンスを最適化する
パフォーマンスを最適化するには、オブジェクトを再利用し、HybridCache
の割り当てが行われないように、byte[]
を構成します。
オブジェクトを再利用する
インスタンスを再利用することで、HybridCache
は、呼び出しごとの逆シリアル化に関連する CPU とオブジェクトの割り当てのオーバーヘッドを削減できます。 この結果、キャッシュされたオブジェクトが大きいか頻繁にアクセスされるシナリオでパフォーマンスが向上する可能性があります。
IDistributedCache
を使用する一般的な既存のコードでは、キャッシュからオブジェクトを取得するたびに、逆シリアル化が行われます。 この動作は、同時呼び出し元のそれぞれがオブジェクトの個別のインスタンスを取得し、それらは他のインスタンスとやり取りを行うことができないことを意味します。 この結果、同じオブジェクト インスタンスに同時に変更を加えるリスクがなくなるため、スレッド セーフになります。
HybridCache
の使用の多くは既存の IDistributedCache
コードを作り変えたものになるため、同時実行によるバグの発生を防ぐために HybridCache
は既定でこの動作を引き継ぎます。 ただし、次の場合、オブジェクトは本質的にスレッドセーフです。
- 不変型である。
- コードでそれを変更しない。
このような場合、次の方法により、インスタンスを再利用しても安全であることを HybridCache
に通知します。
- 型を
sealed
としてマークします。 C# のsealed
キーワードは、クラスを継承できないことを意味します。 - その型に
[ImmutableObject(true)]
属性を追加します。[ImmutableObject(true)]
属性は、オブジェクトの作成後にその状態を変更できないことを示します。
byte[]
の割り当てを行わない
HybridCache
には、IDistributedCache
の割り当てを回避するため、byte[]
の実装に対するオプションの API も用意されています。 この機能は、Microsoft.Extensions.Caching.StackExchangeRedis
および Microsoft.Extensions.Caching.SqlServer
パッケージのプレビュー バージョンによって実装されます。 詳細については、IBufferDistributedCache を参照してください。パッケージをインストールする .NET CLI コマンドを次に示します。
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
dotnet add package Microsoft.Extensions.Caching.SqlServer
カスタム HybridCache の実装
HybridCache
抽象クラスの具象実装は、共有フレームワークに含まれており、依存関係の挿入によって提供されます。 ただし、開発者は API のカスタム実装を提供してかまいません。
互換性
HybridCache
ライブラリでは、.NET Framework 4.7.2 および .NET Standard 2.0 までの古い .NET ランタイムがサポートされています。
その他のリソース
HybridCache
について詳しくは、次のリソースをご覧ください。
- GitHub イシュー dotnet/aspnetcore #54647。
HybridCache
ソース コード
ASP.NET Core