缓存数据改了以后

为什么需要

第 1 页的缓存里,存的是答案的一份拷贝。原来的那一份在数据库里,最完整、最权威,叫(source of truth)。作者给番茄炒蛋(第 3 章的第 1024 号菜谱)的配料加了「白糖 1 勺」,服务器把数据库里的正本改好了;可缓存里键为「菜谱:1024」的那一份,还是改之前放进去的。别的读者来看这道菜,在缓存里命中,拿到的是旧配料。

这种已经和正本对不上、却还会被读到的内容,叫(stale data)。旧答案算不算错,要看内容:「今日热门」晚一会儿更新,没有人在意;配料是读者照着做菜的,旧了就会做错。

怎样做

常见的办法有两种:

  • 只等到期:放回缓存时,同时写上它最多能保留多久,这叫(TTL,time to live),第 1 章的(把域名换成服务器的 IP 地址)也用它。时间一到,这一份就了:你平时说的过期是食品过了保质期;这里指缓存里的拷贝超过了过期时间,不再拿来用,下一次读取没命中,服务器重新去数据库查。这样做不必知道谁改了什么;代价是改了以后,别人最长还会读到一个过期时间那么久的旧答案。打车软件 Uber 的缓存默认的过期时间是 5 分钟(2025)。
  • 修改时主动失效:服务器改好数据库以后,紧接着把缓存里对应的那一份删掉,这叫(cache invalidation)。下一位读者没命中,就从数据库读到新的配料。「过期」是时间到了,「失效」是因为数据改了而主动作废,本课程分开用。

下图是作者改好配料以后的 10 分钟,看别的读者读到的是哪一份。

改了配料:之后的 10 分钟里,读者读到的是哪一份

先看这里先按「下一步」,看作者改好配料的那一刻(第 0 分钟),缓存里存的是哪一份配料。

数据改了以后
过期时间
现在:尚未开始

还没开始。按「下一步」一分钟一分钟往后看,也可以直接看第 10 分钟。

旧配料一共会留多久(这次修改从头到尾)—
读到旧配料的读者(到此刻为止)—
为这道菜查数据库(到此刻为止)—

示意:作者在第 0 分钟改好配料。图里是最坏的情形:旧配料刚放进缓存,作者就改了,所以它要再过一整个过期时间才过期;放进去得早一些,旧配料就留得短一些。读者是随机来的,平均每 15 秒一位,有时一分钟里来好几位,有时好一阵没人来;每次打开这一页,来的都是同一批读者,方便比较不同的设置。主动失效时假设删除立刻送达;真实的系统里还要一点时间,也可能失败。

在上图里换一换过期时间。只等到期时,10 秒只有 2 位读者读到旧配料,可这道菜大多没命中,10 分钟里要查 21 次数据库;30 分钟时一次也不用查,这 10 分钟里来的读者却全都读到旧配料。过期时间太短,命中率上不去;太长,旧答案留得久。再换成「修改时主动失效」:过期时间再长,也没有人读到旧配料。

主动失效的先后有讲究:要先改数据库,再删缓存。反过来就可能出错:缓存里的那一份删掉了,数据库还没改好,恰好有读者来看这道菜。他的请求没命中,服务器从数据库读出还没改的旧配料,又把它放回缓存,此后人人读到的都是旧的,直到它过期。

代价是什么

主动失效的代价,是要知道一次修改牵连了缓存里的哪些键。同一份配料可能牵连好几个键:番茄炒蛋的菜谱页存着一份;有人按配料找过用到白糖的菜,那份答案里也该加上这道菜。漏删一个,那里就一直是旧的,直到它过期。

删除本身也可能失败,或者迟一些才送到。就算先改数据库再删缓存,在很少见的情况下,旧配料也会回到缓存里:服务器替一位读者从数据库读到旧配料,还没放进缓存;这时作者改好了配料,服务器照常删缓存;然后,那次读取才把旧配料放回缓存(Facebook,2013;Meta,2022)。所以主动失效通常仍配一个过期时间兜底,最坏的情况下,旧答案也只留到过期为止。Uber 2025 年的做法,就是主动失效和过期时间一起用。

资料来源

以下资料均于 2026-10-23 和 2026-10-26 访问。

  1. Scaling Memcache at FacebookRajesh Nishtala 等(Facebook),NSDI 2013,2013-04
  2. How Uber Serves over 150 Million Reads per Second from Integrated Cache with Stronger Consistency GuaranteesEli Pozniansky、Preetham Narayanareddy,Uber 工程博客,2025-08-26
  3. Cache-Aside pattern微软 Azure 架构中心,2025-09-11
  4. Cache made consistentLu Pan,Meta 工程博客,2022-06-08