《Bigtable: A Distributed Storage System for Structured Data》是 Google 在 2006 年发表的奠基性论文。它与 GFSGoogle File System、MapReduce 并称为Google 的“三驾马车”。Bigtable是什么Bigtable 是一个分布式结构化数据存储系统集群规模从几台到上千台服务器不等可存储PB 级海量数据的超大规模扩展而设计的。具有高扩展性PB级、高性能、高可用与广泛适用性。在 Google 内部撑了包括 Google Analytics、Google Earth、Google Finance 在内的 60 多个核心项目。Bigtable能够完美驾驭两种截然不同的极端工作负载一类是追求高吞吐量的后端批处理任务另一类是满足终端用户、对延迟极其敏感的实时数据服务。与传统数据库的区别不支持完整的关系型数据模型它提供了一个简单的数据模型支持对数据布局列式存储和格式Bigtable不解析数据格式由客户端自己处理进行动态控制。数据通过任意字符串类型的行名和列名进行索引统一视作未解析的二进制字节流。允许客户端通过参数动态控制数据是从内存还是磁盘读取。Bigtable的数据模型Bigtable 存储的数据结构是一个稀疏的、分布式的、持久化的、多维的、排序的映射表。其索引结构可表示为(row:string,column:string,time:int64)→string(row:string, column:string, time:int64) → string(row:string,column:string,time:int64)→string。上图是webpage的一个数据示例URL 作为行键webpage的多个属性作为列键contents:列族用来存网页 HTML 源码anchor:列族用来存指向该网页的链接文本每个链接文本只存储一个时间戳。上面的例子中包含两个链接列anchor:cnnsi.com和anchor:my.look.ca共同组成一个列族。同一网页在不同时间被抓取的内容通过不同的时间戳t3,t5,t6区分共存实现了历史版本的保留。Rows单行性质row key类型行键是任意字符串最大支持64KB但在实际工业生产中典型大小通常在10~100 字节之间。row的原子性对同一行键下的所有数据执行读或写操作都是具有原子性的无论这一行包含了多少个不同的列。tablettablet是什么Bigtable 的数据在存储时是严格按照行键的字典序排列的。一整张表会按照行键被动态切分成多个连续的子区间每一个子区间被称为一个Tablet。tablet是数据分布与负载均衡的基本单元。这样设计的好处短范围内的顺序扫描Range Scan非常高效通常只需要与极少数的几台机器通信。在读取时可以根据不同tablet的访问频率做不同的部署或者切割。经典案例Webtable 的反转 URL将http://maps.google.com/index.html网址按照com.google.maps/index.html存储那么同一域名下的页面集中存储在靠近的位置上当你需要对某一个网站做批量扫描和局部域名分析时磁盘 I/O 和网络开销将直接降低几个数量级。列族Column Families列族是列键的集合。必须先创建列族才能将数据存储在该列族中的任意列键下。同一列族下的所有数据通常属于同一种数据类型同一列族中的数据一起进行压缩。一个表中列族的数量非常有限最多几百个且在系统运行期间极少发生变更。列族内部的具体的列键是动态、无上限的。列键的命名严格遵循列族 : 修饰符family:qualifier。列族名必须是可打印的字符串但是修饰符可以是任意字符串。访问控制以及磁盘与内存计费均在列族级别进行。时间戳Bigtable 的每一个单元格都可以包含同一份数据的多个历史版本这些版本通过一个64 位整数的时间戳来进行索引和区分。时间戳的生成方式系统自动分配代表数据写入时的时间精确到微秒。用户显式指定由客户端应用自己生成。如果应用需要绝对避免冲突必须自己保证时间戳的唯一性。同一个单元格的不同版本是按照时间戳降序排列的。为了更快的读取最新的数据。历史版本的回收在列族级别支持两种自动淘汰回收策略基于版本数量只保留最近的nnn个版本老版本自动清掉。基于生存时间只保留足够“新鲜”的数据例如只保留最近 7 天内写入的值过期的自动物理删除。Bigtable的API设计Bigtable API 提供了创建和删除表格及列族的功能同时支持修改集群、表格及列族的元数据如访问控制权限。读写操作// Open the tableTable*TOpenOrDie(/bigtable/web/webtable);// Write a new anchor and delete an old anchorRowMutationr1(T,com.cnn.www);r1.Set(anchor:www.c-span.org,CNN);r1.Delete(anchor:www.abc.com);Operation op;Apply(op,r1);RowMutation用于创建批量更新Apply原子性的执行RowMutation中的一个或多个更新动作。迭代遍历Scannerscanner(T);ScanStream*stream;streamscanner.FetchColumnFamily(anchor);// 只迭代anchor列族stream-SetReturnAllVersions();// 返回全部versionscanner.Lookup(com.cnn.www);// 过滤列名for(;!stream-Done();stream-Next()){printf(%s %s %lld %s\n,scanner.RowName(),stream-ColumnName(),stream-MicroTimestamp(),stream-Value());}在数据迭代遍历时客户端可以利用多种机制来过滤返回的行、列和时间戳。例如支持通过正则表达式匹配列名或指定具体的时间戳范围。其他API单行事务对存储在单一行键下的数据实现原子性“读-改-写”序列但暂不支持跨行事务。支持将单元格用作整数计数器。支持用户自定义脚本脚本采用谷歌专为数据处理开发的一种名为Sawzall的语言编写。不支持脚本向Bittable写入数据主要用于数据转换、任意表达式过滤和聚合汇总。与MapReduce集成Bigtable 提供了适配封装使其能够无缝对接 Google 的 MapReduce 分布式计算框架既可以作为 MapReduce 作业的输入数据源也可以作为其输出的目标存储。Bigtable的核心支撑组件Bigtable 由谷歌的多项基础设施构建而成。使用GFSGoogle File System来持久化存储其日志和数据文件。Bigtable 通常部署在共享的机器资源池中与其他分布式应用共同运行。依赖于集群管理系统来调度任务、管理共享机器上的资源、处理机器故障以及监控机器状态。存储格式Bigtable 内部使用SSTable文件格式存储数据。它是一个持久化、有序且不可变的 Key-Value 映射表其中的keyvalue都是任意的字节串。SSTable提供精准查询单个key对应的value和遍历指定键范围内所有键值对的操作。SSTable 内部由多个数据块Block默认 64KB组成文件末尾存有块索引Block Index。SSTable 打开时索引会被加载到内存中。查询时先在内存中对索引进行二分查找定位到具体的块然后只需单次磁盘寻道读取该块即可。也可以把SSTable的数据都加载到内存中避免读取磁盘。分布式锁服务ChubbyChubby 是一个高可用、持久化的分布式锁服务基于Paxos 算法保证 5 个副本之间的一致性多数派存活即可用。它提供类似文件系统的命名空间文件和目录均可作为锁并通过租约机制管理客户端会话支持变更回调通知。在 Bigtable 中的作用确保任何时候最多只有一个活跃的 Master选主。存储 Bigtable 数据的引导位置。处理 Tablet Server 的发现和宕机事件。存储表的元数据列族信息以及访问控制列表ACL。BIgtable强依赖ChubbyChubby如果长期不可用Bigtable 也会随之不可用。Bigtable的实现Bigtable 的实现由以下三个主要部分组成客户端库链接到每个应用程序中客户端读写数据直接与 Tablet Server 通信不经过 Master。客户端不需要通过 Master 来获取 Tablet 的路由位置信息Chubby获取meta本地缓存和错误重新获取meta。Master负责全局的管理与调度工作包括为 Tablet Server 分配 Tablet。检测 Tablet Server 的动态加入、宕机并进行负载均衡。负责 GFS 中无用文件的垃圾回收。处理表和列族的创建等元数据变更。Tablet Servers:动态可扩展的实际工作节点。每个 Tablet Server 管理一个 Tablet 集合通常为 10 到 1000 个直接处理针对这些 Tablet 的读写请求并在 Tablet 过大时进行自动切分。默认情况下每个 Tablet 的大小大约为100-200 MB。Tablet 路由信息Bigtable采用类型B树的三层索引结构来存储tablet的路由信息第一层Chubby 文件包含“根 Tablet”的位置。第二层根 Tablet属于METADATA表的第一个分片。它被特殊对待——永远不会被分裂以此保证整个寻址树最多只有三层。它记录了所有其他METADATATablet 的位置。第三层其他 METADATA Tablet记录具体用户表各个 Tablet 的位置。数据在METADATA表中以“表标识符 结束行键”编码作为行键进行存储。该三层结构能索引的最大存储空间为**2612^{61}261字节**限制单个tablet的大小限制为128MB。每一行元数据在内存中大约占用 1KB。这套三层方案足以支撑高达2342^{34}234个 Tablet2612^{61}261字节的kv存储。客户端如何寻址客户端寻址流程客户端在发起读写请求时先查找本地缓存。如果缓存中存在该 Row Key 对应的用户 Tablet 地址客户端会直接连接对应的 Tablet Server。客户端没有缓存时依次从Chubby、第一层Tablet、第二场tablet获取目标用户Tablet获取成功后会缓存在客户端。客户端缓存过期时Tablet Server会返回“位置失效”的错误客户端会先清除本地该段缓存然后递归向上寻找再向下寻找目标用户Tablet。客户端预取当客户端去读取某个 Tablet 的位置信息时它不会只读这一条而是顺便把相邻的多个 Tablet 的位置元数据一同读出来并塞进缓存。进一步优化客户端获取tablet定位信息的性能。METADATA中存储的其他信息除了位置路由信息METADATA表内还存储了其他的次要信息。例如记录每个 Tablet 相关的所有事件日志如某台服务器何时开始承载该 Tablet。这为系统的后期调试和性能分析提供了关键的数据支持。Tablet 负载分配同一时间每个 Tablet只能被指派给唯一一台Tablet Server进行管理。Tablet Server 状态监控每台 Tablet Server 启动时会在 Chubby 的特定目录下创建一个唯一文件并对其加排他锁Master会持续监控该特定目录。如果 Server 因网络分区等原因丢失了 Chubby 会话Session它将自动停止服务。如果发现自己的 Chubby 文件已被删除Server 会直接自杀。正常退出时Server 会主动释放锁以加速数据交接。Master 会定期询问各 Tablet Server 的锁状态。如果对方报告失锁或 Master 连续多次无法连通该 Tablet ServerMaster 就会尝试去获取该 Server 在 Chubby 上的排他锁。若 Master 成功抢到锁说明 Chubby 正常而该 Server 已死或无法连接 Chubby。Master 会直接删除该 Server 的 Chubby 文件确保其无法复活随后将该 Server 之前承载的所有 Tablet 移动到“未分配”集合中准备重新分配。如果 Master 自身与 Chubby 的会话过期为了防止网络分区引发管理混乱Master 也会选择自杀。Master 的启动流程Master 服务器在内存中实时维护着整个集群的全局状态视图包括哪些 Tablet Server 当前正存活、各个 Tablet 当前的分配归属以及当前有哪些 Tablet 处于未分配状态。Master也负载整个Tablet Server集群的负载均衡。Master的启动流程如下在Chubby中获取唯一的主锁防止多个实例同时运行。扫描Chubby中的服务器目录以识别处于运行状态的Tablet Server。与所有活跃的Tablet Server通信获取各服务器已分配的Tablet列表。扫描METADATA表获取全量 Tablet 列表。若发现有未被承载的 Tablet则加入未分配集合。Tablet的创建、删除、合并以及分裂表的创建、删除以及 Tablet 的合并都由 Master 亲自发起因此 Master 能直接记录控制这些变化。Tablet 分裂是由 Tablet Server 发起的。其具体的分裂流程如下当 Tablet Server 监测到某个 Tablet 大小触发阈值便启动分裂。Tablet Server 向METADATA表发起一个原子事务删除原先老 Tablet 的路由行并同时写入两条全新子 Tablet孩子 A 和孩子 B的位置与范围记录。成功提交METADATA后Tablet Server 会向 Master 发送一个分裂通知。Master 收到通知后抹去老 Tablet登记两个新子 Tablet并记录它们都在这台 Tablet Server 上。登记完成后Master 会评估全集群的负载。如果发现这台 Tablet Server 管理的片太多了Master 稍后可能会发起负载均衡将其中的一个子 Tablet 迁移给其他空闲的 Tablet Server。只要METADATA表修改成功这次分裂再系统上就正式成功提交了。如果METADATA表修改成功但是通知Master失败那么当Master要求Tablet Server加载已分割的Tablet时Tablet Server在METADATA表查到的范围只占了其中Master要求范围的一部分于是再次向 Master 汇报分裂事实。Tablet 运行流程Tablet的整体框架如下图所示Tablet 的持久化状态最终都存储在GFS中。Commit Log新的写入数据会首先写入到 Commit Log 中作为Redo Records进行持久化。Memtable最近提交的记录在commit后会暂存在内存中一个名为 Memtable 的有序缓冲区内。SSTables较老的历史记录则以一系列有序文件的形式持久化存储在 GFS 的SSTable中。Tablet 的崩溃恢复机制Tablet Server 被指派加载并恢复一个 Tablet 时它的执行流程如下从METADATA表中读取该 Tablet 的元数据。这些元数据包含构成该 Tablet 的SSTable 列表以及一组指向 Commit Log 的Redo Points。将所有相关 SSTable 的索引读入内存。从Redo Points开始读取并重放之后所有已提交的记录在内存中重新构建出完整的 Memtable。写入流程检查请求格式并读取 Chubby 文件验证用户权限。将有效的变更写入 Commit Log。为了提升大量小吞吐变更的性能采用批量提交。成功写入日志后将数据正式插入到内存的 Memtable 中读取流程进行格式与权限校验读操作会同时读取Memtable和SSTable将合并结果返回。Tablet 的分裂和合并期间读写流程可以继续执行不会中断。CompactionsMinor Compaction随着数据持续写入内存中memtable的体积不断增大。当达到设定的阈值时系统会触发 Minor CompactionMinor Compaction的目的释放并缩小 Tablet Server 的内存占用。减少该服务器宕机后从commit log中读取和重放的数据量从而加速Recovery。Minor Compaction的执行流程冻结当前Memtable同时创建一个新的memtable接收新写入将冻结的memtable转换成一个全新的SSTable文件并持久化写入 GFSmerging compaction每次 Minor Compaction 都会在 GFS 上生成一个新的SSTable。如果任其发展读操作就需要同时合并归并无数个文件导致读取性能急剧恶化。Merging Compaction的执行流程在后台定期触发执行 Merging Compaction将Memetable和选取部分SSTable进行合并生成一个新的SSTable合并完成后作为输入的Memtable和SSTable就可以删除释放了Major Compaction当 Merging Compaction 将所有的SSTable彻底重写合并为有且仅有一个 SSTable 时这个过程被称为Major Compaction。非Major Compaction对于删除的数据不会真实删除而是保存的数据和删除标记而Major Compaction会将已删除的数据和删除标记都去掉。Major Compaction的触发时机Bigtable 会定期轮流对所有 Tablet 执行 Major Compaction这不仅能让系统真正回收被删除数据占用的物理资源还能确保敏感数据及时且彻底地删除。Bitable的优化Locality Groups局部性组客户端可以将多个列族组合成一个局部性组。在每个 Tablet 内部系统会为每个局部性组生成一套独立的 SSTable 文件。达到的效果将通常不会被一起访问的列族隔离到不同的局部性组中从而实现更低 I/O 消耗的高效读取。应用案例Webtable网页的元数据如语言、校验和可以放在一个局部性组中而网页的具体内容通常体积庞大放在另一个组。当应用程序只需要读取元数据时完全不需要去读取和扫描庞大的网页内容文件。对局部性组设置调优参数。比如内存驻留参数对局部性组设置内存驻留参数后属于局部性组的 SSTable 会被延迟加载到 Tablet Server 的内存中。一旦加载完成对该组内列族的读取就可以完全不访问磁盘直接在内存中读取。压缩客户端可以自由控制是否对某个局部性组的 SSTable 进行压缩并能指定具体的压缩格式与分块大小。压缩算法是应用在 SSTable 的每一个独立数据块上的。虽然对每个块进行单独压缩会损失一部分全局压缩率但带来的巨大好处是当客户端只想读取 SSTable 的一小部分数据时系统只需要解压对应的那个小数据块而不需要解压整个SSTable。压缩算法双阶段压缩方案第一阶段采用 Bentley-McIlroy 方案在一个很大的窗口内检索并压缩超长的共同字符串。第二阶段采用一种极快的压缩算法在一个较小的 16 KB 窗口内寻找局部的重复数据。该方案的编码速度达到 100–200 MB/s解码速度为 400–1000 MB/s。压缩效果在Webtable的数据模型下采用这种压缩方案来存储网页内容网页内容只保存一个版本的数据。该压缩算法达到了10:1 的空间缩减。如此高的缩减率得益于Webtable 的行键设计得非常聪明使得来自同一个域名的所有网页都在物理上紧挨着存放在一起使得算法能够轻松揪出同一网站下大量重复的网页模板。当用户在 Bigtable 中为同一个 Key 存储多个历史版本时新旧版本之间的高度相似性会让这种压缩比会进一步提升。读缓存扫描缓存属于更高层的缓存。它直接缓存由 SSTable 接口返回给 Tablet Server 上层业务代码的具体键值对结果。适用场景适合那些需要反复、频繁读取完全相同的数据项的应用程序。块缓存属于更底层的缓存。它缓存的是直接从 GFS 分布式文件系统中读取出来的 SSTable 原始数据块。适用场景最适合那些读取与最近刚读过的数据在物理位置上相邻的应用程序。比如顺序扫描、热点行多列读取。布隆过滤器在 Bigtable 中执行一次读取操作系统必须同时扫描并合并构成该 Tablet 状态的所有 SSTable 文件这带来了很大的读放大。为了降低磁盘访问次数Bigtable 允许客户端指定为特定局部性组的 SSTable 创建布隆过滤器利用布隆过滤器来过滤不存在的key-value对消耗小部分的内存来大量减少对磁盘的访问。Commit-log问题如果为每个 Tablet 独立写日志会导致 GFS 上并发写入的文件数暴增。受限于底层磁盘物理特性这会引发极其严重的磁盘寻道延迟并且批量提交的合并效果也会大打折扣。解决办法每个 Tablet Server 上的所有 Tablet 共用同一个 Commit Log 文件。多个Tablet共享一个log文件又对Recover的读造成了问题当一台 Tablet Server 挂掉后它原本托管的成百上千个 Tablet 会被分发给集群中的几十上百台新服务器。如果每个新服务器为了恢复自己分到的那几个 Tablet都去全量读取一次这个巨大的、交错的共享日志那么这个日志文件会被重复读取上百次引发严重的 I/O 放大。解决办法Master 会启动分布式排序逻辑将原日志文件拆分为 64 MB 的分片分发给多台临时的 Tablet Server 并行排序。排序规则为日志按照〈table, row name, log sequence number〉排序。排序完成后属于同一个 Tablet 的日志在物理上变得完全连续。问题底层的 GFS 偶尔会因为节点宕机、网络拥堵或高负载出现 I/O 延迟毛刺这会拖慢Commit-log的写入进而影响客户端的IO写入。解决办法每个 Tablet Server 内部同时运行着两个日志写入线程各自对应一个独立的物理日志文件但同一时间只有一个线程处于激活状态。一旦监测到当前激活的日志线程写入性能恶化系统会瞬间将写入队列切到另一个备用线程由新激活的线程继续高速写入从而完美屏蔽了底层 GFS 的瞬时抖动。回放日志的时候根据日志的序列号进行去重线程切换可能带来重复数据。加快tablet的恢复速度Bigtable为例在tablet迁移的时候加快目标节点加载tablet的时间进行如下处理当 Master 决定将一个 Tablet 从源服务器迁移走时源服务器首先会对该 Tablet 执行第一次 Minor Compaction将Memtable持久化到GFS上。第一次Minor Compaction后源服务器会立刻停止对该 Tablet 的一切读写服务。在真正卸载该 Tablet 之前它会紧接着触发第二次 Minor Compaction。这次Minor Compaction的速度很快因为Memtable中自由第一次合并期间写入的数据。在第二次Minor Compaction该Tablet无法提供服务目标服务器接管并加载这个 Tablet 时完全不需要读取和重放任何日志条目因为所有的数据都已经在SSTable中了目标服务器只需要加载METADATA就可以对外提供服务了。不可变性带来的好处由于 SSTable 文件一旦生成就不可变因此在并发读取同一个 SSTable 时不需要进行任何文件系统级别的同步或加锁操作。整个系统中唯一同时承载读写的只有 Memtable。为了减少读取时的锁竞争Bigtable 将 Memtable 的每一行都设计为写时复制从而实现了真正的读写完全并行。在Bigtable中删除数据转化为了清理废弃 SSTable 文件的垃圾回收问题。Master 节点会定期执行标记-清除垃圾回收它以METADATA表中的根集合为起点遍历并标记所有还在被引用的 SSTable最后将那些没有被标记的、因合并而过期的废弃 SSTable 文件从 GFS 中删除。Tablet 分裂时子 Tablet 完全不需要生成任何新的 SSTable 文件而是直接共享父 Tablet 的旧 SSTable 文件。实际的数据搬移发生在Compaction阶段。性能评估实验环境包含 1786 台机器的 GFS 集群每台机器配置 2 个双核处理器、2×400 GB 硬盘、1 Gbps 网卡。机器之间通过两层树状交换网络连接根节点总带宽达 100-200 Gbps延迟小于 1ms。GFS、Master、Tablet Server 和测试客户端全量混部在同一批机器上。客户端数量与 Tablet Server 数量NNN严格保持 1:1以确保客户端不会成为性能瓶颈。测试场景测试统一读写 1000 字节1 KB大小且完全不可压缩的随机字符串每个 Tablet Server 平均分摊约 1 GB 的测试数据量涵盖了以下六种测试场景测试项核心机制与设计优化顺序写将 Key 空间划分为10N10N10N个分片通过中央调度器动态分配给客户端。谁写完就领下一个以此屏蔽由于机器混部导致的性能抖动。随机写写入前对 Key 进行 Hash 取模使写流量在整个测试期间均匀、随机地散落在整个 Row 空间。顺序读严格按照顺序写生成的 Key 序列进行回读。随机读类似随机写采用 Hash Key 进行全盘随机读取。范围扫描类似于顺序读但使用了 Bigtable API 的 Scan 批量流式接口。由于单次 RPC 就能拉取大批 K-V 序列大幅减少了网络 RPC 的交互次数。内存随机读数据所在的局部性组被显式声明为常驻内存。读取完全由 Tablet Server 内存响应不触发任何 GFS 磁盘 I/O。为了塞进 1GB 的服务器内存此项测试将单个 Server 的数据量缩减到了 100 MB。测试结果及分析单Tablet Server随机读性能分析随机读的性能比其他所有操作都慢了一个数量级以上单机只能跑大约 1200 ops/s。原因分析客户端虽然只想要一个 1000 字节1 KB的值但系统为了读到它必须从远程 GFS 跨网络传输一整个64 KB 的 SSTable 数据块这带来极大的读放大。1200 ops/s 意味着服务器要从 GFS 每秒读取 75 MB 的数据。这股数据流所带来的网络协议栈开销、SSTable 解析以及 Bigtable 业务逻辑开销已经达到了CPU的瓶颈同时也几乎吃满了单机千兆网卡的带宽上限。优化办法 如果遇到此类随机读密集的应用通常会把 SSTable 的数据块大小调小到 8 KB从而减轻这种放大效应。内存随机读一旦数据常驻内存性能就会极快。因为每一次 1000 字节的读取都可以直接由本地内存提供响应彻底免去了从 GFS 抓取 64 KB 远程大文件的恐怖开销。随机写与顺序写随机写与顺序写的性能都要优于随机读。因为无论是随机写还是顺序写在 Bigtable 底层并没有本质区别。Tablet Server 都会将所有写请求顺序追加到同一个 Commit Log 文件中。顺序读顺序读性能优于随机读。顺序读之所以比随机读快得益于 Block Cache。从 GFS 拉过来的每一个 64 KB 数据块都会缓存在内存中。因为是顺序读取这一个块直接就能连续满足接下来的64次读取请求。范围扫描描是所有读取里最快的。因为 Bigtable API 支持批量扫描Tablet Server 可以在单次客户端 RPC 响应中直接打包返回大量的数据。从而减少网络RPC的开销。集群性能分析当系统中的 Tablet 服务器数量从 1 台增加到 500 台扩大 500 倍时集群的聚合总吞吐量实现了超过 100 倍。其中内存随机读的总性能提升了近 300 倍。这证明了该测试的性能瓶颈完全在单台服务器的 CPU 上因此增加服务器能大幅提升其性能。当服务器数量从 1 台增加到 50 台时单机平均吞吐量出现了显著的下降。其原因分析如下多服务器下的负载不均衡在多机配置下其他共存的混部进程会激烈争抢 CPU 和网络带宽。尽管 Bigtable 有负载均衡算法但由于以下两个现实工程原因它无法做到完美为了避免频繁迁移 Tablet 导致短暂的停服时间搬迁时 Tablet 通常会不可用不到 1 秒系统限制了重新均衡的频率。基准测试产生的写入/读取负载在测试过程中在不断变化算法很难实时跟上。即使服务器增加了 500 倍普通随机读的总吞吐量仅仅提升了 100 倍。时测试场景里面最低的原因分析如下随机读的读放大带来了极高的网络开销占满了网络带宽导致单机的平均吞吐量随着机器规模的扩大而急剧下滑。经验总结真实世界的分布式系统故障远比理论复杂在实际生产中Bigtable 遭遇了各种光怪陆离的真实故障包括内存与网络数据损坏、巨大的时钟漂移、机器卡死、非对称网络分区、底层系统如 Chubby的 Bug、GFS 配额溢出以及计划内外的主机维护。应对手段在 RPC 机制中强制引入了校验和以对抗数据损坏。消除模块之间的盲目信任。例如系统不再假设 Chubby 某项操作只会返回几种固定的标准错误代码。实施恰当的系统级监控至关重要扩展了 RPC 系统能够详细记录选定RPC的所有关键操作。这写监控帮助定位并修复了很多问题比如Tablet 数据结构的锁竞争、写 Commit Log 刷 GFS 时的速度变慢以及元数据表不可用时导致的请求卡死等深层次问题。简洁明了的设计保持设计和代码的清晰度对于后期的维护和Debug具有极大的帮助。