The default settings in ASP.NET Core leave room for improvement - even though it's a high-performance web framework. Optimisation patterns I'm about to share apply to any ASP.NET Core 3.1 application, no matter the size or complexity.

Response compression and caching are two powerful tools to reduce server load. Enable Brotli compression for JSON, HTML, CSS, and JavaScript, as it can reduce response size by 20-26% compared to gzip.

In practice, we've seen Brotli compression make a big difference in production. For example, our API returns a mix of JSON, HTML, and static assets. Brotli compression reduced our average response size by 22%, which in turn reduced our average response time by 15%.

Response caching, on the other hand, eliminates repeated computation for read-heavy endpoints with tolerable staleness. When combined, compression and caching can reduce server load by 50-70% for read-dominated APIs.

When working with strings, binary data, or arrays, consider using System.Span and System.Memory. These allow you to operate on contiguous memory regions without heap allocations, which can make a huge difference in hot paths.

We've seen significant performance improvements by replacing string concatenation with Span<T> in a high-throughput endpoint. Before the change, the endpoint was allocating 100KB of memory per request, which led to frequent garbage collection and slowed down the application.

For instance, ArrayPool.Shared provides pooled array rental, eliminating large array allocations in processing loops. The impact is measurable in reduced GC pressure and improved throughput.

For high-throughput endpoints, consider using minimal API endpoints or IHttpRequestHandler to bypass MVC overhead. This can be especially beneficial when combined with manual JSON parsing using System.Text.Json and returning IActionResult instead of ValueTask.

The TechEmpower benchmark results show that lean ASP.NET Core can achieve near-raw-socket throughput, but only with careful optimization. We've seen cases where the overhead of MVC can add 10-20% latency to the raw socket throughput.

Don't create a new HttpClient per request - it exhausts the ephemeral port pool under load. Instead, register HttpClient as a singleton or use IHttpClientFactory to manage its lifetime and connection reuse.

This also allows for handler rotation when DNS changes occur. We've experienced this firsthand when our external API endpoint changed its DNS, causing all our HttpClient instances to fail. By using IHttpClientFactory, we could easily rotate to the new endpoint without affecting our application's performance.

When it comes to database connections, ensure that EF Core's connection pool is sized appropriately for the expected concurrency. The default pool size is 100, which may not be sufficient for large-scale applications.

In our experience, setting the connection pool size to 500-1000 has improved the performance of our data-driven endpoints by reducing the time spent waiting for connections. The key is to find the right balance between pool size and concurrency to avoid both underutilization and overutilization of connections.