HybridCache

キャッシュライブラリ.NETC#Microsoftメモリキャッシュ分散キャッシュパフォーマンス

GitHub概要

dotnet/aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.

ホームページ:https://asp.net
スター37,296
ウォッチ1,431
フォーク10,465
作成日:2014年3月11日
言語:C#
ライセンス:MIT License

トピックス

aspnetcoredotnethacktoberfesthelp-wanted

スター履歴

dotnet/aspnetcore Star History
データ取得日時: 2025/10/22 10:05

キャッシュライブラリ

HybridCache

概要

HybridCacheは、.NET 9で導入されたMicrosoftの新しいキャッシュライブラリで、メモリ内キャッシュ(L1)と分散キャッシュ(L2)を組み合わせた統合ソリューションです。

詳細

HybridCacheは、従来のIMemoryCacheとIDistributedCacheの制限を解決するために設計された次世代キャッシュライブラリです。L1キャッシュとして高速なメモリ内キャッシュを使用し、L2キャッシュとしてRedisやSQL Server等の分散キャッシュを自動的に活用します。キャッシュスタンピード問題を防ぐため、同じデータに対する複数の同時リクエストが発生した場合、最初のリクエストのみが処理を実行し、他のリクエストはその結果を待機する仕組みを提供します。タグベースの無効化機能により、関連するキャッシュエントリを効率的に削除できます。.NET Framework 4.7.2から.NET 9まで幅広いランタイムをサポートし、従来のキャッシュAPIからのドロップイン置換として設計されています。自動的なオブジェクトシリアル化、カスタムシリアライザーサポート、設定可能な有効期限オプションを提供します。

メリット・デメリット

メリット

  • 統合API: メモリ内キャッシュと分散キャッシュを統一APIで操作
  • キャッシュスタンピード防止: 同じデータへの複数リクエストを効率的に処理
  • L1/L2階層構造: 高速なメモリキャッシュと大容量分散キャッシュの最適な組み合わせ
  • タグベース無効化: 関連するキャッシュエントリを一括削除
  • 自動シリアル化: オブジェクトの自動変換とカスタムシリアライザーサポート
  • 幅広い互換性: .NET Framework 4.7.2から最新.NETまでサポート
  • 設定可能: 豊富な設定オプションと柔軟な有効期限管理

デメリット

  • 新しいライブラリ: .NET 9が必要(下位互換性はあるが制限あり)
  • 学習コスト: 新しいAPIと概念の習得が必要
  • 複雑性: L1/L2キャッシュの管理が複雑な場合がある
  • 依存関係: Microsoft.Extensions.Caching.Hybridパッケージが必要
  • 設定要求: 最適なパフォーマンスには適切な設定が必要

主要リンク

書き方の例

基本的な設定とDI登録

// Program.cs
using Microsoft.Extensions.Caching.Hybrid;

var builder = WebApplication.CreateBuilder(args);

// HybridCacheの登録
builder.Services.AddHybridCache();

// Redisを分散キャッシュとして使用
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";
});

var app = builder.Build();

基本的なキャッシュ操作

public class ProductService
{
    private readonly HybridCache _cache;
    
    public ProductService(HybridCache cache)
    {
        _cache = cache;
    }
    
    public async Task<Product> GetProductAsync(int id, CancellationToken cancellationToken = default)
    {
        return await _cache.GetOrCreateAsync(
            $"product:{id}", // キャッシュキー
            async cancel => await FetchProductFromDatabaseAsync(id, cancel), // ファクトリメソッド
            token: cancellationToken
        );
    }
}

有効期限とオプション設定

public class UserService
{
    private readonly HybridCache _cache;
    
    public UserService(HybridCache cache)
    {
        _cache = cache;
    }
    
    public async Task<List<User>> GetUsersAsync(string category)
    {
        var options = new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromMinutes(30), // L1とL2の共有有効期限
            LocalCacheExpiration = TimeSpan.FromMinutes(5) // L1のみの有効期限
        };
        
        return await _cache.GetOrCreateAsync(
            $"users:category:{category}",
            async cancel => await GetUsersFromDatabaseAsync(category, cancel),
            options,
            token: CancellationToken.None
        );
    }
}

詳細設定とカスタマイズ

// Startup/Program.cs
builder.Services.AddHybridCache(options =>
{
    // 最大ペイロードサイズ(10MB)
    options.MaximumPayloadBytes = 1024 * 1024 * 10;
    
    // 最大キー長
    options.MaximumKeyLength = 512;
    
    // デフォルト有効期限設定
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(30),
        LocalCacheExpiration = TimeSpan.FromMinutes(5)
    };
});

カスタムシリアライザーの実装

public class CustomProductSerializer : IHybridCacheSerializer<Product>
{
    public Product Deserialize(ReadOnlySequence<byte> source)
    {
        // カスタムデシリアル化ロジック
        return JsonSerializer.Deserialize<Product>(source);
    }
    
    public void Serialize(Product value, IBufferWriter<byte> target)
    {
        // カスタムシリアル化ロジック
        JsonSerializer.Serialize(target, value);
    }
}

// 登録
builder.Services.AddHybridCache()
    .AddSerializer<Product, CustomProductSerializer>();

タグベース無効化

public class CacheInvalidationService
{
    private readonly HybridCache _cache;
    
    public CacheInvalidationService(HybridCache cache)
    {
        _cache = cache;
    }
    
    public async Task InvalidateProductCacheAsync(int categoryId)
    {
        // 特定のタグを持つすべてのキャッシュエントリを無効化
        await _cache.RemoveByTagAsync($"category:{categoryId}", CancellationToken.None);
    }
    
    public async Task SetProductWithTagsAsync(Product product)
    {
        var options = new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromMinutes(30)
        };
        
        // タグ付きでキャッシュに保存
        await _cache.SetAsync(
            $"product:{product.Id}",
            product,
            options,
            new[] { $"category:{product.CategoryId}", "products" },
            CancellationToken.None
        );
    }
}

エラーハンドリングとログ

public class RobustCacheService
{
    private readonly HybridCache _cache;
    private readonly ILogger<RobustCacheService> _logger;
    
    public RobustCacheService(HybridCache cache, ILogger<RobustCacheService> logger)
    {
        _cache = cache;
        _logger = logger;
    }
    
    public async Task<T> GetOrCreateWithFallbackAsync<T>(
        string key,
        Func<CancellationToken, Task<T>> factory,
        CancellationToken cancellationToken = default)
    {
        try
        {
            return await _cache.GetOrCreateAsync(key, factory, token: cancellationToken);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "キャッシュエラーが発生しました。キー: {Key}", key);
            
            // キャッシュ失敗時はファクトリメソッドを直接実行
            return await factory(cancellationToken);
        }
    }
}