MichaCo / CacheManager

CacheManager is an open source caching abstraction layer for .NET written in C#. It supports various cache providers and implements many advanced features.
http://cachemanager.michaco.net
Apache License 2.0
2.34k stars 457 forks source link

Absolute expiration not working properly #241

Open geek197 opened 6 years ago

geek197 commented 6 years ago

Hi

I found some problem with CacheManager connected with issue https://github.com/MichaCo/CacheManager/issues/136 (IsExpired not working after extending absolute expiration) and commit https://github.com/MichaCo/CacheManager/commit/fb7ce8e4cb2c99e22f275a98197c93f691c17cc5

This change introduced very strange and unexpected behaviour when I working with multiple application instances using 2-handle cache.

Test scenario:

Conditions:

  1. We have two application instances A and B with identical cache configuration (but with independent local memory cache).
  2. In configuration we define two cache handles
    • first - local instances memory (System.Runtime.Caching),
    • second - shared Memcached server
  3. We want to cache response data in Web application with Absolute expiration for 10 minutes.

Time line

  1. 0 minute - we don't have data in any cache handle
  2. 1 minute - request to instance A - because we don't have data in cache handles, we generate fresh data and store it (Put) to local memory of instance A and shared memcached server.
  3. 8 minute - request to instance B - we don't have data in local memory of instance B, but we still have valid data in memcached server. During Get operation CacheManager copy this data (Add) to local memory of instance B, but also set CreatedUtc to current date.
  4. 15 minute - request to instance B - because of rewrite CreatedUtc, data on local memory of instance B is still valid,

This behaviour is completely unexpected - we want to expire item after 10 minutes (from Put), not after 15 or 20 minutes.

MichaCo commented 6 years ago

Hi @marcinot Yeah this is not a problem, it is the intended behavior.

Imagine you have an in-memory cache with 1 second absolute expiration and memcached with 10 seconds absolute.

You Add a key to the cache and read it back via Get after 5 seconds. That Get operation would put it into local in-memory cache with 1 second expiration.

Using the initial created date (5 seconds ago) would mark the item expired right away...

In general, the absolute expiration of an item should start the moment it gets added to a cache handle. Meaning, if both layers have 10 second absolute expiration and the Get operation adds the item to the in-memory layer after 5 seconds. The total life time of the cached item would be ~15 seconds.

So, if you have the same absolute expiration on both cache layers, yeah, you'll see that behavior that you might have stale data in memory although the item doesn't exist anymore on the memcached server.

But that could totally be a valid use case, too!

For the use case you have, you should probably use a way lower expiration timeout for the in-memory cache. Like 10 seconds, and 10 minutes out of process. In that scenario, you might still have stale data eventually, but only for a few seconds.

geek197 commented 6 years ago

Thank you for fast response :-) I understand your motivation for introducing this change, but my bussines cases are liitle different.

  1. I don't need difference in expiration timeout between handles because memory consumption isn't problem for me. I use memcache server to share cached data between multiple application instances, and local memory to speed up cache access (lower latency)
  2. I use custom expiration timeout per item, so I can't introduce difference in expiration timeout between handles even if I want.
  3. Often I need to expire item after specyfic time (from original Put/Add) or in exact point in time (eg today at 23:59:59)

Maybe the simplest solution would be to introduce new expiration mode StrictAbsolute in which CreatedUtc isn't modified.