Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The CacheLib Caching Engine: Design and Experiences at Scale

  • 作者: Benjamin Berg, Daniel S. Berger, Sara McAllister, Isaac Grosof, Sathya Gunasekar, Jimmy Lu, Michael Uhlar, Jim Carrig, Nathan Beckmann, Mor Harchol-Balter, Gregory R. Ganger(CMU / Facebook / Microsoft Research)
  • 会议: OSDI 2020
  • 原文链接: usenix.org
  • PDF: osdi20-berg.pdf

摘要

Web 服务几乎在系统架构的每一层都依赖缓存。通常每个缓存都由不同的团队独立实现和 维护,并针对自身功能高度特化——比如应用数据缓存与 CDN 缓存彼此独立。但这种做法 忽视了不同缓存系统所共有的那些困难挑战,大大增加了部署、维护和扩展每个缓存的 总体成本。

本文提出一种不同的缓存开发思路(已在 Facebook 成功落地):从原本互不相干的缓存 系统中抽取出一组共同的核心需求与功能。CacheLib 是一个通用缓存引擎,基于 Facebook 内部一系列缓存用例的经验设计,让缓存的开发与维护变得容易。CacheLib 于 2017 年首次在 Facebook 部署,如今支撑着 70 多个服务,涵盖 CDN、存储和应用数据 缓存。

本文描述了从独立、特化的缓存迁移到广泛采用 CacheLib 这一过程中的经验:生产环境 负载与用例的特征如何驱动了关键设计决策,Facebook 的缓存如何随时间演进(包括 部署 CacheLib 带来的显著收益),以及这些经验对未来缓存设计与研究的启示。

关键结果

  • 在 Facebook,CDN 缓存承载了 70% 的 web 请求,把延迟降低了一个数量级。
  • 单台缓存服务器可以替代数十台后端数据库服务器:吞吐提升 20×,命中率超过 80%
  • CacheLib 2017 年首次部署,现已支撑 70+ 个服务(CDN、存储、应用数据缓存等)。
  • 缓存工作集大到需要同时使用 DRAM 和 flash;系统还必须容忍因应用更新导致的 频繁重启。

笔记

API

allocate(PoolId, Key, size, ttlSecs) -> ItemHandle
insertOrReplace(ItemHandle) -> bool
find(Key) -> ItemHandle
Item::getMemory() -> void*
Item::markNvmUnclean()
remove(Key) -> bool
  • 内存管理由 CacheLib 掌控,零拷贝
  • 原子更新的范式是「allocate 新 handle → getMemory 改 → markNvmUnclean 标记脏 → insertOrReplace 使其可见」
  • find 是异步的,内存里没有数据会异步从 flash 里拿
  • 没有标记脏的数据不会下沉到 nvm

DRAM Cache

实现常规套路(slab class,配置容量,不同汰换策略),不讨论。

negative caching: SocialGraph 55.6% 的请求查询的是不存在的 key, 剩下 44.4% 查有效对象,这部分的命中率是 86.5%,用得上的地方收益极大,用不上的地方完全不需要。

不做 negative caching:整体命中率 = 44.4% × 86.5% ≈ 38.4%。

做了 negative caching:整体命中率 ≈ 55.6% × 86.5% + 38.4% ≈ 86.5% (假设空命中率和有效命中率相当)。

缺少 negative caching 条目一致性相关的讨论。

串型点优化:

  • T: 刚被 reset 到 most-recently-used(MRU) 位的 Item,在 T 时间内不再 reset,可以有效缓解热点问题。
  • Flat combining: 类似 rocksdb write group 选 leader 攒批。
    • From: Danny Hendler, Itai Incze, Nir Shavit, and Moran Tzafrir. Flat combining and the synchronization-parallelism tradeoff. In Proceedings of the twenty-second annual ACM symposium on Parallelism in algorithms and architectures, pages 355–364, 2010.

Flash Cache:LOC/SOC:

LOC(≥2KB)SOC(<2KB)
索引精确:DRAM 里的 segmented B+ 树近似:key 哈希进 set,每 set 一个 8B Bloom filter
索引规模跟随对象数(百万级)flash 页数
定位粒度4B、4KB 对齐地址 → 可索引 16TB一个 set = 一个 4KB flash page,内含多个对象
每页对象数最多 1 个(>4KB 的跨页)多个
驱逐粒度整个 region(如 16MB)set 内单个对象
驱逐策略默认 FIFO(可选 region 粒度 pseudo-LRU)只能 FIFO

横向对比:

对比场景 / 配置结果归因
vs Memcached v1.6.6内存 look-aside;均用 LRU、32 线程、8–144GB、1 亿对象命中率相近(小缓存 Memcached 略高,大缓存略低);吞吐最多高 60%flat combining + T=60s;T=10s 则命中率反超但吞吐降
vs NGINX / ATSHTTP flash CDN;各用默认配置、512GB flash;固定对象大小逐档扫描小对象显著领先,对象变大优势收窄,最终三者都 network-boundSOC 对小对象的专门处理;8KB 处 NGINX 略胜,原因未查明
vs RocksDBLSM 存储;生产环境缓存 SocialGraph 数据命中率 53% vs 76%;达同等吞吐需多 50% CPU无法定向驱逐(tombstone+compaction);FIFO compaction 下“最久未更新“≠“最久未使用”

生产验证

  • DRAM 开销:LOC <0.1%、SOC <0.2%、DRAM cache <7%,主要靠手动调 slab class,实际情况不调碎片会翻倍
  • flash 层尽量使用 FIFO 驱逐策略,FTL 几乎不用搬运任何有效数据,device-level 写放大 1.05×,代价是 FIFO 不看冷热,热对象被 FIFO 驱逐了,会稍微抬高 application-level 写入放大
  • 小对象的准入策略比驱逐策略重要得多,application-level 为了对齐 4KB 会有较大的写放大,不做准入控制,设备寿命直接不达标
  • 准入策略:
    • 固定 p 概率写入:默认使用,和配置 iops/throughput 上限类似
    • reject-n:一个对象前 n 次驱逐写入都拒绝,超过 n 次才写入
    • Flashield:观察数据在 DRAM 里的访问频次决定写不写 flash;问题:如果 DRAM 里存在大量0访问数据(典型扫描/中长周期负载),能准入写入的只有很少
    • 高级策略:直接采样原始负载,训练模型来做准入策略,比较麻烦,但是收益足以覆盖成本
  • flash 容量会超配 50%:多出来的空间留给 FTL 做腾挪
  • 预热很重要:关掉重启命中率需要一段时间才能回升(数小时~数天),系统可能会突然过载;工程团队既要快速发版、也要快速回滚,系统的 uptime 不足以完全缓存
  • 真实负载没有那么 Zipf,学术界和 benchmark 工具普遍假设 Zipf α ≈ 0.9
负载α
Lookaside≈ 1
CDN0.7
SocialGraph0.55
Storage头部明显更平,只有尾部符合 Zipf
  • 真实负载有大量 churn(热点更替):某一小时的热门对象,一小时后超过 2/3 跌出前 10%;10 分钟后就有 50% 不再热门。
  • 命中率和延迟同等重要:网络拥塞、后端排队,一次 miss 会带来不可预测的延迟,一次 flash 命中和一次 DRAM 命中一样有价值,尽管 flash 慢好几个数量级

通用 vs 特化

  • 特性会被意外地广泛复用:发现一个特殊场景优化很好 → 被大家知道了,都在使用 → 变成了通用优化(不同系统的问题很多都是同构的)
    • 事实上 FB CacheLib 前十大用户占了全部 DRAM 缓存用量的 89%,但没有任何单个服务占主导
  • 稳定性:把原本各自为政的实现收敛到一个成熟、久经测试的平台

未来

  • ZNS SSD