关于算法:缓存失效 – 是否有通用解决方案?

Cache Invalidation — Is there a General Solution?

"There are only two hard problems in Computer Science: cache invalidation and naming things."

菲尔KarltonP></

is there or method to a通用解决方案invalidating缓存;当an entry is基本知道,你是我永远的移调to get保鲜日期?P></

考虑到getData()for example,函数,得到从数据文件。基于caches EN EN修饰the last time of the which every时间检查文件,它是所谓的。然后你add a second function transformData()which the transforms日期,时间和caches its result is called for the next函数。它有没有文件知识of the你好add that if the is changed the依赖文件,无效的缓存becomes this?P></

你可以呼叫getData()is called出现时间和每一transformData()EN值that was with the used to build the cache,but that我有甚costly端上。P></


你所说的是生命周期依赖链,一件事依赖于另一件事,而另一件事可以在它的控制之外被修改。

如果你有一个从abc的等幂函数,其中,如果ab相同,那么c相同,但检查b的成本很高,那么你可以:

  • 接受您有时使用过期信息进行操作,不要总是检查b
  • 尽可能快地进行检查。
  • 你不能吃蛋糕…

    如果您可以在顶部覆盖基于a的附加缓存,那么这将影响初始问题,而不是一个位。如果您选择1,那么您拥有自己所给予的任何自由,因此可以进行更多的缓存,但必须记住考虑b缓存值的有效性。如果选择2,则每次都必须检查b,但如果b签出,则可以返回a的缓存。

    如果对缓存进行分层,则必须考虑是否由于组合行为而违反了系统的"规则"。

    如果您知道a始终有效,如果b总是有效,那么您可以这样安排缓存(伪代码):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    private map<b,map> cache //
    private func realFunction    // (a,b) -> c

    get(a, b)
    {
        c result;
        map endCache;
        if (cache[b] expired or not present)
        {
            remove all b -> * entries in cache;  
            endCache = new map();      
            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;
    }

    显然,只要在每个阶段,新增加的输入的有效性与xabxbxa的关系相匹配,连续分层(如x)就微不足道。

    但是,您很可能会得到三个有效性完全独立(或是循环的)的输入,因此不可能进行分层。这意味着标有//重要的行必须改为

    if (endCache[a] expired or not present)


    缓存失效中的问题是,在我们不知道的情况下,内容会发生变化。因此,在某些情况下,如果有其他事情知道并能通知我们,解决方案是可能的。在给定的示例中,getdata函数可以钩住文件系统,该系统知道对文件的所有更改,而不管哪个进程更改了文件,而这个组件反过来可以通知转换数据的组件。

    我不认为有任何一般的魔术解决办法可以让问题消失。但在许多实际情况下,很可能会有机会将基于"轮询"的方法转换为基于"中断"的方法,这会使问题简单地消失。


    功能反应式编程(FRP)在某种意义上是解决缓存失效的一种通用方法。

    原因如下:在玻璃钢术语中陈旧的数据被称为故障。玻璃钢的目标之一是保证无故障。

    在这篇"玻璃钢的本质"的演讲中,以及在这个问题的答案中,对玻璃钢作了更详细的解释。

    在谈话中,Cell表示一个缓存的对象/实体,如果其中一个依赖项被刷新,则会刷新Cell

    FRP隐藏了与依赖关系图关联的管道代码,并确保没有过时的Cells。

    我能想到的另一种方法(不同于FRP)是将计算值(类型为b的)包装到某种类型的编写器monad Writer (Set (uuid)) b中,其中Set (uuid)(haskell符号)包含计算值b所依赖的可变值的所有标识符。因此,uuid是一种唯一的标识符,它标识计算的b所依赖的可变值/变量(例如数据库中的一行)。

    将这个想法与在这种编写器monad上运行的组合器结合起来,如果您只使用这些组合器来计算一个新的b,那么可能会导致某种通用的缓存失效解决方案。这种组合器(比如说filter的特殊版本)以writer monads和(uuid, a)s作为输入,其中a是由uuid标识的可变数据/变量。

    因此,每次更改计算b类型的计算值所依赖的"原始"数据(uuid, a)(即计算b的数据库中的规范化数据)时,如果您改变计算b值所依赖的任何值a,则可以使包含b类型的缓存无效,because基于作者monad中的Set (uuid),你可以知道什么时候会发生这种情况。

    所以,当你用一个给定的uuid突变某个东西时,你把这个突变广播给所有的cache-s,它们使依赖于用所述uuid标识的可变值的b值无效,因为在其中包装b的作者单子可以判断该b是否依赖于所述uuid或不是。

    当然,只有当你阅读的次数比写作的次数多的时候,这才有回报。

    第三种实用的方法是在数据库中使用物化视图,并将它们用作缓存。他们还致力于解决失效问题。当然,这限制了将可变数据连接到派生数据的操作。


    如果每次进行转换时都要使用getData(),那么就消除了缓存的全部好处。

    对于您的示例,似乎有一个解决方案,当您生成转换的数据时,还可以存储数据所生成的文件的文件名和上次修改时间(您已经将其存储在getdata()返回的任何数据结构中,因此您只需将该记录复制到transformdata()返回的数据结构中),然后d然后当您再次调用transformdata()时,检查文件的最后修改时间。


    我现在正在研究一种基于Postshap和记忆功能的方法。我已经从我的导师那里运行过了,他同意这是一个以内容无关的方式实现缓存的好方法。

    每个函数都可以用指定其有效期的属性进行标记。以这种方式标记的每个函数都是内存化的,结果存储在缓存中,函数调用的散列和参数用作键。我在后台使用Velocity,它处理缓存数据的分发。


    没有一般的解决方案,但:

    • 缓存可以充当代理(拉)。假设您的缓存知道最后一个源更改的时间戳,当有人调用getData()时,缓存会向源请求它的最后一个更改的时间戳,如果相同,它会返回缓存,否则它会用源更改更新其内容并返回其内容。(一个变化是客户端直接发送请求的时间戳,如果其时间戳不同,则源只返回内容。)

    • 您仍然可以使用通知进程(push),缓存观察源,如果源发生更改,它将向缓存发送一个通知,然后将其标记为"脏"。如果有人调用getData(),缓存将首先更新到源,删除"脏"标志,然后返回其内容。

    一般来说,选择取决于:

    • 频率:许多对getData()的调用更喜欢推送,以避免getTimeStamp函数淹没源。
    • 您对源的访问:您拥有源模型吗?否则,很可能无法添加任何通知进程。

    注意:由于使用时间戳是HTTP代理工作的传统方式,另一种方法是共享存储的内容散列。我知道两个实体在一起更新的唯一方法就是要么我叫你(拉),要么你叫我…(推),就这些。


    Is there a general solution or method to creating a cache, to know when an entry is stale, so you are guaranteed to always get fresh data?

    不,因为所有数据都不同。有些数据可能在一分钟后"过时",有些在一小时后"过时",而有些数据可能会在几天或几个月内保持良好状态。

    对于您的具体示例,最简单的解决方案是为文件提供"缓存检查"功能,您可以从getDatatransformData调用该功能。


    缓存很难,因为您需要考虑:1)缓存是多个节点,需要协商一致2)失效时间3)多发性起跳/起跳时的比赛状态

    这是一个很好的读物:https://www.confluent.io/blog/turning-the-database-inside-out-with-apache-samza/


    也许缓存遗忘算法将是最通用的(或者至少,更少依赖于硬件配置),因为它们将首先使用最快的缓存,然后从那里继续。这是麻省理工学院的一个讲座:缓存遗忘算法