Redis
 Computer >> コンピューター >  >> プログラミング >> Redis

ASP.NET Coreでパフォーマンスとスケーラビリティを最大化する実証済みの戦略

ASP.NET Coreは、高性能でスケーラブルなWebアプリケーション構築のために設計された、モダンなオープンソースのクロスプラットフォームフレームワークです。マイクロサービスからエンタープライズ級のAPIまで、そのアーキテクチャは優れたスループット、最小限のレイテンシ、効率的なリソース利用を実現します。

本記事では、ASP.NET Coreアプリケーションのパフォーマンスとスケーラビリティを最大化するための重要な戦略、設定のコツ、コード例を詳しく解説します。

🚀 パフォーマンスとスケーラビリティの基本概念

実装に入る前に、2つの重要な概念を整理しておきましょう。

  • パフォーマンス:単一のリクエストに対してアプリケーションがどれだけ速く応答できるか。
    (例:レスポンスタイムを300msから100msに短縮する)

  • スケーラビリティ:負荷が増加した際にアプリケーションがどれだけ耐えられるか。
    (例:同時接続ユーザー10,000人でもクラッシュせずに処理する)

ASP.NET Coreは、効率的なメモリ管理、非同期プログラミング、依存性注入(DI)、キャッシュ、分散システムの組み込みサポートによって、この両方を実現しています。

ASP.NET Coreでパフォーマンスとスケーラビリティを最大化する実証済みの戦略

⚙️ 非同期プログラミングの活用

ASP.NET Coreランタイムは非同期I/O操作向けに最適化されています。asyncawaitキーワードを使うことで、スレッドをブロックせずに解放し、より多くのリクエストを同時に処理できます。

✅ 例:非同期コントローラーアクション

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductsController(IProductService productService)
    {
        _productService = productService;
    }

    [HttpGet("{id}")]
    public async Task GetProductById(int id)
    {
        var product = await _productService.GetProductAsync(id);
        if (product == null)
            return NotFound();

        return Ok(product);
    }
}

Task<IActionResult>を使用することで、データベースクエリやAPI呼び出しなどのI/Oバウンド操作の待機中にスレッドがブロックされません。これにより、高負荷時のスケーラビリティが大幅に向上します。

🧩 ミドルウェアパイプラインの最適化

ミドルウェアコンポーネントは各リクエストを順番に処理します。ミドルウェアは軽量に保ち、不要な処理を避けることが重要です。

✅ 例:カスタム軽量ミドルウェア

public class RequestTimingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    public RequestTimingMiddleware(RequestDelegate next,
        ILogger logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var start = DateTime.UtcNow;
        await _next(context);
        var elapsed = DateTime.UtcNow - start;
        _logger.LogInformation($"Request took {elapsed.TotalMilliseconds} ms");
    }
}

// Program.csでの登録
app.UseMiddleware();

👉 ヒント:
軽量なミドルウェア(ルーティングや圧縮など)はパイプラインの先頭に配置し、重いミドルウェア(認証など)は後段に配置しましょう。

⚡ レスポンスキャッシュの有効化

キャッシュを活用すると、結果の再計算やデータベースへの反復アクセスを削減できます。ASP.NET Coreにはレスポンスキャッシュミドルウェアが組み込まれています。

✅ 例:レスポンスキャッシュの有効化

// Program.cs
builder.Services.AddResponseCaching();

var app = builder.Build();
app.UseResponseCaching();

app.MapGet("/time", (HttpContext context) =>
{
    context.Response.GetTypedHeaders().CacheControl =
        new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
        {
            Public = true,
            MaxAge = TimeSpan.FromSeconds(30)
        };

    return DateTime.UtcNow.ToString("T");
});

これにより、30秒以内の後続リクエストはキャッシュから提供され、パフォーマンスが劇的に向上します。

🧠 EF Coreによるデータアクセスの最適化

データベースアクセスは多くの場合、最大のボトルネックとなります。Entity Framework Coreを効率的に使うために、以下のテクニックを活用しましょう。

  • 読み取り専用クエリにはAsNoTracking()を使用

  • 繰り返し実行されるクエリにはコンパイル済みクエリを使用

  • 接続プーリングの活用

✅ 例:AsNoTracking()の使用

public async Task> GetAllProductsAsync()
{
    return await _context.Products
        .AsNoTracking() // パフォーマンス向上
        .ToListAsync();
}

類似のクエリを頻繁に実行する場合は、コンパイル済みクエリの利用を検討してください。

private static readonly Func> _getProductById =
    EF.CompileAsyncQuery((AppDbContext context, int id) =>
        context.Products.FirstOrDefault(p => p.Id == id));

public Task GetProductAsync(int id) =>
    _getProductById(_context, id);

🧰 出力圧縮の利用

クライアントへ送信前にレスポンスを圧縮することで、帯域幅の使用量を削減し、配信速度を向上させることができます。

✅ 例:レスポンス圧縮の有効化

// Program.cs
builder.Services.AddResponseCompression(options =>
{
    options.EnableForHttps = true;
    options.MimeTypes = new[] { "text/plain", "application/json" };
});

var app = builder.Build();
app.UseResponseCompression();

これにより、すべてのapplication/jsonレスポンスが自動的にGZIP圧縮されます。

🌍 ロードバランシングによるスケールアウト

トラフィックが増加した場合、パフォーマンスチューニングだけでは不十分です。スケーラビリティを実現するには、複数サーバーへの負荷分散が必要です。

  • 水平スケーリング:サーバーの追加

  • ロードバランサー:NGINX、Azure Front Door、AWS ELBなど

分散システムでは、セッション状態キャッシュを外部化する(Redisなど)ことが推奨されます。

✅ 例:分散キャッシュ(Redis)の設定

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";
});

public class CacheService
{
    private readonly IDistributedCache _cache;

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

    public async Task SetCacheAsync(string key, string value)
    {
        await _cache.SetStringAsync(key, value, new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
        });
    }

    public Task GetCacheAsync(string key) =>
        _cache.GetStringAsync(key);
}

これによりアプリケーションがステートレスになり、ロードバランシングに不可欠な状態になります。

🧩 高スループットのためのKestrelとホスティング設定

ASP.NET Coreの組み込みWebサーバーであるKestrelは、適切に設定すれば毎秒数十万件のリクエストを処理できます。

✅ 例:Kestrel設定の最適化

builder.WebHost.ConfigureKestrel(options =>
{
    options.Limits.MaxConcurrentConnections = 10000;
    options.Limits.MaxConcurrentUpgradedConnections = 1000;
    options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30);
});

さらに以下も検討しましょう。

  • 静的ファイルの処理やTLS終端のためにリバースプロキシサーバー(NGINXやIISなど)を利用する。

  • オートスケーリングのためにコンテナ環境(Kubernetesなど)にデプロイする。

🧮 メモリとオブジェクトプーリングの活用

頻繁なオブジェクト割り当てとガベージコレクションを避けるため、ASP.NET Coreはオブジェクトプーリングをサポートしています。

✅ 例:ArrayPool<T>の使用

using System.Buffers;

public class BufferService
{
    public void ProcessData()
    {
        var pool = ArrayPool.Shared;
        var buffer = pool.Rent(1024); // 1KBのバッファを確保
        try
        {
            // バッファを使用した処理
        }
        finally
        {
            pool.Return(buffer);
        }
    }
}

このアプローチによりヒープ割り当てを最小限に抑え、GC負荷を軽減できます。パフォーマンスが重要なアプリケーションでは不可欠な手法です。

🧱 起動時間とメモリフットプリントの最小化

  • Program.cs不要なサービス登録を避ける

  • 適切な場面ではAddTransientではなくAddSingletonを使用する。

  • *.csprojファイルで依存関係を整理・削減する。

✅ 例:Minimal APIセットアップ

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton();

var app = builder.Build();
app.MapGet("/products", async (IProductService service) =>
    await service.GetAllProductsAsync());

app.Run();

Minimal APIはボイラープレートコードを削減し、起動パフォーマンスを向上させます。

📊 モニタリングとベンチマーク

測定しないものは改善できません。次のようなツールを活用しましょう。

  • dotnet-traceおよびdotnet-counters

  • Application Insights

  • BenchmarkDotNet

✅ 例:BenchmarkDotNetの使用

[MemoryDiagnoser]
public class PerformanceTests
{
    private readonly ProductService _service = new();

    [Benchmark]
    public async Task FetchProducts()
    {
        await _service.GetAllProductsAsync();
    }
}

このベンチマークを実行することで、ボトルネックやメモリの非効率な箇所を特定できます。

🧩 その他の最適化のヒント

  • より良い並列処理のためにHTTP/2またはHTTP/3を有効化する。

  • 静的アセットにはCDNを利用する。

  • データベースやHTTPクライアントに接続プーリングを採用する。

  • ソケット枯渇を防ぐためにIHttpClientFactoryを使用する。

builder.Services.AddHttpClient("MyClient")
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

🏁 まとめ

ASP.NET Coreにおける高いパフォーマンスとスケーラビリティは、非同期設計キャッシュ効率的なデータアクセス、そして賢明なインフラ選択の組み合わせによって実現されます。

本記事で紹介した戦略——ミドルウェアやKestrel設定の最適化から、Redisや圧縮の活用まで——を実践することで、あなたのASP.NET Coreアプリケーションは低レイテンシかつ高信頼性で大規模なワークロードを処理できるようになります。

  1. Redis ZADDコマンドの使い方 – ソート済みセットへの要素の作成と追加

    このチュートリアルでは、RedisのZADDコマンドを使って、キーに保存されたソート済みセット(sorted set)の値に要素を作成・追加する方法を解説します。 ZADDコマンドは、指定したキーに格納されたソート済みセットに対して、それぞれのスコアを持つ要素を1つ以上追加するために使用します。すでに同じ要素が存在する場合は、そのスコアが更新され、正しい順序を保てるように適切な位置へ再挿入されます。スコアには倍精度浮動小数点数(double)の文字列表現を指定し、+inf(正の無限大)や-inf(負の無限大)も有効な値として利用できます。 キーがデータストアに存在しない場合は、挿入操作を行う前

  2. Redisリスト入門:リスト型の値を管理するための主要コマンド一覧

    Redisリストとは Redisのリスト(List)は、挿入された順番どおりに並べられた文字列のシーケンスです。Redisでは、このリストをキーに対する値として保存することができます。さらに、さまざまなRedisコマンドを組み合わせることで、データベースに格納されたリスト値の保存・管理・取得を柔軟に行えます。 構文 redis host:post> <Command Name> <key name> 実行例 Redisリスト操作の主なコマンド一覧 Redisデータベース内のリスト値を管理するうえで押さえておきたい重要なコマンドを、以下の表にまとめました。