Uber是如何花费巨大精力实现缓存精确失效?
内容提要
Uber通过CacheFront解决了在Docstore上扩展读取工作负载的挑战,提供了强一致性的缓存解决方案,降低了数据库引擎负载,实现了低延迟读取请求。
延伸解读
缓存失效的难点与应对
文章引用“计算机科学中只有两件难事:缓存失效和命名”来强调缓存失效的复杂性。Uber的CacheFront默认使用5分钟TTL,但为了更快反映数据变更,必须依赖更精确的失效机制。条件更新场景下,缓存层无法预知哪些行会受影响,因此需要结合变更数据捕获(CDC)和流服务Flux来跟踪MySQL binlog事件,实现缓存失效。这体现了在大规模分布式数据库中,平衡一致性与性能的挑战。
CDC与Flux在缓存失效中的作用
CacheFront利用Docstore的变更数据捕获和流服务Flux来使缓存失效。Flux跟踪存储引擎层中每个集群的MySQL binlog事件,并将事件发布到消费者列表,为CDC、复制、物化视图等提供支持。通过这种方式,缓存能够及时感知数据变更,避免依赖TTL导致的延迟。但文章也指出,这种策略仍提供最终一致性语义,对于需要更强一致性的场景,还需额外机制。
避免过时写入的重复删除策略
由于写入在读取路径和写入路径之间同时发生在缓存中,可能会无意中将过时的行写入缓存,覆盖从数据库检索到的最新值。CacheFront根据MySQL中行集的时间戳来删除重复写入,时间戳从Redis中的编码行值解析出来,作为行的版本。这一机制有效防止了缓存被旧数据污染,确保了缓存数据的准确性,是强一致性缓存解决方案的关键一环。
更强一致性保证的专用API
虽然Flux允许比仅依赖Redis TTL更快地使缓存条目失效,但它仍提供最终一致性语义。某些用例需要更强的一致性,例如读自己写。为此,Uber向查询引擎添加了专用API,允许用户在相应的写入完成后显式使缓存的行无效。这为特定场景提供了更强的一致性保证,体现了CacheFront在灵活性和一致性之间的平衡设计。
Q&A
Uber的Docstore是什么?它面临什么挑战?
Uber的Docstore是一个内部分布式数据库,存储数十PB的数据并处理数千万个请求,是Uber最大的数据库引擎之一。它面临大规模低延迟读取需求的挑战。
CacheFront是什么?它如何解决Docstore的读取扩展问题?
CacheFront是Uber为Docstore开发的集成缓存解决方案,通过降低数据库引擎层的资源分配,改善P50和P99延迟,从而解决读取扩展问题。
CacheFront如何实现缓存读取?
CacheFront使用缓存预留策略:查询引擎层收到读取请求后,如果启用缓存,先尝试从Redis获取行并流式传输给用户,同时从存储引擎检索剩余行,异步填充Redis,再将剩余行流式传输给用户。
为什么缓存失效在Docstore中是一个难题?
因为Docstore支持条件更新,缓存层无法确定哪些行会受到影响,直到数据库引擎更新实际行。此外,默认TTL过期可能导致一致性不足。
CacheFront如何利用变更数据捕获来使缓存失效?
CacheFront利用Docstore的变更数据捕获和流服务Flux,Flux跟踪MySQL binlog事件并发布到消费者列表,从而及时使缓存失效。
CacheFront如何避免将过时的行写入缓存?
CacheFront根据MySQL中行集的时间戳来删除重复写入,时间戳从Redis中的编码行值解析,作为版本控制,避免过时行覆盖最新值。
CacheFront如何提供更强的一致性保证?
对于需要更强一致性的用例(如读自己写),CacheFront在查询引擎中添加专用API,允许用户在写入完成后显式使缓存的行无效。