Polly icon indicating copy to clipboard operation
Polly copied to clipboard

Returning last good value from cache after failure to retrieve a new value

Open michaeljmars opened this issue 5 years ago • 11 comments

Summary: What are you wanting to achieve?

I would like to cache results retrieved from an external service called over HTTP. This all looks simple with Polly however, once the cached entry has expired and a fresh value could not be retrieved I want the last known good configuration to be used.

I could see how to do this with 2 caches, one expiring and the other used by a Fallback policy but I would like to avoid caching the same value twice.

Has anyone any suggestions on how to achieve this?

michaeljmars avatar May 31 '19 11:05 michaeljmars

There is no in-built way in Polly to do this. You can probably construct it out of existing elements in the manner you suggest, though.

You could also author your own custom policy to achieve the effect directly. This would provide the behaviour directly but you can still consume it in PolicyWrap. We would love to see anything you come up with!

Re:

I would like to avoid caching the same value twice

Could you cache the value with TTL=forever and maintain a separate timestamp of when-to-refresh? Or maintain that when-to-refresh by caching just a flag on a related key with TTL = when-to-refresh? (could be the basic logic for a custom-policy implementation.)

Lateral q: Are we talking memory cache or a remote distributed cache? If memory cache and it's a heap object, you might have both cache keys pointing at the same heap object anyway? (so no duplication).

Anyone any other ideas?

reisenberger avatar May 31 '19 15:05 reisenberger

I spent some time reasoning whether it was possible to write a custom cache provider to achieve this within a single CachePolicy, but it is not.

Can't see a better idea than your original one, caching the value twice with different TTLs; or if it is really important to avoid caching the value twice, my suggestion of caching just a flag for the shorter TTL.

Was this useful? Did you have any other ideas?

reisenberger avatar Jun 08 '19 11:06 reisenberger

Or you if you only want to hit the cache once (because it is an external distributed cache, so extra RESTful call), you could craft an implementation around caching a tuple of the shorter-refresh-time and value-to-cache. (Not knowing why wanting to avoid caching twice was important, not sure how best to target suggestions.)

reisenberger avatar Jun 10 '19 16:06 reisenberger

Apologies for the late reply.

In the end I created a custom policy that essentially wraps a concurrent dictionary instance. The policy will always invoke the action given to it but will handle any exceptions thrown from invoking the action. If an exception is handled it will return the value (if any) from the set of cached values.

I would like to share the code (and maybe integrate it into Polly) but I need to get permission from my Company first.

Whilst this approach does cache the same value twice it caches the same instance twice. It was caching duplicated values (doubling the memory cost) that I wanted to avoid. Sorry for not making that clearer.

michaeljmars avatar Jun 13 '19 10:06 michaeljmars

@dotnetprogrammr Great: glad you got to a solution! Great to hear custom policies (a new feature we launched) is helping the community 🙂

We would love to see code if you are able to share! (understand re company permissions). Either it might be suitable to integrate into Polly, or we also have a Polly Contrib organisation now, where users can make contributions around Polly, eg where they have built useful custom policy for a particular case (we provide support in terms of rights on a github repo, template repos, appveyor build, nuget publishing etc).

reisenberger avatar Jun 13 '19 18:06 reisenberger

@reisenberger I like the idea of storing data once in the cache with a related when to refresh entry, as I think it addresses an issue I have of cache invalidation.

Data we display to the customer is expensive to compute so we store it in the cache with a long TTL, but then when new data arrives we have to somehow flush the cache entry and call the underlying service. I couldn't figure out a way of doing this with the standard cache, but with this approach it becomes much easier as the refresh request just afters the when-to-refresh entry and then behaves as normal

phatcher avatar Jun 22 '19 12:06 phatcher

@phatcher Makes sense - lots of good use cases for long-term-cache-with-refresh.

For clarity, this doesn't align with my development prios at mo, so I'm not expecting to spend any time on it. However, if you have a need for/are developing this and think it would integrate well with Polly, consider contributing a custom Policy. It could be something like CacheRefreshPolicy or CacheWithRefreshPolicy, and make use of the existing Polly cache-provider interfaces.

Let me know if you want to go that route and I can create a Polly.Contrib.CacheWithRefreshPolicy repo and give you rights on it.

reisenberger avatar Jun 25 '19 06:06 reisenberger

@reisenberger I'll have a play first and come back to you once I have a working solution

phatcher avatar Jun 25 '19 12:06 phatcher

@phatcher @dotnetprogrammr I was wondering if you've made any progress on this. I am also in need of a "CacheRefresh" policy and would like to leverage any efforts you've already contributed!

udlose avatar Oct 24 '19 19:10 udlose

Just checking if anyone has an implementation that can be shared as I encountered the same problem recently.

alexb5dh avatar Dec 23 '20 20:12 alexb5dh

As far as using MemoryCache is concerned, it seems that a CacheProvider that takes the cache and a PostEvictionDelegate along with setting up the CacheEntry options according to the TTL strategy would do the trick. Your delegate would then call the CachePolicy.Execute() which would see that that the entry is gone, call the action to get a new value, and add it to the cache.

madsen89 avatar Feb 11 '22 18:02 madsen89