Skip to content

Time‐based eviction

Alex Peck edited this page May 17, 2024 · 7 revisions

Both ConcurrentLru and ConcurrentLfu support 3 different time-based eviction policies:

  • Expire after access: evict after a fixed duration since an entry's most recent read or write. This is equivalent to MemoryCache's sliding expiry, and is useful for data bound to a session that expires due to inactivity.

  • Expire after write: evict after a fixed duration since an entry's creation or most recent replacement. This is similar to MemoryCache's absolute expiry (except expiry time is measured since creation/replacement, rather than in absolute time), and is useful for implementing eventual consistency.

  • Expire after: evict after a duration calculated for each item using an implementation of IExpiryCalculator. Expiry time is fully configurable per cache entry, and may be set independently at creation, after a read and after a write.

Duration and precision

The Duration struct internally selects the fastest available time API on the host operating system (Windows/MacOS/Linux). Duration.SinceEpoch() is optimized for low cache lookup latency at the expense of precision. In the worst case, precision is likely to be in the range of 10-16 milliseconds. The default time-based expiry policies all use Duration to measure time.

Operating System Duration Time API Precision
Windows Environment.TickCount64 10-16ms
MacOS Stopwatch.GetTimestamp 100ns
Linux* Environment.TickCount64 10-16ms

* Linux running on .NET Standard currently falls back to Stopwatch.GetTimestamp.

How to implement IExpiryCalculator

Each of the GetExpireAfter* methods should return the Duration within which a cache entry must be read/updated before being evicted from the cache. The Duration is relative to the current time, so if an item should be evicted 5 minutes from now, the method should return Duration.FromMinutes(5).

The current Duration until expiry is passed as an input to GetExpireAfterRead and GetExpireAfterUpdate. This value can be returned to preserve the existing expiry.

Example: expire after write

Evict after 5 minutes since an entry's creation or most recent replacement.

public class ExpireAfterWrite : IExpiryCalculator<Guid, string>
{
    private readonly Duration timeToExpire = Duration.FromMinutes(5);

    public Duration GetExpireAfterCreate(Guid key, string value) => timeToExpire;
    public Duration GetExpireAfterRead(Guid key, string value, Duration current) => current;
    public Duration GetExpireAfterUpdate(Guid key, string value, Duration current) => timeToExpire;
}

Example: expire after access

Evict after 5 minutes since an entry's most recent read or write.

public class ExpireAfterAccess : IExpiryCalculator<Guid, string>
{
    private readonly Duration timeToExpire = Duration.FromMinutes(5);

    public Duration GetExpireAfterCreate(Guid key, string value) => timeToExpire;
    public Duration GetExpireAfterRead(Guid key, string value, Duration current) => timeToExpire;
    public Duration GetExpireAfterUpdate(Guid key, string value, Duration current) => timeToExpire;
}