Cache Invalidation — Is there a General Solution?
"There are only two hard problems in Computer Science: cache invalidation and naming things."
Phil Karlton
Is there a general solution or method to invalidating a cache; to know when an entry is stale, so you are guaranteed to always get fresh data?
For example, consider a function getData()
that gets data from a file. It caches it based on the last modified time of the file, which it checks every time it's called.
Then you add a second function transformData()
which transforms the data, and caches its result for next time the function is called. It has no knowledge of the file - how do you add the dependency that if the file is changed, this cache becomes invalid?
You could call getData()
every time transformData()
is called and compare it with the value that was used to build the cache, but that could end up being very costly.
What you are talking about is lifetime dependency chaining, that one thing is dependent on another which can be modified outside of it's control.
If you have an idempotent function from a
, b
to c
where, if a
and b
are the same then c
is the same but the cost of checking b
is high then you either:
b
b
as fast as possible You cannot have your cake and eat it...
If you can layer an additional cache based on a
over the top then this affects the initial problem not one bit. If you chose 1 then you have whatever freedom you gave yourself and can thus cache more but must remember to consider the validity of the cached value of b
. If you chose 2 you must still check b
every time but can fall back on the cache for a
if b
checks out.
If you layer caches you must consider whether you have violated the 'rules' of the system as a result of the combined behaviour.
If you know that a
always has validity if b
does then you can arrange your cache like so (pseudocode):
private map<b,map<a,c>> cache //
private func realFunction // (a,b) -> c
get(a, b)
{
c result;
map<a,c> endCache;
if (cache[b] expired or not present)
{
remove all b -> * entries in cache;
endCache = new map<a,c>();
add to cache b -> endCache;
}
else
{
endCache = cache[b];
}
if (endCache[a] not present) // important line
{
result = realFunction(a,b);
endCache[a] = result;
}
else
{
result = endCache[a];
}
return result;
}
Obviously successive layering (say x
) is trivial so long as, at each stage the validity of the newly added input matches the a
: b
relationship for x
: b
and x
: a
.
However it is quite possible that you could get three inputs whose validity was entirely independent (or was cyclic), so no layering would be possible. This would mean the line marked // important would have to change to
if (endCache[a] expired or not present)
The problem in cache invalidation is that stuff changes without us knowing about it. So, in some cases, a solution is possible if there is some other thing that does know about it and can notify us. In the given example, the getData function could hook into the file system, which does know about all changes to files, regardless of what process changes the file, and this component in turn could notify the component that transforms the data.
I don't think there is any general magic fix to make the problem go away. But in many practical cases there may very well be opportunities to transform a "polling"-based approach into an "interrupt"-based one, which can make the problem simply go away.
If you're going to getData() every time you do the transform, then you've eliminated the entire benefit of the cache.
For your example, it seems like a solution would be for when you generate the transformed data, to also store the filename and last modified time of the file the data was generated from (you already stored this in whatever data structure was returned by getData(), so you just copy that record into the data structure returned by transformData()) and then when you call transformData() again, check the last modified time of the file.
链接地址: http://www.djcxy.com/p/47708.html上一篇: 规范的功能反应编程语言
下一篇: 缓存失效 - 是否有一个通用解决方案?