ASP.NET Core の分散キャッシュ

モーシン・ナシルスマンディア

分散キャッシュは、複数のアプリ サーバーによって共有されるキャッシュであり、通常、それにアクセスするアプリサーバーに対する外部サービスとして保持されます。 分散キャッシュを使用すると、ASP.NET Core アプリのパフォーマンスとスケーラビリティを向上させることができ、アプリがクラウド サービスまたはサーバー ファームでホストされていると特に効果的です。

キャッシュされたデータが個々のアプリ サーバーに格納される他のキャッシュ シナリオと比べて、分散キャッシュにはいくつかの利点があります。

キャッシュされたデータが分散されると、データは次のようになります。

  • 複数のサーバーに対する要求の間で "コヒーレント" (一貫したもの) になります。
  • サーバーが再起動されたり、アプリがデプロイされたりしても維持されます。
  • ローカル メモリを使用しません。

分散キャッシュの構成は実装に固有です。 この記事では、SQL Server と Redis 分散キャッシュを構成する方法について説明します。 NCache (GitHub の NCache) など、サードパーティの実装も利用できます。 どの実装を選んでも、アプリとキャッシュの対話には IDistributedCache インターフェイスを使用します。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

必須コンポーネント

SQL Server の分散キャッシュを使用するには、Microsoft.Extensions.Caching.SqlServer パッケージへのパッケージ参照を追加します。

Redis の分散キャッシュを使用するには、Microsoft.Extensions.Caching.StackExchangeRedis パッケージへのパッケージ参照を追加します。

NCache の分散キャッシュを使用するには、NCache.Microsoft.Extensions.Caching.OpenSource パッケージへのパッケージ参照を追加します。

IDistributedCache インターフェイス

IDistributedCache インターフェイスから、分散キャッシュの実装内の項目を操作するための次のメソッドが提供されます。

  • GetGetAsync: 文字列キーを受け取り、キャッシュされた項目がキャッシュ内で見つかった場合は、byte[] 配列としてそれを取得します。
  • SetSetAsync: 文字列キーを使用して、項目を (byte[] 配列として) キャッシュに追加します。
  • RefreshRefreshAsync: キャッシュ内の項目をそのキーに基づいて更新し、そのスライド式有効期限のタイムアウトをリセットします (ある場合)。
  • RemoveRemoveAsync: キャッシュ項目をその文字列キーに基づいて削除します。

分散キャッシュ サービスを確立する

IDistributedCache の実装を Program.cs に登録します。 このトピックで説明するフレームワーク提供の実装には、次のものが含まれます。

分散メモリ キャッシュ

分散メモリ キャッシュ (AddDistributedMemoryCache) は、フレームワークによって提供される IDistributedCache の実装であり、項目がメモリに格納されます。 分散メモリ キャッシュは実際の分散キャッシュではありません。 キャッシュされる項目は、アプリが実行されているサーバー上のアプリ インスタンスによって格納されます。

分散メモリ キャッシュは、次のような場合に役に立つ実装です。

  • 開発とテストのシナリオ。
  • 運用環境で 1 台のサーバーが使用されていて、メモリの消費量が問題ではない場合。 分散メモリ キャッシュを実装すると、キャッシュされたデータ ストレージが抽象化されます。 これにより、後で複数のノードまたはフォールト トレランスが必要になった場合に、真の分散キャッシュ ソリューションを実装できます。

このサンプル アプリでは、Program.cs の開発環境でアプリが実行されているときに、分散メモリ キャッシュが使用されます。

builder.Services.AddDistributedMemoryCache();

分散 SQL Server キャッシュ

分散 SQL Server キャッシュの実装 (AddDistributedSqlServerCache) では、分散キャッシュで SQL Server データベースをバッキング ストアとして使用できます。 SQL Server のインスタンスに SQL Server のキャッシュ項目テーブルを作成するには、sql-cache ツールを使用できます。 このツールにより、指定した名前とスキーマでテーブルが作成されます。

sql-cache create コマンドを実行して、SQL Server にテーブルを作成します。 SQL Server のインスタンス (Data Source)、データベース (Initial Catalog)、スキーマ (dbo など)、テーブル名 (TestCache など) を指定します。

dotnet sql-cache create "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DistCache;Integrated Security=True;" dbo TestCache

ツールが正常に実行されたことを示すメッセージがログに記録されます。

Table and index were created successfully.

sql-cache ツールによって作成されるテーブルのスキーマは、次のようになります。

SqlServer キャッシュ テーブル

Note

アプリでは、SqlServerCache ではなく、IDistributedCache のインスタンスを使用してキャッシュの値を操作する必要があります。

このサンプル アプリでは、Program.cs の開発以外の環境に SqlServerCache が実装されます。

builder.Services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = builder.Configuration.GetConnectionString(
        "DistCache_ConnectionString");
    options.SchemaName = "dbo";
    options.TableName = "TestCache";
});

Note

A ConnectionString (および必要に応じて) は、 SchemaNameTableName通常、ソース管理の外部に格納されます (たとえば、 Secret Manager またはファイルに appsettings.json/appsettings.{Environment}.json 格納されます)。 接続文字列には、ソース管理システムの外部に保持する必要がある資格情報を含めることができます。

分散 Redis キャッシュ

Redis は、オープンソースのメモリ内データ ストアであり、多くの場合、分散キャッシュとして使用されます。 Azure でホストされる ASP.NET Core アプリ用に Azure Redis Cache を構成し、ローカル開発に Azure Redis Cache を使用することができます。

アプリでは、RedisCache のインスタンス (AddStackExchangeRedisCache) を使用してキャッシュの実装を構成します。

  1. Azure Cache for Redis を作成します。
  2. プライマリ接続文字列 (StackExchange.Redis) を構成にコピーします。
    • ローカル開発: シークレット マネージャーで接続文字列を保存します。
    • Azure: App Service 構成または別のセキュリティ保護されたストアに接続文字列を保存します。

次のコードにより、Azure Cache for Redis が有効になります。

builder.Services.AddStackExchangeRedisCache(options =>
 {
     options.Configuration = builder.Configuration.GetConnectionString("MyRedisConStr");
     options.InstanceName = "SampleInstance";
 });

上のコードでは、プライマリ接続文字列 (StackExchange.Redis) は MyRedisConStr というキー名で構成に保存されていたものと想定されています。

詳細については、「Azure Cache for Redis」を参照してください。

ローカル Redis キャッシュに対する別のアプローチについては、こちらの GitHub イシューをご覧ください。

分散 NCache キャッシュ

Ncache はオープンソースのメモリ内分散キャッシュであり、.NET および .NET Core でネイティブに開発されました。 NCache は、ローカル環境でも、Azure または他のホスティング プラットフォームで実行される ASP.NET Core アプリ用の分散キャッシュ クラスターとしての構成でも動作します。

ローカル コンピューターに NCache をインストールして構成する方法については、「Windows (.NET および .NET Core) 向け使用開始ガイド」をご覧ください。

NCache を構成するには:

  1. Ncache オープンソース NuGet をインストールします。
  2. client.ncconf でキャッシュ クラスターを構成します。
  3. Program.cs に次のコードを追加します。
builder.Services.AddNCacheDistributedCache(configuration =>
{
    configuration.CacheName = "democache";
    configuration.EnableLogs = true;
    configuration.ExceptionsEnabled = true;
});

分散キャッシュを使用する

インターフェイスを IDistributedCache 使用するには、アプリ内の IDistributedCache インスタンスを要求します。 インスタンスは、依存関係の挿入 (DI) によって提供されます。

サンプル アプリが開始すると、IDistributedCacheProgram.cs に挿入されます。 IHostApplicationLifetime を使用して現在の時刻がキャッシュされます (詳しくは、汎用ホストの IHostApplicationLifetime に関するページをご覧ください)。

app.Lifetime.ApplicationStarted.Register(() =>
{
    var currentTimeUTC = DateTime.UtcNow.ToString();
    byte[] encodedCurrentTimeUTC = System.Text.Encoding.UTF8.GetBytes(currentTimeUTC);
    var options = new DistributedCacheEntryOptions()
        .SetSlidingExpiration(TimeSpan.FromSeconds(20));
    app.Services.GetService<IDistributedCache>()
                              .Set("cachedTimeUTC", encodedCurrentTimeUTC, options);
});

インデックス ページで使用するため、サンプル アプリによって IDistributedCacheIndexModel に挿入されます。

インデックス ページが読み込まれるたびに、OnGetAsync でキャッシュのキャッシュ時刻がチェックされます。 キャッシュ時刻が期限切れになっていない場合は、時刻が表示されます。 キャッシュ時刻が最後にアクセスされたとき (このページが最後に読み込まれたとき) から 20 秒経過した場合、ページには "キャッシュ時刻が期限切れになった" ことが表示されます。

[Reset Cached Time]\(キャッシュ時刻のリセット\) ボタンを選んで、キャッシュ時刻を現在の時刻に直ちに更新します。 このボタンにより、OnPostResetCachedTime ハンドラー メソッドがトリガーされます。

public class IndexModel : PageModel
{
    private readonly IDistributedCache _cache;

    public IndexModel(IDistributedCache cache)
    {
        _cache = cache;
    }

    public string? CachedTimeUTC { get; set; }
    public string? ASP_Environment { get; set; }

    public async Task OnGetAsync()
    {
        CachedTimeUTC = "Cached Time Expired";
        var encodedCachedTimeUTC = await _cache.GetAsync("cachedTimeUTC");

        if (encodedCachedTimeUTC != null)
        {
            CachedTimeUTC = Encoding.UTF8.GetString(encodedCachedTimeUTC);
        }

        ASP_Environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
        if (String.IsNullOrEmpty(ASP_Environment))
        {
            ASP_Environment = "Null, so Production";
        }
    }

    public async Task<IActionResult> OnPostResetCachedTime()
    {
        var currentTimeUTC = DateTime.UtcNow.ToString();
        byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
        var options = new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(20));
        await _cache.SetAsync("cachedTimeUTC", encodedCurrentTimeUTC, options);

        return RedirectToPage();
    }
}

組み込みの実装を使用するインスタンスに対IDistributedCacheして、シングルトンまたはスコープ付き有効期間を使用する必要はありません

また、DI を使用する代わりに、IDistributedCache のインスタンスが必要な場所でそれを作成することもできますが、コードでインスタンスを作成すると、コードのテストが難しくなり、明示的な依存関係の原則に違反する可能性があります。

推奨事項

アプリに最適な IDistributedCache の実装を決定するときは、次の点を考慮してください。

  • 既存のインフラストラクチャ
  • パフォーマンス要件
  • [コスト]
  • チームのエクスペリエンス

通常、キャッシュ ソリューションではキャッシュされたデータの高速取得を実現するためにメモリ内ストレージが利用されますが、メモリは限られたリソースであり、拡張にはコストがかかります。 キャッシュにはよく使用されるデータのみを格納してください。

一般に、SQL Server キャッシュより Redis キャッシュの方が、スループットは高く、待機時間は短くなります。 ただし、通常は、キャッシュ戦略のパフォーマンス特性を判断するためにベンチマークが必要です。

SQL Server が分散キャッシュのバッキング ストアとして使用されている場合、キャッシュと、アプリの通常のデータ ストレージおよび取得に、同じデータベースを使用すると、パフォーマンスに悪影響を与える可能性があります。 分散キャッシュのバッキング ストアには、専用の SQL Server インスタンスを使用することをお勧めします。

その他の技術情報

分散キャッシュは、複数のアプリ サーバーによって共有されるキャッシュであり、通常、それにアクセスするアプリサーバーに対する外部サービスとして保持されます。 分散キャッシュを使用すると、ASP.NET Core アプリのパフォーマンスとスケーラビリティを向上させることができ、アプリがクラウド サービスまたはサーバー ファームでホストされていると特に効果的です。

キャッシュされたデータが個々のアプリ サーバーに格納される他のキャッシュ シナリオと比べて、分散キャッシュにはいくつかの利点があります。

キャッシュされたデータが分散されると、データは次のようになります。

  • 複数のサーバーに対する要求の間で "コヒーレント" (一貫したもの) になります。
  • サーバーが再起動されたり、アプリがデプロイされたりしても維持されます。
  • ローカル メモリを使用しません。

分散キャッシュの構成は実装に固有です。 この記事では、SQL Server と Redis 分散キャッシュを構成する方法について説明します。 NCache (GitHub の NCache) など、サードパーティの実装も利用できます。 どの実装を選んでも、アプリとキャッシュの対話には IDistributedCache インターフェイスを使用します。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

必須コンポーネント

SQL Server の分散キャッシュを使用するには、Microsoft.Extensions.Caching.SqlServer パッケージへのパッケージ参照を追加します。

Redis の分散キャッシュを使用するには、Microsoft.Extensions.Caching.StackExchangeRedis パッケージへのパッケージ参照を追加します。

NCache の分散キャッシュを使用するには、NCache.Microsoft.Extensions.Caching.OpenSource パッケージへのパッケージ参照を追加します。

IDistributedCache インターフェイス

IDistributedCache インターフェイスから、分散キャッシュの実装内の項目を操作するための次のメソッドが提供されます。

  • GetGetAsync: 文字列キーを受け取り、キャッシュされた項目がキャッシュ内で見つかった場合は、byte[] 配列としてそれを取得します。
  • SetSetAsync: 文字列キーを使用して、項目を (byte[] 配列として) キャッシュに追加します。
  • RefreshRefreshAsync: キャッシュ内の項目をそのキーに基づいて更新し、そのスライド式有効期限のタイムアウトをリセットします (ある場合)。
  • RemoveRemoveAsync: キャッシュ項目をその文字列キーに基づいて削除します。

分散キャッシュ サービスを確立する

IDistributedCache の実装を Startup.ConfigureServices に登録します。 このトピックで説明するフレームワーク提供の実装には、次のものが含まれます。

分散メモリ キャッシュ

分散メモリ キャッシュ (AddDistributedMemoryCache) は、フレームワークによって提供される IDistributedCache の実装であり、項目がメモリに格納されます。 分散メモリ キャッシュは実際の分散キャッシュではありません。 キャッシュされる項目は、アプリが実行されているサーバー上のアプリ インスタンスによって格納されます。

分散メモリ キャッシュは、次のような場合に役に立つ実装です。

  • 開発とテストのシナリオ。
  • 運用環境で 1 台のサーバーが使用されていて、メモリの消費量が問題ではない場合。 分散メモリ キャッシュを実装すると、キャッシュされたデータ ストレージが抽象化されます。 これにより、後で複数のノードまたはフォールト トレランスが必要になった場合に、真の分散キャッシュ ソリューションを実装できます。

このサンプル アプリでは、Startup.ConfigureServices の開発環境でアプリが実行されているときに、分散メモリ キャッシュが使用されます。

services.AddDistributedMemoryCache();

分散 SQL Server キャッシュ

分散 SQL Server キャッシュの実装 (AddDistributedSqlServerCache) では、分散キャッシュで SQL Server データベースをバッキング ストアとして使用できます。 SQL Server のインスタンスに SQL Server のキャッシュ項目テーブルを作成するには、sql-cache ツールを使用できます。 このツールにより、指定した名前とスキーマでテーブルが作成されます。

sql-cache create コマンドを実行して、SQL Server にテーブルを作成します。 SQL Server のインスタンス (Data Source)、データベース (Initial Catalog)、スキーマ (dbo など)、テーブル名 (TestCache など) を指定します。

dotnet sql-cache create "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DistCache;Integrated Security=True;" dbo TestCache

ツールが正常に実行されたことを示すメッセージがログに記録されます。

Table and index were created successfully.

sql-cache ツールによって作成されるテーブルのスキーマは、次のようになります。

SqlServer キャッシュ テーブル

Note

アプリでは、SqlServerCache ではなく、IDistributedCache のインスタンスを使用してキャッシュの値を操作する必要があります。

このサンプル アプリでは、Startup.ConfigureServices の開発以外の環境に SqlServerCache が実装されます。

services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = 
        _config["DistCache_ConnectionString"];
    options.SchemaName = "dbo";
    options.TableName = "TestCache";
});

Note

A ConnectionString (および必要に応じて) は、 SchemaNameTableName通常、ソース管理の外部に格納されます (たとえば、 Secret Manager またはファイルに appsettings.json/appsettings.{Environment}.json 格納されます)。 接続文字列には、ソース管理システムの外部に保持する必要がある資格情報を含めることができます。

分散 Redis キャッシュ

Redis は、オープンソースのメモリ内データ ストアであり、多くの場合、分散キャッシュとして使用されます。 Azure でホストされる ASP.NET Core アプリ用に Azure Redis Cache を構成し、ローカル開発に Azure Redis Cache を使用することができます。

アプリでは、RedisCache のインスタンス (AddStackExchangeRedisCache) を使用してキャッシュの実装を構成します。

  1. Azure Cache for Redis を作成します。
  2. プライマリ接続文字列 (StackExchange.Redis) を構成にコピーします。
    • ローカル開発: シークレット マネージャーで接続文字列を保存します。
    • Azure: App Service 構成または別のセキュリティ保護されたストアに接続文字列を保存します。

次のコードにより、Azure Cache for Redis が有効になります。

public void ConfigureServices(IServiceCollection services)
{
    if (_hostContext.IsDevelopment())
    {
        services.AddDistributedMemoryCache();
    }
    else
    {
        services.AddStackExchangeRedisCache(options =>
        {
            options.Configuration = _config["MyRedisConStr"];
            options.InstanceName = "SampleInstance";
        });
    }

    services.AddRazorPages();
}

上のコードでは、プライマリ接続文字列 (StackExchange.Redis) は MyRedisConStr というキー名で構成に保存されていたものと想定されています。

詳細については、「Azure Cache for Redis」を参照してください。

ローカル Redis キャッシュに対する別のアプローチについては、こちらの GitHub イシューをご覧ください。

分散 NCache キャッシュ

Ncache はオープンソースのメモリ内分散キャッシュであり、.NET および .NET Core でネイティブに開発されました。 NCache は、ローカル環境でも、Azure または他のホスティング プラットフォームで実行される ASP.NET Core アプリ用の分散キャッシュ クラスターとしての構成でも動作します。

ローカル コンピューターに NCache をインストールして構成する方法については、「Windows (.NET および .NET Core) 向け使用開始ガイド」をご覧ください。

NCache を構成するには:

  1. Ncache オープンソース NuGet をインストールします。

  2. client.ncconf でキャッシュ クラスターを構成します。

  3. Startup.ConfigureServices に次のコードを追加します。

    services.AddNCacheDistributedCache(configuration =>    
    {        
        configuration.CacheName = "demoClusteredCache";
        configuration.EnableLogs = true;
        configuration.ExceptionsEnabled = true;
    });
    

分散キャッシュを使用する

IDistributedCache インターフェイスを使用するには、アプリの任意のコンストラクターから IDistributedCache のインスタンスを要求します。 インスタンスは、依存関係の挿入 (DI) によって提供されます。

サンプル アプリが開始すると、IDistributedCacheStartup.Configure に挿入されます。 IHostApplicationLifetime を使用して現在の時刻がキャッシュされます (詳しくは、汎用ホストの IHostApplicationLifetime に関するページをご覧ください)。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, 
    IHostApplicationLifetime lifetime, IDistributedCache cache)
{
    lifetime.ApplicationStarted.Register(() =>
    {
        var currentTimeUTC = DateTime.UtcNow.ToString();
        byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
        var options = new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(20));
        cache.Set("cachedTimeUTC", encodedCurrentTimeUTC, options);
    });

インデックス ページで使用するため、サンプル アプリによって IDistributedCacheIndexModel に挿入されます。

インデックス ページが読み込まれるたびに、OnGetAsync でキャッシュのキャッシュ時刻がチェックされます。 キャッシュ時刻が期限切れになっていない場合は、時刻が表示されます。 キャッシュ時刻が最後にアクセスされたとき (このページが最後に読み込まれたとき) から 20 秒経過した場合、ページには "キャッシュ時刻が期限切れになった" ことが表示されます。

[Reset Cached Time]\(キャッシュ時刻のリセット\) ボタンを選んで、キャッシュ時刻を現在の時刻に直ちに更新します。 このボタンにより、OnPostResetCachedTime ハンドラー メソッドがトリガーされます。

public class IndexModel : PageModel
{
    private readonly IDistributedCache _cache;

    public IndexModel(IDistributedCache cache)
    {
        _cache = cache;
    }

    public string CachedTimeUTC { get; set; }

    public async Task OnGetAsync()
    {
        CachedTimeUTC = "Cached Time Expired";
        var encodedCachedTimeUTC = await _cache.GetAsync("cachedTimeUTC");

        if (encodedCachedTimeUTC != null)
        {
            CachedTimeUTC = Encoding.UTF8.GetString(encodedCachedTimeUTC);
        }
    }

    public async Task<IActionResult> OnPostResetCachedTime()
    {
        var currentTimeUTC = DateTime.UtcNow.ToString();
        byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
        var options = new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(20));
        await _cache.SetAsync("cachedTimeUTC", encodedCurrentTimeUTC, options);

        return RedirectToPage();
    }
}

Note

IDistributedCache のインスタンスに対して、シングルトンまたはスコープ設定された有効期間を使用する必要はありません (少なくとも組み込み実装の場合)。

また、DI を使用する代わりに、IDistributedCache のインスタンスが必要な場所でそれを作成することもできますが、コードでインスタンスを作成すると、コードのテストが難しくなり、明示的な依存関係の原則に違反する可能性があります。

推奨事項

アプリに最適な IDistributedCache の実装を決定するときは、次の点を考慮してください。

  • 既存のインフラストラクチャ
  • パフォーマンス要件
  • [コスト]
  • チームのエクスペリエンス

通常、キャッシュ ソリューションではキャッシュされたデータの高速取得を実現するためにメモリ内ストレージが利用されますが、メモリは限られたリソースであり、拡張にはコストがかかります。 キャッシュにはよく使用されるデータのみを格納してください。

一般に、SQL Server キャッシュより Redis キャッシュの方が、スループットは高く、待機時間は短くなります。 ただし、通常は、キャッシュ戦略のパフォーマンス特性を判断するためにベンチマークが必要です。

SQL Server が分散キャッシュのバッキング ストアとして使用されている場合、キャッシュと、アプリの通常のデータ ストレージおよび取得に、同じデータベースを使用すると、パフォーマンスに悪影響を与える可能性があります。 分散キャッシュのバッキング ストアには、専用の SQL Server インスタンスを使用することをお勧めします。

その他の技術情報