-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
Background and motivation
Microsoft.Extensions.Caching.Memory.IMemoryCache is the primary in-process cache for modern .NET apps. Compared to System.Runtime.Caching, it lacks a standard, built-in metrics story. That gap makes it hard to:
- Migrate from
System.Runtime.Cachingwhile keeping comparable observability. - Validate that caches are actually saving work in production.
- Detect thrashing due to memory pressure or aggressive expirations.
.NET 8 added MemoryCacheOptions.TrackStatistics and IMemoryCache.GetCurrentStatistics(), which expose hit/miss and size data. However, each team still has to:
- Invent its own metric names and tag schema.
- Integrate manually with
Meter/ OpenTelemetry. - Handle multiple caches and naming conventions.
- Be careful not to add hot-path overhead in the cache.
API Proposal
namespace Microsoft.Extensions.Caching.Memory;
public class MemoryCacheStatistics
{
long TotalHits { get; init; }
long TotalMisses { get; init; }
long CurrentEntryCount { get; init; }
long CurrentEstimatedSize { get; init; }
+ long TotalEvictions { get; init; }
}TotalEvictions is total number of entries evicted by the cache implementation since the cache was created. It includes removals due to size limits, expirations, memory pressure, but not i.e. explicit user removals.
namespace Microsoft.Extensions.Caching.Memory;
public class MemoryCacheOptions
{
// ...
public bool TrackStatistics { get; set; }
+ public string Name { get; set; } = "Default";
}The name for this cache. Will be used as dimension in OTEL instruments or as an opaque identifier to distinguish between two or more caches.
namespace Microsoft.Extensions.Caching.Memory;
public class MemoryCache
{
public MemoryCache(IOptions<MemoryCacheOptions> optionsAccessor)
public MemoryCache(IOptions<MemoryCacheOptions> optionsAccessor, ILoggerFactory loggerFactory)
+ public MemoryCache(IOptions<MemoryCacheOptions> optionsAccessor, ILoggerFactory loggerFactory, IMeterFactory? meterFactory)
// ...
}Optional IMeterFactory that will be used to create Meter and instruments. I.e. WebApplication already contains registration for DefaultMeterFactory so it will work out of the box. If not provided, implementation will use shared meter like in i.e. S.N.Http.
The actual implementation will use underlying MemoryCacheStatistics and will just publish those values. If and only if TrackStatistics is true.
OTEL names and instruments:
Meter:Microsoft.Extensions.Caching.MemoryTotalHits+TotalMisses=>ObservableCounter<long>:cache.requests- with attribute
cache.request.typeHit,Miss
- with attribute
CurrentEntryCount=>ObservableUpDownCounter<long>:cache.entriesCurrentEstimatedSize=>ObservableGauge<long>:cache.estimated_sizeTotalEvictions=>ObservableCounter<long>:cache.evictions- dimension for all =>
cache.name
All these instruments are observable to not create any bottleneck in hot path.
API Usage
With DI (most of the usages):
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMemoryCache(options =>
{
options.TrackStatistics = true;
options.Name = "my-cache";
});
// Optional: hook into OpenTelemetry metrics
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddMeter("Microsoft.Extensions.Caching.Memory");
// configure exporters, views, etc.
});Or when used manually:
var options = new MemoryCacheOptions
{
TrackStatistics = true,
Name = "my-cache",
};
var cache = new MemoryCache(Options.Create(options));Alternative Designs
Alternative design could be to use - instead of "eager" publishing of meter/instruments - "late" bound publishing where developer is required to explicitly enable the publishing (and also optionally stop the publishing).
public interface IMemoryCacheMetrics
{
void AddCache(string name, IMemoryCache cache);
void RemoveCache(IMemoryCache cache);
}This is non-intuitive. Requires developer to do extra steps and does not follow what we do in i.e. System.Runtime or System.Net.Http where metrics are available without extra steps. And also bloats the API surface area. We can consider this in the future as another way, if we see strong demand for this kind of flexibility.
Risks
- Incorrect implementation of metrics polling could increase latency or allocations. But it is the same as calling
GetCurrentStatisticsextensively. Namerelies on callers to supply stable, unique names. Duplicate names or name changes over time can make metrics harder to interpret.
These risks are considered manageable and can be mitigated with clear documentation, testing, and early feedback.