跳转到内容

F1 2013 — 把 Spanner 包成 SQL,扛起 AdWords 全部账单

是什么

F1 是 Google 2013 年公开的一个分布式 SQL 数据库,目的非常具体:把 AdWords(卖广告那套)原来跑在一堆 MySQL 分片上的核心库整体搬走。

日常类比:原来公司财务用了几十本账本,每本管一个分公司,跨账本对账要人肉抄;F1 给你一本单本账册,但内部自动复制到全球十几个仓库,你写一笔,所有仓库自动同步、强一致,断电丢仓库也不丢钱。

它建在 spanner-2012 之上——Spanner 提供”全球一致存储 + Paxos 复制”,F1 在上面套一层 SQL 引擎、二级索引、查询执行器、ORM。

简单说:Spanner 是底盘 + 发动机,F1 是方向盘 + 仪表盘 + 整车,开起来像 MySQL,跑起来全球一致。

为什么重要

不读 F1 没法回答这些事:

  • 为什么”全球一致 SQL + ACID + 高可用 + 扩缩容”这堆需求第一次被同时满足——之前要么牺牲一致性(Dynamo 系),要么牺牲扩展性(单机 Postgres)
  • 为什么 cockroachdb / TiDB / YugabyteDB 这一票”NewSQL”几乎都是 F1 + Spanner 的开源致敬版
  • 为什么 Google 敢让广告这种每秒上万、错一笔就赔钱的系统跑在跨洲同步复制上,commit 50-150ms 还能接受
  • AdWords 是 Google 命根子收入;F1 上线证明”全球一致 SQL 真能扛 OLTP”,给整个工业界吃了定心丸

核心要点

F1 的 5 个关键设计:

  1. 建在 Spanner 上:底层不自己搞,直接用 Spanner 的 Paxos 跨机房同步复制 + TrueTime + 全局事务。F1 只做 SQL 层。

  2. 层级 schema(hierarchical schema):子表的行物理穿插在父表行下面。比如 Customer 下挂 Campaign,再下挂 AdGroup,所有同一 customer 的数据在 Spanner 里聚成一块,落到同一个 Paxos group。跨 group 事务才慢,本地事务就快。

  3. 三种事务模式

    • snapshot read(只读、无锁、读历史快照)
    • pessimistic(拿锁,类 MySQL)
    • optimistic(先读、提交时检查冲突、失败重试)—— F1 默认推这个,client 端写完整业务再一次性 commit
  4. 分布式 SQL 查询引擎:能跨 shard 做 join、aggregate;同时支持低延迟 OLTP 查询和大规模并行扫描。

  5. ProtoBuf 列 + change history:F1 列里可以直接存 protobuf 对象(schemaless 风格),且每次 commit 自动生成 change history,下游订阅做实时同步。

承认的代价:单次 commit 50-150ms(同步 Paxos 跨洲),比 MySQL 慢一个数量级。F1 用”批量 + 并行 + 异步 ORM”把这个延迟藏起来。

实践案例

案例 1:AdWords 一个广告主的所有 campaign

广告主 ID = 12345,下面挂 1 万个 campaign,每个 campaign 下挂 N 个广告组:

CREATE TABLE Customer (CustomerId INT64, ...) PRIMARY KEY (CustomerId);
CREATE TABLE Campaign (CustomerId INT64, CampaignId INT64, ...)
PRIMARY KEY (CustomerId, CampaignId), INTERLEAVE IN PARENT Customer;
CREATE TABLE AdGroup (CustomerId INT64, CampaignId INT64, AdGroupId INT64, ...)
PRIMARY KEY (CustomerId, CampaignId, AdGroupId), INTERLEAVE IN PARENT Campaign;

INTERLEAVE IN PARENT 让 Spanner 把这一家子的行物理上塞在一起。一次”读这个广告主全部数据”的查询不用跨 Paxos group,本地一次扫描搞定。

案例 2:optimistic 事务怎么写

# F1 推荐的 ORM 用法(伪码)
with f1.optimistic_txn() as txn:
customer = txn.read("Customer", id=12345) # 读快照,不锁
campaigns = txn.read_all("Campaign", customer=12345) # 批量读,不锁
for c in campaigns:
c.budget = c.budget * 1.1 # 内存里改
txn.commit() # 这里才走 Paxos,50-150ms

关键:所有读不阻塞,业务逻辑完整跑完再一次性 commit。Paxos 慢的那 100ms 只占整个事务一次,不是每次读都付。

案例 3:分布式 join 怎么办

跨 shard 的 join F1 不是不能做,是不鼓励。论文实测:跨数据中心 join 一千万行,用并行扫描 + 哈希 join 几秒能出。但日常 OLTP 不会跑这种,只有报表和分析才跑。OLTP 几乎都吃层级 schema 的本地化红利。

伪码示意:

-- 这种跨广告主的扫描会被 F1 切成多个 shard 的并行扫描
SELECT customer_id, SUM(daily_spend)
FROM Campaign
WHERE date BETWEEN '2013-01-01' AND '2013-01-31'
GROUP BY customer_id;

F1 的执行计划会自动 fan-out 到所有 Spanner shard,每个 shard 本地聚合后再汇总——经典 volcano 风格的迭代器加分布式扩展。

案例 4:change history 喂下游

F1 每次 commit 自动生成一条变更记录(含 before/after),订阅这个流的下游系统可以做实时数据同步、缓存失效、广告反作弊检测。比传统 MySQL binlog 干净,因为 F1 是强一致写入,不存在主从延迟问题。

踩过的坑

  1. 当 MySQL 用 = 性能崩盘:直接把现有 ORM(先 SELECT 1 再 SELECT N)搬到 F1,每个 SELECT 一次 Paxos,N+1 问题被放大 100 倍。必须改成批量 + 并行 + 异步。

  2. schema 不分层 = 跨 group 事务暴增:如果不用 INTERLEAVE,所有表都平铺,事务大概率跨 Paxos group,2PC 跨洲,延迟从 100ms 涨到 500ms+。

  3. 二级索引也走 Paxos:F1 支持强一致二级索引,但每次写都要同步更新索引——索引多了写入慢。AdWords 团队反复权衡哪些该建索引。

  4. client 库要重写:MySQL 那套 client 假设”读便宜、写贵”,F1 是”读快、写也不便宜、但写一次包很多操作划算”。整套 ORM、应用层连接管理、重试逻辑都重做。

适用 vs 不适用场景

适用

  • 全球分布业务、要强一致 + ACID + SQL(金融、广告、订单、库存)
  • 数据量从 TB 长到 PB、QPS 从万长到十万的成长期系统
  • 不能丢任何一笔交易、机房挂了要秒级切换的场景
  • 业务能改 ORM 写法(批量 + 异步)的团队

不适用

  • 单机房单实例就够、QPS 几百、数据 GB 级 — 上 F1/Spanner 是用核武器打蚊子
  • 极低延迟 OLTP(< 10ms commit)— 同步 Paxos 跨洲做不到,留给本地 MySQL/Postgres 或内存数据库(tigerbeetle
  • 复杂 OLAP 报表为主 — 用专门数仓(BigQuery / Snowflake),F1 不擅长大扫
  • 没法改应用代码的遗留系统

历史小故事(可跳过)

  • 2007-2011:AdWords 用 MySQL 分片集群,分片管理团队几十人,每次扩容、加新分片维度都是大手术。
  • 2011 年起:Google 内部 spanner-2012 项目成熟,提供”全球一致 KV”。AdWords 团队决定在它上面建 SQL 层,叫 F1。
  • 2013 VLDB:F1 论文公开,宣布 AdWords 已经在 F1 上跑。这是 Google 第一次承认”用全球同步复制扛 OLTP 是可行的”。
  • 2014 起:F1 启发的开源派系冒出——CockroachDB(2015)、TiDB(2016)、YugabyteDB(2017)、Vitess on Spanner-like 模型,都在抄 F1 + Spanner 的核心思路。

学到什么

  1. 强一致全球 SQL 不是不可能,是要重新算账:把 100ms commit 摊到批量操作上,端到端可能比 MySQL 还快。
  2. Schema 设计决定一致性成本:层级 schema 让”哪些事务本地、哪些跨 group”在 schema 里就定了,比运行时再判断便宜得多。
  3. 同步复制 + Paxos 是新基线:在 Spanner/F1 之后,“异步复制 + 偶尔丢数据”在大公司核心系统逐渐被替换。
  4. NewSQL 是真存在的第三条路:CAP 不是非此即彼,工程能在”全球一致 + 高可用 + SQL”三角里找到平衡点(代价是延迟)。
  5. 延迟可以摊掉,丢数据摊不掉:F1 选择把延迟难题留给应用层(异步 ORM、批量),把”绝不丢数据”的责任独占。这种边界划分思路通用——把不可摊的成本放在系统底层。

延伸阅读

  • 论文 PDF(VLDB 2013,14 页):F1: A Distributed SQL Database That Scales
  • spanner-2012 —— F1 的底盘,必须先理解 Spanner 才能理解 F1
  • cockroachdb —— F1 思想的开源版,文档里直接说”灵感来自 Spanner/F1”
  • paxos —— Spanner 复制依赖的共识协议
  • brewer-cap-2000 —— F1 是对 CAP “必须二选一”的工程反例
  • 后续论文:F1 Query (VLDB 2018),把查询引擎抽象成独立组件
  • mapreduce —— Google 老一代大数据范式,与 F1 互补:MapReduce 跑批,F1 扛在线
  • gfs —— Spanner 间接依赖的存储基石

关联

  • spanner-2012 —— F1 的存储层;F1 = Spanner + SQL + ORM + 查询引擎
  • bigtable —— Spanner 的前身;F1 是从 NoSQL 时代回到 SQL 的标志
  • paxos —— 同步复制的共识基石
  • system-r-1976 —— 关系型 SQL 的鼻祖,F1 是它的全球分布式重生
  • aries-1992 —— 单机日志恢复算法;F1 用 Paxos 替代了 ARIES 那种主从 redo log
  • brewer-cap-2000 —— F1 是对 CAP 的工程反例:CP + 可用性可同时拿
  • cockroachdb —— F1 思想的开源继承
  • aurora —— AWS 的另一条路:单 region 共享存储而非跨 region 同步
  • tigerbeetle —— 极端低延迟金融 OLTP,与 F1 在另一端