mcpField
C
caching

caching

github.com/codewithmukesh開発7912.9k 回インストール12 週間前に更新
リポジトリ

caching について

.NET 開発者向けのキャッシュ運用スキルです。HybridCache、出力キャッシュ、無効化の方法を、期限設定や一般的な落とし穴への注意とともに説明します。

主な機能

  1. 01HybridCache の推奨方法
  2. 02キャッシュ無効化パターン
  3. 03HTTP レスポンスの出力キャッシュ
  4. 04TTL とユーザー別キー

活用シーン

  1. 01頻繁に読み込むデータのキャッシュ
  2. 02データ変更後のキャッシュ消去
  3. 03HTTP レスポンス全体のキャッシュ

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill caching

インストール後、Claude Code で/cachingを入力して読み込まれたか確認します。

詳細

キャッシュ

基本原則

  1. HybridCache がデフォルトです — .NET 9+ では、統合キャッシュ抽象化として HybridCache が導入されました。インメモリ(L1)キャッシュと分散(L2)キャッシュを、スタンピード保護と組み合わせます。ADR-004 を参照してください。
  2. 書き込みではなく読み取りをキャッシュします — GET 操作をキャッシュします。変更時には無効化します。POST/PUT/DELETE のレスポンスは決してキャッシュしないでください。
  3. レスポンス全体には出力キャッシュを使用します — HTTP レスポンス全体をキャッシュできる場合(公開 API、静的データ)は、出力キャッシュミドルウェアを使用します。
  4. TTL を明示的に設定します — キャッシュするすべての項目に有効期限が必要です。無制限のキャッシュは使用しないでください。

パターン

HybridCache(推奨デフォルト)
// Program.cs
builder.Services.AddHybridCache(options =>
{
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(5),
        LocalCacheExpiration = TimeSpan.FromMinutes(2)
    };
});

// Optional: Add Redis as the L2 distributed cache
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("Redis");
});
// Usage in a handler
public class GetProduct
{
    public record Query(Guid Id);
    public record Response(Guid Id, string Name, decimal Price);

    internal class Handler(AppDbContext db, HybridCache cache)
    {
        public async Task<Response?> Handle(Query query, CancellationToken ct)
        {
            return await cache.GetOrCreateAsync(
                $"products:{query.Id}",
                async token => await db.Products
                    .Where(p => p.Id == query.Id)
                    .Select(p => new Response(p.Id, p.Name, p.Price))
                    .FirstOrDefaultAsync(token),
                new HybridCacheEntryOptions
                {
                    Expiration = TimeSpan.FromMinutes(10)
                },
                cancellationToken: ct);
        }
    }
}
キャッシュの無効化
// Invalidate on mutation
public class UpdateProduct
{
    internal class Handler(AppDbContext db, HybridCache cache)
    {
        public async Task<Result> Handle(Command command, CancellationToken ct)
        {
            var product = await db.Products.FindAsync([command.Id], ct);
            if (product is null) return Result.Failure("Product not found");

            product.Update(command.Name, command.Price);
            await db.SaveChangesAsync(ct);

            // Invalidate the cached entry
            await cache.RemoveAsync($"products:{command.Id}", ct);

            return Result.Success();
        }
    }
}
出力キャッシュ(レスポンス全体のキャッシュ)
// Program.cs
builder.Services.AddOutputCache(options =>
{
    options.AddBasePolicy(b => b.NoCache()); // Don't cache by default

    options.AddPolicy("ProductList", b => b
        .Expire(TimeSpan.FromMinutes(5))
        .Tag("products"));

    options.AddPolicy("ProductById", b => b
        .Expire(TimeSpan.FromMinutes(10))
        .SetVaryByRouteValue("id")
        .Tag("products"));
});

app.UseOutputCache();

// Apply to endpoints
group.MapGet("/", ListProducts).CacheOutput("ProductList");
group.MapGet("/{id:guid}", GetProduct).CacheOutput("ProductById");

// Invalidate by tag on mutations
group.MapPut("/{id:guid}", async (Guid id, UpdateProductRequest request,
    IOutputCacheStore store, CancellationToken ct) =>
{
    // ... update logic ...
    await store.EvictByTagAsync("products", ct);
    return TypedResults.NoContent();
});
キャッシュ・アサイドパターン(レガシー)

新しいコードではすべて HybridCache を優先してください。IDistributedCache を手動で使うキャッシュ・アサイドでは、スタンピード保護がなく、シリアル化を手動で行う必要があり、L1/L2 の階層化もありません。IDistributedCache をすでに直接使用している既存コードとの統合時にのみ使用してください。

アンチパターン

有効期限なしでキャッシュしない
// BAD — cache lives forever, stale data guaranteed
await cache.SetStringAsync(key, value);

// GOOD — always set TTL
await cache.SetStringAsync(key, value, new DistributedCacheEntryOptions
{
    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
});
変更可能なユーザー固有データをキャッシュしない
// BAD — caching user's cart with a global key
await cache.GetOrCreateAsync("shopping-cart", ...);

// GOOD — include user ID in key
await cache.GetOrCreateAsync($"shopping-cart:{userId}", ...);
独自のスタンピード保護を実装しない
// BAD — manual lock to prevent cache stampede
private static readonly SemaphoreSlim Lock = new(1, 1);
await Lock.WaitAsync();
try { /* check cache, populate if missing */ }
finally { Lock.Release(); }

// GOOD — HybridCache has built-in stampede protection
await hybridCache.GetOrCreateAsync(key, factory);

選択ガイド

シナリオ推奨事項
一般的なデータのキャッシュHybridCache(GetOrCreateAsync)
HTTP レスポンス全体.CacheOutput() による出力キャッシュ
頻繁に読み取られ、書き込みがまれなデータTTL を長めに設定した HybridCache
ユーザー固有のデータユーザー単位のキーを使用する HybridCache
書き込み時のキャッシュ無効化cache.RemoveAsync() または出力キャッシュタグ
分散デプロイRedis L2 バックエンドを使用する HybridCache
単一サーバーのデプロイインメモリのみで使用する HybridCache

コメント