Bigtable: A Distributed Storage System for Structured Data 论文阅读

发表于 OSDI’06

#0. 摘要

Bigtable 是一个分布式存储系统,用于管理结构化数据,能够扩展到巨大的规模:PB 级别的数据,数千台机器。 Google 的很多项目都保存在 Bigtable 中,包括网页索引,Google Earth 和 Google Finance。

#1. 引言

Bigtable 从很多方面像一个数据库:它与数据库有很多类似的策略。并行数据库,内存数据库已经实现了缩放性和高性能,但是 Bigtable 提供了不同的接口。 Bigtable 不支持完整的关系模型;取而代之,它提供一个简单的数据模型,还支持动态改变数据布局和格式。而且允许客户端对底层存储的数据的局部性进行推理。数据使用行和列名进行索引,行列是任意的字符串。 Bigtable 把数据视为字符串,需要客户端序列化和反序列化后使用。客户端能够通过细致地控制 schema 来控制数据的局部性。最后,Bigtable 的 schema 有参数控制能否在 OOM 的时候继续提供服务。

#2. 数据模型

Bigtable 是一个稀疏、分布式、持久化的多维排序映射表。这个表是通过行键、列键和时间戳索引的。表中的值是字节串。

(row:string, column:string, time:int64) -> string

Google 分析了很多潜在的应用场景,最终敲定了这个数据模型。

#行键

表格中的行键是任意的字符串,最大可以是 64KB,典型值是 10-100 字节。

Bigtable 会按照字典序排序行键。表中的所有行会被动态划分为更小的段,称为 Tablet,是数据分散和负载均衡的单元。客户端能够控制行键的格式,以便于控制数据的局部性。例如,在 Google 的 Webtable 中,行键是反转的 hostname,这样 maps.google.com/index.html 就会存储在 com.google.maps/index.html 的位置。

#列族(Column Family, CF)

表格中的列被分成若干组,称为列族,列族是基本的 ACL 单元。一个列族中的所有数据通常都是同一种类型(同一个列族的会放在一起压缩)。列族必须在表创建时指定,一旦写入数据之后就不能修改。列族数量按预期是很小的,最多几百个,而且很少会改变。相对的,列的数量没有上限。

列键使用 family:qualifier 的形式命名。其中 family 必须是可打印的字符,qualifier 可以是任意的字节串。例如 Webtable 中的一个 CF 是 language,用于存储网页的语言。language 下只有一个列,存储语言 ID。另一个 CF 是 anchor,其中每一个列名是指向的站点的名字,值是链接文本。例如 Webtable 中的一条数据可能是这样的:

Row Keycontentsanchor:cnnsi.comanchor:my.look.ca
“com.cnn.www”“<html>”“CNN”CNN.com

访问控制和磁盘和内存统计都是在 CF 级别进行的。因此可以通过 CF 管理不同的应用程序:例如一部分只写入新的基本数据,一部分读基本数据然后创建衍生数据,一部分只读取已有的数据。

#时间戳

Bigtable 中的每个单元都可以包含相同数据的多个版本,这些版本通过时间戳进行索引。 Bigtable 的时间戳是 64 位整数,这个时间戳可以由 Bigtable 自动生成(使用现实世界的毫秒),也可以由客户端提供。如果由客户端提供,需要保证并发写的时候,多个客户端的时间戳是不冲突的。 Bigtable 会按照时间戳的逆序存储数据,这样最新的数据会被最先读取。

为了更容易地管理多版本数据,Bigtable 提供了两个 CF 级别的设置:允许 Bigtable 自动回收旧版本的数据。客户端可以选择保留多少个版本,或者只保留最新的版本。

在 Webtable 中,contents 列存储的数据的时间戳是文档的实际爬取时间,而且设置了只保存最新 3 个版本。

#3. API

写入 Bigtable 的代码示例:

1
2
3
4
5
6
7
8
9
// Open the table
Table *T = OpenOrDie("/bigtable/web/webtable");

// Write a new anchor and delete an old anchor
RowMutation r1(T, "com.cnn.www");
r1.Set("anchor:www.c-span.org", "CNN");
r1.Delete("anchor:www.abc.com");
Operation op;
Apply(&op, &r1);

读取 Bigtable 的代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
Scanner scanner(T);
ScanStream *stream;
stream = scanner.FetchColumnFamily("anchor");
stream->SetReturnAllVersions();
scanner.Lookup("com.cnn.www");
for (; !stream->Done(); stream->Next()) {
printf("%s %s %lld %s\n",
scanner.RowName(),
stream->ColumnName(),
stream->MicroTimestamp(),
stream->Value());
}

Bigtable API 提供了这些方法和特性:

  • 修改集群
  • 创建和删除表
  • 创建、修改和删除 CF,修改 ACL
  • 读、写和扫描数据
  • 把一组操作打包成一个原子操作
  • 单行事务:可以对单行数据进行原子的 Read-Modify-Write 操作
  • 单元格作为计数器
  • 在服务器的地址空间中执行客户端提供的 Sawzall 脚本(PS. 类似 lua in redis) Sawzall 的 API 不能写数据,但是可以进行一些复杂的数据变换、过滤和聚合操作。

Bigtable 还可以和 MapReduce 结合使用,Google 写了很多包装器,用于在 MapReduce 任务中读写 Bigtable。

#4. 基本构建块

Bigtable 是建立在其他几个已有的 Google 系统之上的:

  1. GFS(Google File System):用于存储日志和数据
  2. 集群管理系统:调度任务,管理资源,处理机器故障,和监控集群状态
  3. Chubby:分布式锁服务

Bigtable 使用了 Google SSTable 文件格式。SSTable 提供一个持久化的、有序的、不可变的 K-V 映射,其中 K 和 V 都是任意的字节串。SSTable 文件能提供的操作有:

  • 按 key 查找 value
  • 迭代特定范围内的所有 key-value 对

SSTable 内部包含一个块序列(块大小可变,默认 64KB)。SSTable 文件末尾存储着块索引,用于定位块。当 SSTable 文件被打开时,块索引会被读入内存。每次查询操作只需要一次磁盘 seek 操作:首先在内存索引中二分查找到块的位置,然后读取该块即可。可选地,SSTable 还可以被完全映射到内存中,这样就不需要访问磁盘了。

Bigtable 还依赖于高可用和持久化地分布式锁服务 Chubby。 Chubby 服务包含 5 个副本,其中一个会被选举为 Master,依赖于 Paxos 算法保证副本数据一致性。Chubby 提供了一个由目录和文件组成的命名空间,每个目录或文件都可以用作锁,而且读写文件的操作是原子的。 Chubby 客户端库提供对 Chubby 文件的一致性缓存,每个客户端都维持着一个 session。如果客户端在超时时间内没有成功续约,则会过期,并失去所有的锁。 Chubby 客户端还可以注册 callback,当锁的状态发生变化时,这个 callback 会被调用。

Bigtable 用 Chubby 实现这些任务:

  • 保证任意时刻最多只有一个 Master;
  • 保存 Bigtable 的 bootstrap 位置;
  • 发现新 Tablet Server 的位置;处理 Tablet Server 的后事;
  • 存储 schema 信息:每个表的 CF;
  • 存储 ACL;

Bigtable 强依赖于 Chubby,如果 Chubby 不可用,Bigtable 也不可用。 Google 衡量了由于 Chubby 问题导致的 Bigtable 不可用的平均时间,在 14 个 Bigtable 集群,使用 11 个 Chubby 实例,这个时间只占 0.0047%。受影响最严重的单个集群,这个时间也只占 0.00326%。

#5. 实现

Bigtable 的实现包含三部分:链接到应用程序中的客户端库、一个 Master 进程和若干个 Tablet Server 进程。Tablet Server 可以动态地增加或删除。

Bigtable 的 Master 进程负责:

  • 分配 Tablet 到 Tablet Server
  • 检测 Tablet Server 的增删
  • 均衡 Tablet Server 的负载
  • 垃圾回收 GFS 中的文件
  • 处理 Schema 变更(例如表和 CF 的增删)。

Tablet Server 负责管理 Tablet,每个 Tablet Server 通常会管理 10 个到 1000 个 Tablet。 Tablet Server 还要负责处理客户端的读写请求,还有分裂过大的 Tablet。

和许多单主节点的分布式存储系统(比如 GFS)一样,客户端数据是不会流过 Master 节点的,而是直接和 Tablet Server 交互。因为客户端不依赖于 Master 来获取 Tablet 位置信息,大多数客户端从来不会和 Master 通信。因此 Master 的任务是很轻松的。

一个 Bigtable 集群存储多个表,每个表由一系列的 Tablet 组成,每个 Tablet 包含了一段行范围的数据。初始时,每个表只有一个 Tablet。随着表的增长,Tablet 会被分裂成更多更小的 Tablet,默认 Tablet 的大小是 100-200 MB。

#5.1 Tablet 位置

1
2
3
4
5
6
7
8
9
10
Level 0  |         Chubby file
| |
| v
Level 1 | Root Tablet (META1)
| / | | \
| v v v v
Level 2 | META2 META3 META4 ...
| /|\ /|\ /|\ ...
| vvv vvv vvv ...
Level 3 | USER1.. ... ... ...

Bigtable 使用了三级层次结构来存储 Tablet 位置信息。第 0 级是一个保存于 Chubby 中的文件,保存着 Root Tablet 的位置信息。

Root Tablet 是 METADATA 表的第一个 Tablet,而且不会被分裂。

METADATA 表是整个系统的元数据表,其中保存着所有其他表的 Tablet 位置信息。其行键是表名和末尾行的编码,值是 Tablet 的位置信息。 METADATA 中的每一行数据大约占用 1KB。按照保守估计,每个 Tablet 大小 128MB 的话,三级的 METADATA 表可以存储 2^34 个 Tablet 的位置。

客户端库会缓存 Tablet 的位置信息。如果客户端没有缓存,或者发现缓存的位置信息不正确,那么它就会递归地到上一级查找。按照这种算法,如果当前没有缓存,定位需要 3 个 RT,加上 1 次 Chubby 读。如果缓存不对,那么最多需要 6 个 RT,加上 1 次 Chubby 读。虽然 Tablet 位置是保存在内存中的,即不需要访问 GFS,但是这种定位方法还是很慢。为了加速一般情况,Bigtable 会让客户端预取 Tablet 位置信息,每次读的时候都多读几个 Tablet 的位置信息。

为方便 debug,METADATA 表中还会存储每个 Tablet 的时间日志。

#5.2 Tablet 分配

每个 Tablet 最多只会分配给一个 Tablet Server。 Master 负责记录活着的 Tablet Server,以及 Tablet 到 Tablet Server 的映射关系。如果一个 Tablet 还没有分配,而且某个 Tablet Server 有空闲空间,那么 Master 就会向这个 Tablet Server 发送一个 Tablet load 请求,把这个 Tablet 分配给他。

Bigtable 使用 Chubby 追踪 Tablet Server 的状态。当一个 Tablet Server 启动时,它会在 Chubby 的特定目录下创建一个名字唯一的文件(Server file),并且获取文件的排他锁。Master 会监听这个目录来发现新的 Tablet Server。如果一个 Tablet Server 丢失了这个排它锁,那么它就不再对外提供服务。它会先检查 Server file 是否存在,如果存在就尝试重新获取锁,否则就会直接退出。当 Tablet Server 结束时,它会释放锁,就会被 Master 拿到。

Master 需要周期性地监测 Tablet Server 是否在正常工作,如果不是,就要尽快把它负责的 Tablet 分配给其他 Tablet Server。为此,Master 会定期询问 Tablet Server 是否还持有 Server file 的锁,如果没有,或者多次尝试都超时,Master 就会尝试拿一下这个 Tablet 的锁。如果拿到了,说明 Chubby 服务正常,Tablet Server 挂了,就会把这个 Server file 删掉,让 Tablet Server 自行退出;然后把它负责的 Tablet 添加到未分配集合中。为了确保 Bigtable 集群不会受 Master 和 Chubby 之间网络的影响,Master 的 Chubby session 过期,就会直接退出。

当一个新 Master 被集群管理系统拉起的时候,它需要先检查有哪些 Tablet,按照如下步骤:

  1. Master 获取一个 Master lock;
  2. 扫描 Server file 目录,找到所有活着的 Tablet Server;
  3. 与每一个 Tablet Server 通信,询问它们负责的 Tablet 列表;
  4. 扫描 METADATA 表,找到所有的 Tablet:此时就可以找到哪些 Tablet 还未分配。

一个小问题是如果 METADATA Tablet 还没有分配的话,Master 就没办法找到 METADATA 表的位置,那步骤 4 就无法进行。因此在步骤 4 之前,Master 会额外检查一下 Root Tablet 有没有分配,如果没有分配,就会额外把 Root Tablet 添加到未分配集合中,最终 Root Tablet 会被分配给空闲的 Tablet Server。

已有 Tablet 的集合只有当以下时候才会变化:

  1. 创建或删除表;
  2. Tablet 合并:两个已有的 Tablet 合并成一个大的 Tablet;
  3. Tablet 分裂:一个 Tablet 被分裂成两个更小的 Tablet;

情况 1 和 2 都是由 Master 初始化的,Master 自然能够追踪这些变更;情况 3 是由 Tablet Server 完成的,Tablet Server 会把分裂出来的新 Tablet 信息记录在 METADATA 表中,然后通知 Master。即使这条通知消息丢失了,当 Master 要求 Tablet Server 加载分裂出来的 Tablet 时,Master 也会找到这个新 Tablet。

#5.3 Tablet 服务

Tablet 的持久化状态是保存在 GFS 中的。更新操作会被记录成操作日志(Commit log),其中保存着 redo 记录。在这些更新中,最近提交的更新会被保存在内存中的一个称为 Memtable 的有序数据结构中。为了恢复一个 Tablet,Tablet Server 需要先从 METADATA 表中找到这个 Tablet 的元数据,包括组成这个 Tablet 的一系列 SSTable 文件,还有一系列 redo 点,即指向操作日志的指针。 Tablet Server 会把这些 SSTable 文件的索引加载到内存中,然后通过应用自 redo 点开始的所有更新来重建 Memtable。

写操作:

  1. Server 检查是否合法,是否有权限(通过读取 Chubby 中的 ACL)
  2. 有效的变更会被写入操作日志:会分批写入提高吞吐量
  3. 变更会被写入 Memtable

读操作:

  1. Server 检查是否合法,是否有权限(通过读取 Chubby 中的 ACL)
  2. 在由一系列 SSTable 和 Memtable 组成的数据视图中查找数据

Tablet 分裂和合并不影响读写操作。

#5.4 数据回收

Minor compaction:写入数据的时候,Memtable 会增大,当增加到一定程度时,就会被冻结,然后转换成 SSTable 写入 GFS。与此同时,一个新的 Memtable 会被创建。

Minor compaction 的目的是:

  • 减小内存占用
  • 减少重启时的恢复时间

Minor compaction 不影响读写操作。

每次 Minor compaction 都会创建一个新的 SSTable。如果一直增加下去,读的时候就需要合并很多 SSTable。因此 Bigtable 会定期在后台执行 Merging compaction,也叫 Major compaction。 Major compaction 会把多个 SSTable + Memtable 合并成一个 SSTable。合并的时候,会把被删除的数据去掉。

Major compaction 的目的是:

  • 回收删除数据占用的空间
  • 保证被删除的数据能够及时消失(对于敏感数据)

#6 细化

为了实现高性能,高可用,高可靠性,Bigtable 实现上还做了很多细节优化。

#局部性群组

客户端可以把多个 CF 设置成一个局部性群组。在每个 Tablet 中,每个局部性群组都会有一个单独的 SSTable。将不经常一起访问的 CF 划分成多个局部性群组,能够提高读性能。例如,Webtable 中的页面元数据(例如 langauge 和 chunksums)可以放进一个局部性群组,而网页内容可以放进另一个局部性群组:因为一个读元数据的应用程序往往不会读取内容。

此外,局部性群组有一些有用的调优参数,例如,局部性群组可以被声明为 in-memory。这样的话,这个局部性群组的 SSTable 在用到时就会被加载到内存中,在读取这个局部性群组内的 CF 时就不用访问磁盘了。这个特性对于频繁访问的小块数据很有用:METADATA 表中的 location CF 就是一个 in-memory 的局部性群组。

#压缩

客户端可以控制一个局部性群组的 SSTable 是否压缩,以及使用什么算法。压缩的粒度是 SSTable 中的块,而不是整个 SSTable,这样读的时候就不用解压整个 SSTable。很多客户端会使用一个两趟压缩算法,第一趟用 Bentley-McIlroy 模式,用一个长窗口压缩长的重复串,第二趟用一个快速的算法,在 16KB 的小窗口内压缩剩下的数据。这种两趟压缩算法是很快的,在现代机器上,压缩速度可以达到 100-200MB/s,解压速度可以达到 400-1000MB/s。

虽然我们在选择压缩算法的时候更倾向于压缩速度而不是压缩率。这种两趟压缩算法的压缩率也不错。在 Webtable 中进行测试,压缩 html 文档,每个文档只保存一个版本,两趟压缩算法的压缩率能达到 10:1,比 gzip 的 3:1 或者 4:1 好的多。如果保存多个版本,压缩率会更高。

#缓存以提升读性能

为了提升读性能,Tablet Server 使用了两级缓存。

  • Scan 缓存是 high-level 的缓存,缓存 SSTable 返回的 kv 对。
  • Block 缓存是 low-level 的缓存,缓存从 GFS 中读出来的 SSTable 块。

#布隆过滤器

读操作必须要从所有的 SSTable 中查找数据,如果 SSTable 不在内存中,就会需要多次磁盘访问。 Bigtable 允许客户端指定创建一个布隆过滤器,用于快速判断一个 key 是否在 SSTable 中。

#提交日志实现

如果我们把每个 Tablet 的提交日志都放在单独的 log 文件中,就需要往 GFS 里面写大量的文件。再加上 GFS 实现,可能导致大量磁盘访问。另外,用多个 log 文件也不容易做 group commit 优化。因此 Bigtable 设计成每个 Tablet Server 使用一个 log 文件。

使用单个 log 文件提供了正常路径下的性能优势,但是会让恢复变得麻烦。当 Tablet Server 挂了,它负责的 Tablet 需要被迁移走,而每个接管的 Server 通常只会分到其中一部分。为了进行恢复,每个 Server 都需要读取一遍完整的 log 文件,会导致巨量的读放大。

为了避免这种读放大,首先,Bigtable 会按照 <table, row name, log sequence number> 顺序排序 log 文件。排序后,对于单个 Tablet 的变更就是连续存储的,因此新 owner 只需要进行一次 seek 加顺序读就可以了。为了并行化这种排序过程,log 文件会按照 64MB 大小分段,然后分散在不同的 Tablet Server 上并行排序。这种排序过程是由 Master 协调的。当任意 Tablet Server 需要从 log 文件恢复 Tablet 的时候,就会开始这种排序过程。

由于各种各样的原因,有时往 GFS 上写日志的时候,会有性能抖动。例如 GFS 正在故障恢复,或者网络拥塞,或者单纯就是负载太重。为了避免 GFS 延迟尖峰,每个 Tablet Server 实际上会有两个写 log 的线程,每个线程写自己的日志文件,同一时刻只会有一个线程被使用。如果写入的性能太差,就会切换到另一个线程。队列里面后续的变更也会由另一个线程来处理。日志条目中包含了自增序列号以防止线程切换的时候重复应用相同的日志。

#加速 Tablet 恢复

如果 Master 要把一个 Tablet 从一个 Server 迁移到另一个,源 Server 会先对这个 Tablet 进行一次 Minor compaction。这次 compaction 能够缩短恢复时间,因为需要恢复的 log 变少了。在 compaction 之后,源 Tablet Server 就会停止服务这个 Tablet。在真正卸载这个 Tablet 之前,Server 会再做一次(通常是很快的)Minor compaction,目的是消除第一次 compaction 期间收到的变更。第二次 compaction 之后,就没有剩余的 log 了,目标 Server 只需加载 Tablet 就可以了。

#利用不可变性

除了 SSTable 缓存以外,Bigtable 系统的各种其他部分也利用了 SSTable 文件的不可变性。比如,并发读 SSTable 的时候不需要任何同步机制。因此行之间的并发控制就可以实现得非常高效。唯一的例外是 Memtable。为了减少 Memtable 的读争抢,我们让每个 Memtable 的行都是 CoW 的,这样读和写就可以并行进行。

因为 SSTable 是不可变的,所以移除已经删除的数据就转换成了垃圾回收过时 SSTable 的问题。每个 Tablet 的 SSTable 都被注册到 METADATA 表中。Master 在 SSTable 文件集合上使用标记-清除算法来回收过时的 SSTable。

SSTable 的不可变性还允许我们快速地分裂 Tablet。相对于给每个 Tablet 生成一组新的 SSTable 文件,Bigtable 让子 Tablet 共享父 Tablet 的 SSTable 文件。

#7 性能评估

评估实验使用了一个 N 个 Tablet Server 的 Bigtable 集群。用来评估性能和可拓展性随 N 的变化。每个 Tablet Server 使用 1GB 内存,写入一个 1,786 机器的 GFS 集群,每个 GFS 节点配置两块 400GB 的 IDE 硬盘。还有 N 个客户端用于生成读写负载。N 个客户端相对于 N 个 Tablet Server 是非常充足的,这样可以保证客户端的负载不会成为瓶颈。每个机器有两颗双核 Opteron 2GHz 的 CPU。

(略)

#8 现实世界的应用

截至 2006 年 8 月,Google 内部有 388 个生产 Bigtable 集群,一共有 24,500 个 Tablet Server。其中大部分都是供开发使用,因此经常是闲置的。大部分集群都有很少的机器数。有 14 个忙的集群,一共由 8069 个 Tablet Server 组成,服务了 1.2M 的 QPS,进的 RPC 流量大约是 741MB/s,出的是 16GB/s。

#8.1 Google Analytics

Google Analytics 用于帮助网站所有者分析流量模式。包括聚合统计,例如每天的 UV,每个 URL 的 PV,还有支付的用户比例。为了启用这个功能,网站所有者需要在网站上插入一个 JavaScript 代码片段。每次页面被访问的时候,这个代码片段就会执行。这个代码会记录各种各样的信息,例如用户 id 和页面的信息,发送到 Google Analytics。 Google Analytics 总结这些信息,然后提供给网站所有者。

Google Analytics 使用的 Bigtable 表包括:

  • raw click 表:~200TB,每个用户会话一行,行名字是网站名+Session 创建时间。这样保证了访问相同站点的用户会话会被存储在一起,而且是按照时间排序的。这个表的压缩率是 14%。
  • summary 表:~20TB,包含每个网站的各种聚合统计信息。这个表是被周期调度的 MapReduce 任务从 raw click 表中生成的。每个 MR 任务从 raw click 表中提取最近的会话数据。这个表的压缩率是 29%。

#8.2 Google Earth

Google 运营着一系列服务,提供地球表面高解析度的卫星图像。这个服务既通过基于 web 的 Google Maps 提供,也通过 Google Earth 软件客户端提供。

Google Earth 的预处理流水线使用了一个 Bigtable 表存储原始图像数据。在预处理过程中,图像被清理和合并,形成最终提供服务的图像。这个表包含了约 70TB 的数据,因此是存储在磁盘中的。这些图像已经被有效地压缩过了,因此 Bigtable 的压缩被关闭了。

图像表的每一行对应一个地理区域,行名字保证了相邻区域的行是相邻的。这个表包含了一个 CF,用于追踪每个区域的数据来源。这个 CF 有很多列:基本上每个原始图像都有一个列。因为每个区域的图像是从很少几张原始图像中合成的,这个 CF 非常稀疏。

预处理流水线重度依赖 MapReduce over Bigtable 来处理图像数据。

服务系统使用一个表来索引 GFS 中的数据。这个表相对较小,只有 ~500GB,但是要保证每个数据中心能低延迟地提供上万的 QPS。因此这个表跨越了数百个 Tablet Server,还开启了 in-memory 选项。

#8.3 个性化搜索

Personalized Search 是一个 Google 的实验性服务,记录用户的查询和点击行为,跨越 Google 的多种服务,例如搜索,图片和新闻。用户可以浏览搜索历史来找到他们之前搜索和点击过的内容,然后他们可以寻求个性化的搜索结果,根据他们的历史行为。

个性化搜索把每个用户的数据保存在 Bigtable 中。每个用户有一个唯一的 userid,和一个对应的行。所有用户的行为都会被记录在这个行中。每种行为都有一个单独的 CF 保存(例如有一个 CF 保存搜索历史)。数据元素是行为发生的时间戳。个性化搜索用 MapReduce over Bigtable 来生成用户的资料。

个性化搜索数据在多个 Bigtable 集群之间复制,以增加可用性和减少延迟。个性化搜索团队原本在 Bigtable 之上实现了一个自己的复制系统,但是后来改成了使用 Bigtable 的复制功能。

个性化搜索的存储系统允许其他组在他们的列中添加每个用户的信息,然后这个系统也被很多其他的,需要为每个用户保存数据的 Google 业务使用。在很多组之间共享一个表导致了不寻常的 CF 数量。为了解决这个问题,Bigtable 增加了一个简单的 Quota 机制来限制单个客户端在共享表中的空间消耗,提供了一定的隔离性。

#FAQ

#1. GFS 可能出现重复记录或者 padding,Bigtable 如何处理这种情况?

Bigtable 写入 GFS 的数据分为两种:

  1. 操作日志,当 Tablet Server 发生故障时,它上面服务的 Tablet 会被迁移到集群中的其他 Tablet Server 上继续提供服务,加载 Tablet 可能需要回放操作日志,每条操作日志唯一的序号,通过它可以去除重复的操作日志。
  2. 每个 Tablet 包含的 SSTable 数据,如果写入 GFS 失败可以重试并产生多条重复记录,但是 Bigtable 只会索引最后一条写入成功的记录。

#2. 如何保证同一个 Tablet 不会被多台机器同时服务?

利用 Chubby 保证互斥性。