TAO — Facebook 给十亿人好友列表造的专用图数据库
待复核TAO 是 Facebook 2013 年公开的一套社交图谱专用存储。日常类比:你不是在做账本(关系型 DB 擅长这个),你是在画一张超大的人际关系网——A 加了 B 是一条线,B 给 A 的照片点赞又是一条线,这种『点 + 线』的数据模型叫图。
TAO 把社交图分成两类东西:
- 对象(object):用户、照片、评论——一个 64 位 id 加一组字段
- 关联(association):有向边,带 from / to / 类型 / 时间戳 / 自定义数据
API 是固定的一小套(不是任意图查询):对象的增删改查,加上关联的增删、改类型、计数、按位置取一段、按时间取一段。背后是分片(shard = 把超大表切成多块,各块可放不同机器)MySQL 做持久层,再加两级缓存挡读。
一句话记住:TAO 不追求『什么图查询都能跑』,只把社交产品每天要打十亿次的那几类读写做到又快又稳。
不理解 TAO 你解释不了几件事:
- 为什么 Facebook / 微信 / 微博的好友列表能秒开,但同样的查询在 MySQL 里要跑几秒
- 为什么『刚加完好友立刻刷新还看得到』这件看似简单的事,工程上要专门设计
- 为什么大公司不买现成的 Neo4j / Nebula,要自己造一个
- 为什么 Twitter 的 FlockDB、LinkedIn 的 LIquid 都长得跟 TAO 几乎一样
更深一层:TAO 是『关系型 DB 不是万能的』在工业界最响亮的案例之一——不是 MySQL 差,是负载形状不对。
-
关系型 DB 的四个硬伤:图查询要 self-JOIN(『朋友的朋友』像把两本通讯录交叉对一遍,O(N²));明星行成锁热点;跨机房 MySQL 异步复制有 lag(刚写完别处还看不到);MySQL + memcached look-aside(旁路缓存:应用自己决定先查缓存还是先查库)会在『撕缓存』与『写库』之间竞态。类比:账本和便利贴是两套系统,便利贴撕晚了就会看错数。
-
两级缓存:本区 follower + 本区 leader:每个区域有离用户近的 follower 挡绝大多数读;本区 leader 管本区库与写协调。跨区域时,写还要再转发到持有 master 库的那个区域的 leader——不是『全球只有一个 leader 盒子』。
区域 A (master 库) 区域 B (slave 库) follower ←→ leader ←→ MySQL follower ←→ leader ←→ MySQL副本 ↑写转发到 master 区 leader ←———————┘读路径:follower 命中即返 → miss 找本区 leader → 再 miss 查本区 MySQL。写路径:follower 转发 → 本区 leader →(必要时)master 区 leader → MySQL,再异步通知其他 follower invalidate(作废旧缓存 = 撕掉过期便利贴)或 refill(把新边列表补回来)。
- read-after-write 只保证『自己刚写的』:发起写的那个 follower 会同步拿到更新,让该客户端下一次读能看到新值;其他机房是毫秒级最终一致。类比:你在本店柜台改完地址,本店立刻改底册;外地分店隔一会儿才同步——社交能接受,金融不行。论文负载里读约占 99.8%、写约 0.2%(约 500:1),所以这套折衷划算。
案例 1:查好友列表
Section titled “案例 1:查好友列表”assoc_range(user=1234, type=FRIEND, offset=0, limit=20)逐步发生:
- 客户端打到最近的 follower
- follower 缓存命中 → 直接返回前 20 个朋友(绝大多数请求停在这)
- miss → 本区 leader;leader 再 miss 则查 MySQL,再一路回写缓存
MySQL 单独扛不住这种延迟与 QPS;缓存命中才是『秒开』的原因。
好友列表几乎总是『只要最近/最相关的一小段』,所以 range 比『整表 JOIN』对口得多。
案例 2:加好友(配置了反向边)
Section titled “案例 2:加好友(配置了反向边)”assoc_add(from=A, to=B, type=FRIEND)逐步发生:
- follower 把写转给本区 leader;若本区库是 slave,再转到 master 区 leader
- 若 FRIEND 配置了 inverse 类型,系统会再写 B→A。两条边可能落在不同 shard,不保证原子——失败时可能短暂只剩一条(hanging association),靠异步任务修补
- 发起写的 follower 同步更新缓存;其他 follower 异步收到 invalidate/refill
应用层不用手维护双向边,但仍要容忍极短窗口的不对称——这是论文明确写出的取舍,不是『一次事务写完就绝对对称』。
案例 3:明星热点与跨区读写
Section titled “案例 3:明星热点与跨区读写”『千万粉丝』的 friend / like 列表不能整张塞进一次响应。TAO 给 association 查询设了每类型上限通常约 6000,超出必须用 pos 或时间游标翻页——同时挡住:单次扫描爆炸、缓存条目过大撑死内存。
跨区时:亚洲写、欧洲读,写走 master 区、读留本地——用写延迟换读的全球低延迟。热点对象还可在客户端侧再挡一层(带版本号),避免把单个 follower 打穿。
- TAO 不是 ACID:跨对象事务做不了;『移出群 + 记一条管理日志』只能最终一致 + 重试
- association list 有上限:默认约 6000;要十万条边得自己分页或换模型
- 反向边可能短暂不对称:跨 shard 写 inverse 失败会留下 hanging association,社交可忍、金融不行
- leader / master 切换窗口:写失败会直接报错给客户端,不自动重试;未持久化的写可能丢
- 不能跨 shard 排序:列表在 shard 内按 time 倒排;全站热榜要另接 feed 系统
- follower 故障切换可能打破 read-after-write:读打到备份 tier 时,可能暂时看不到自己刚写的值
适用 vs 不适用场景
Section titled “适用 vs 不适用场景”适用:
- 读写比极度倾斜(约 500:1 以上)的图数据
- 关系本身就是核心业务(社交、关注、点赞、评论)
- 全球多区域、需要单调/最终一致而非强一致
- 列表查询前 N 个的访问模式
不适用:
- OLAP 分析(算每个用户的二度网络规模)→ 图分析专用系统
- 强 ACID(金融、计费、库存)→ 关系型 DB 仍是首选
- 写多读少 → 双层缓存反而是负担
- 复杂图算法(最短路径、社区发现)→ Neo4j / Nebula / TigerGraph
- 需要跨多对象原子提交 → TAO 明确不做,别硬拧
历史小故事(可跳过)
Section titled “历史小故事(可跳过)”- 2007 年:Facebook 用 MySQL + memcached,应用层手维护双向边,bug 多到团队疯了
- 2009 年:第一版社交图谱缓存上线,写入仍直打 MySQL,read-after-write 不稳
- 2012 年:内部定名 TAO(The Associations and Objects),落地 leader/follower 双层
- 2013 年 6 月:USENIX ATC 论文公开,业界第一次见到全貌
- 之后 Twitter FlockDB、LinkedIn LIquid、Pinterest Zen 都长出了类似形状
- 数据模型决定一切:用对的抽象(objects + associations)比堆机器有用;一旦把图当一等公民,MySQL 上拧巴的东西就顺了
- 读写比是设计起点:约 500:1 才值得两级缓存 + 写转发;50:50 是过度设计
- read-after-write 不是免费的:要么跨区强一致付延迟,要么局部强、全局最终——TAO 选后者
- 专用系统打通用系统:一类查询量级压垮通用 DB 时,造『只擅长这一件事』的系统值得——前提是这件事真的够多
- 论文 PDF:TAO — Facebook Distributed Data Store for the Social Graph(12 页,写得清楚)
- 工程对照:Scaling Memcache at Facebook (NSDI 2013)(理解 look-aside 为何不够)
- 视频:Nathan Bronson — TAO talk @ ATC 2013
- 对照笔记:spanner-2012 —— 强一致全球部署,与 TAO 取舍相反
- 对照笔记:aurora —— 关系型在云上的另一条现代化路线
- 相关系统关键词:FlockDB / LIquid / Zen(形态相近的工业实现,便于横向对比)
- aurora —— Amazon Aurora,同样面对『MySQL 撑不住』但选了关系型现代化
- spanner-2012 —— Google Spanner,强一致 + 全球部署,TAO 选了相反取舍
- raft-2014 —— 共识协议;TAO 用写转发到 master leader,没用 Raft
- rest-fielding-2000 —— REST;TAO 的固定 API 更专,只服务一种数据模型
- bigtable —— 宽表存储另一极;TAO 是图 API + MySQL,不是任意 KV
- dynamo —— 高可用最终一致的另一条工业路线,可对照一致性光谱
- pnuts-2008 —— 另一套按应用选一致性的大规模存储,和 TAO 同属『不为强一致付满价』
- farm-2015 —— FaRM — 把一排机器的内存当成一个低延迟仓库