IT 论文精读 · PAPER 19
Chang, Dean, Ghemawat 等 · Google · OSDI 2006
2006 年,Google 公开了 Bigtable——它内部用来存海量数据的「一张超级大表」。Google Earth 的卫星图、每个网页的历史快照、亿万用户的个性化数据,全塞在这类表里,横跨成千上万台机器。它是继 GFS(Paper 17)、MapReduce(Paper 18)之后 Google「老三件套」的第三件,也是开源世界 HBase、Cassandra 这一整类「宽列数据库」的祖师爷。
我们熟悉的数据库(就是银行、电商背后那种,规规矩矩的表格 + SQL 查询)在几百万、几千万行时又快又好用。可 Google 要存的东西是另一个量级:给全网每个网页存一行,就是几百亿行;每个网页每被爬一次还要留一个带日期的旧版本;不同网页有的字段有、有的没有,乱七八糟、大部分格子是空的。这么大、这么稀、还随时在长的数据,硬塞进传统数据库既装不下、也太贵。Google 需要一个专门为「大到离谱、形态松散」而生的存储系统。
想象一张 Excel,但把它的三条限制全撤了:行可以有几百亿、列可以随时随地加、每个格子还能同时存好几个带时间戳的历史版本。更关键的是它「稀疏」——绝大多数格子是空的,而空格子完全不占地方,所以你尽管随意加列、留白,不心疼。这张表还有个讲究:所有行按名字(行键)排好序。于是你只要给行起个好名字(比如把网址反过来写,让同一网站的页面挨在一起),相关的数据天然就排在相邻位置,成片地查特别快。
表太大,一台机器装不下,就把它按行横着切成一段一段,每段(Google 叫一个 tablet)交给一台机器保管;因为行是排好序的,切出来的每段都是一个连续区间。真正的巧思在怎么读写又快又不怕机器坏:新数据进来,先在一个「流水账」上记一笔(这本流水账放在可靠的 GFS 上,机器烧了也丢不了),再顺手塞进内存里的一个「小账本」;小账本满了,就把它整理誊清、冻成一个只读的文件存到硬盘,然后换本新的接着记。查数据时,把内存里的小账本和硬盘上那一摞只读文件「合着看」——新的盖旧的。这套「先记账、攒够了誊清、旧账本只读不改」的打法,让写入飞快、机器随时坏了也能照着流水账恢复。系统还会时不时把一摞旧文件合并成一个,免得越查越慢。
Bigtable 撑起了 Google Analytics、Google Earth、个性化搜索、网页索引等一大批产品;论文发表时,Google 内部已有几百个 Bigtable 集群、几万台机器在跑它。开源世界照着这篇论文做出了 HBase、又结合 Amazon Dynamo 的思路做出了 Cassandra,「宽列 NoSQL」从此成为一大类主流数据库;它那套「先写内存+流水账、再誊清成只读文件」的引擎思想,还长成了 LevelDB、RocksDB,今天无数数据库的底子。诚实的代价:它只保证单独一行内的改动是「要么全成、要么全不成」,跨很多行的复杂交易、SQL 那种花哨的多表关联,它一概不做——正是靠砍掉这些,它才换来了近乎无限的横向扩展。
Bigtable 是 Google 的「一张能长到 PB 级的稀疏大表」:行按名字排序、可有几百亿,列随手加,格子存带时间戳的多版本,空格不占地方。它按行把表切成段摊到上千台机器,用「先记流水账+写内存、攒满了誊成只读文件、旧文件定期合并」的引擎读写。它砍掉了通用事务和 SQL,换来近乎无限的规模——是宽列 NoSQL 的开山之作。
想看它的数据模型长什么样、三级寻址怎么在上千台机器里定位一行、还有 memtable + SSTable 的读写与合并机制? → 切到精读版
Bigtable 是 Google 提出的一套分布式结构化数据存储系统。它把数据组织成一张稀疏、有序、多维的大表——每个单元格由 (行键, 列, 时间戳) 三元组定位,值是一串不被系统解释的字节。这张表能横跨上千台机器、长到 PB 级:它按行键有序地把表切成一段段 tablet 分摊到众多机器,底层用 LSM-tree 式引擎(内存中的 memtable + GFS 上一摞只读 SSTable + 定期合并)读写,用 GFS 存文件、用 Chubby 做协调与选主。它主动放弃了关系数据库的通用事务与 SQL,换来近乎无限的横向扩展,是 Google「老三件套」的第三件,也是宽列 NoSQL(HBase、Cassandra)的直接源头。
作者是 Fay Chang、Jeffrey Dean、Sanjay Ghemawat、Mike Burrows 等一众 Google 工程师,论文发表于 OSDI 2006。它是 Google「分布式系统老三件套」的收官之作,站在前两件的肩膀上:数据文件与日志都存在 GFS(Paper 17)上,很多批量数据加工用 MapReduce(Paper 18)完成,协调与选主交给 Chubby(Burrows 正是 Chubby 的作者,也在此列)。它对外启发深远:开源界照它做出 HBase,Facebook 把它的数据模型与 Dynamo 的去中心化分布结合成 Cassandra;它的单机引擎思想后来被 Dean 与 Ghemawat 提炼成开源的 LevelDB,再长成 Facebook 的 RocksDB,成为今天大量数据库的存储底座。Google 自己也在它之上继续演进出 Percolator(加事务)、Spanner(全球一致),并把它作为公有云服务 Cloud Bigtable 对外开放。
2000 年代中期,Google 内部涌现出大量既要存海量数据、又对形态和访问方式有特殊要求的场景:为全网数十亿网页各存一行、每次抓取留一个带时间戳的版本;Google Earth 的 TB 级卫星与地图瓦片;亿级用户的个性化搜索偏好;各种爬虫、分析产生的中间数据。它们的共同点是——
关系型数据库为「中等规模 + 复杂查询」而生,硬撑这种「超大规模 + 简单查询 + 松散多版本」的负载既贵又难扩。于是 Google 决定不通用、只解决自己这类问题:设计一个能横向扩到上千台机器、面向稀疏多版本数据、只保留最必要语义的存储系统——用放弃通用性来换极致的规模与简单。
Bigtable 最漂亮的一步是它的数据模型。论文一句话定义它:一个稀疏的、分布式的、持久化的、多维有序 map(映射表)。这个 map 的「钥匙」是一个三元组、「锁开出来」的是一串字节:
(行键 row, 列 column, 时间戳 timestamp) → 值 value(不被解释的字节串)
拆开看四个维度:
maps.google.com/index.html 写成 com.google.maps/index.html),于是同一网站的所有页面排在连续区间里。族:限定词(family:qualifier),如 anchor:cnnsi.com。列族是访问控制、磁盘与内存计量的基本单位,数量要少(几百个以内)、极少变;而族内的列(限定词)可以有无限多、随时新增。这就同时满足了「结构可控」与「字段可无限扩展」。contents: 列族存正文的多个时间戳版本,anchor: 列族每个来源站是一列。格子稀疏、可多版本,钥匙是 (行, 列, 时间戳)。表按行键有序,于是可以按行键区间横切成一段段 tablet(每个约 100–200 MB),tablet 是分布与负载均衡的基本单位——一台 tablet server 负责若干个 tablet,机器多了就多切几段、摊得更开。系统有三类角色:一个轻量的 master(分配 tablet、监控服务器上下线、均衡负载、回收 GFS 垃圾、处理建表/改列族),众多 tablet server(真正处理读写),和链接进每个应用的客户端库。
难点是:几百亿行切出海量 tablet,客户端怎么快速找到「某一行归哪台服务器管」?Bigtable 借鉴 B+ 树,用三级寻址:
顺着「Chubby → 根 tablet → METADATA → 用户 tablet」三跳,就能定位任意一行;论文算过,这套结构足以寻址 2³⁴ 个 tablet。客户端还会缓存已查到的位置,绝大多数请求根本不碰 master——这正是 master 能保持轻载、不成瓶颈的原因。tablet 到底归谁,用 Chubby 锁裁定:每台 tablet server 在 Chubby 的一个目录里创建并独占一个锁文件,锁在则活、锁丢则停;master 监视这个目录来发现服务器上线,并靠「能否抢到某台的锁」来判定它是真死还是只是网络隔断,从而安全地把它的 tablet 重新分配。
这是全篇工程上最精彩的部分,也是被后世抄得最多的一招。一个 tablet 的持久状态存在 GFS 上,由三样东西构成:
于是读写路径变成:
只往内存和日志里加、从不原地改磁盘,内存迟早会满,于是有三种压实(compaction)来收拾:
这套「写只追加、旧文件只读、后台定期合并」正是 LSM-tree(日志结构合并树)的思路:用「顺序写 + 批量整理」换来了极高的写吞吐与简单的容错。为什么这么设计能又快又稳——写不做随机磁盘改写(分布式文件系统上最贵的操作),全是顺序追加;SSTable 一旦生成就不可变,读它无需加锁、并发天然安全,机器坏了直接从 GFS 上重新加载 SSTable + 重放日志即可,恢复干净利落。
光有上面的骨架还不够快,论文用一组务实的优化把性能拉满:
论文在一个 tablet server 数从 1 加到 500 的集群上,用 1000 字节的值测了随机读、随机写、顺序读/写、扫描等负载。几个要点:
这些数字要传达的不是「它是最快的数据库」,而是——在这种超大规模、简单负载下,一套受限但可横向扩展的设计,能把成千上万台廉价机器聚成一个几乎无限大的存储层。
Bigtable 的贡献和 MapReduce 一样,主要在抽象与工程取舍,而非某个算法。它证明了一件事:放弃关系数据库的通用性(通用事务、SQL、多表关联),只保留「按行键读写 + 扫区间 + 多版本」这组最必要的语义,就能换来近乎无限的横向扩展。它开创的「宽列(wide-column)」数据模型——行键有序、列族 + 无限限定词、稀疏多版本——成了一整类数据库的范式:开源界的 HBase 几乎是它的直接复刻,Facebook 的 Cassandra 把它的数据模型嫁接到 Dynamo 的去中心化分布上,还有 Hypertable、Accumulo 等。它与 GFS、MapReduce 合成 Google「老三件套」,共同定义了「大数据基础设施」的样貌。更深远的是它普及的一整套心法:用有序行键把逻辑局部性变成物理局部性、LSM 式「顺序写 + 只读文件 + 后台合并」的存储引擎、用不可变数据换免锁并发、把协调/选主外包给一个 Chubby 这样的小而可靠的锁服务——这些都被后来无数系统继承。它的单机引擎更被 Dean 与 Ghemawat 提炼成 LevelDB,再演化出 RocksDB,如今是从 MySQL 的存储引擎到各种 NoSQL 的共同底座。
① 一句话:Google 的分布式结构化存储系统,把数据组织成能横跨上千台机器、长到 PB 级的「稀疏、有序、多维大表」,用 GFS 存文件、Chubby 做协调。
② 数据模型:(行键, 列, 时间戳) → 字节值。行按行键字典序、单行读写原子;列归列族(访问控制单位)+ 无限限定词;每格多版本;稀疏(空格不占地)。
③ 行键即局部性:把逻辑相关数据编成相邻行键(如网址反转),物理上就挨在一起,扫区间极快。
④ 切片与角色:按行键区间切成 tablet(约 100–200 MB),由众多 tablet server 服务、轻量 master 分配调度、客户端库直连。
⑤ 三级寻址:Chubby → 根 tablet → METADATA → 用户 tablet,形如 B+ 树,客户端缓存位置,master 因此轻载不成瓶颈。
⑥ 引擎(LSM):写 = 追加提交日志(GFS)+ 插入内存 memtable;读 = memtable 与只读 SSTable 合并看;memtable 满则次压实成 SSTable,另有合并/主压实控数量、清删除。
⑦ 为何又快又稳:写全是顺序追加、不做随机磁盘改写;SSTable 不可变→读免锁、故障后重放日志即恢复。
⑧ 精调:局部性组、约 10:1 压缩、布隆过滤器省寻道、两级缓存、每服务器一份日志、靠不可变性让 tablet 分裂近乎零成本。
⑨ 规模:论文时内部 388 个集群、约 24500 台 tablet server,撑起 Google Earth、Analytics、个性化搜索等;单表可达数百 TB。
⑩ 影响与局限:宽列 NoSQL(HBase、Cassandra)与 LevelDB/RocksDB 的源头;但只有单行事务、无 SQL/二级索引,后由 Percolator、Spanner 补齐。