本周早些时候,Facebook 在他们的项目博客上发布了文章《 Facebook 可伸缩 Mercurial 》,介绍如何修改 Mercurial 来扩展版本库。
Facebook 在单个版本库中存储了所有代码,两年前存放在 subversion 上。这样就不用为每个项目区分版本库(共享二进制版本库),整个代码库存储在单个大型源代码版本库上。
对于 Facebook 不幸的是,git 和 mercurial 的设计都不支持存储所有工程的单个大版本库。由于分布式版本控制系统的目的是存储完整历史,因此,如果公司项目都存储在一个版本库内,那么包含历史的版本库可能大的离谱。相比之下,像 CVS 和 Subversion 等集中版本控制系统都能够处理多个项目,使其共享一个版本库,这是因为客户端可以只签出最新版本或者或版本库的子集。
尽管 Facebook 团队研究修改 Git 来满足需求,但结论是 Mercurial 有更多方便的编程 API,这些 API 可用于挂钩,以支持他们的需求。(Git 有定义良好的对象结构,工具可解析,而 Mercurial 具有代码库可用的低层 API)。他们已经把许多优化反馈给了上游的 Mercurial 项目,包括新的图形算法和用 C 重写的代码。
两项具体修改使 Facebook 能用 Mercurial 来处理他们的版本库大小:修改文件的状态更新来检查指定文件的变化,而不是内容的变化(钩入文件变更的操作系统列表),修改签出的文件,以获得一个无需完整历史状态的轻量级副本。
一般情况下,分布式版本控制系统将依据数据的内容而不是时间戳进行哈希。因此,想要判断版本库是否有变化,往往需要扫描每一个文件的计算哈希值来判断文件内容是否有差异。通过限制文件集来检查操作系统报告的上次扫描发生变化的文件,速度与时间戳变化的文件大小成正比,而不是检查当前工作区中的所有文件。Git 试图通过运行 LSTAT 确定具体的文件信息以减少消耗,但仍需扫描资源库所有文件,以确定它们是否发生变化。通过让操作系统提供上述信息,版本库可以优化为只扫描 OS 报告中发生变化的文件。
Facebook 试图解决的另一问题是,最小化下载或克隆操作所需的数据量。将所有项目存储在同一个版本库,版本库的大小是与最后产生伸缩性问题的整个历史数据成正比。因为库中没有进行有效分区,解决方案只能是下载最新版本的文件,其中含有提交日志。
这让开发者能够快速获取当前文件集(与 subversion 和 CVS 处理方式大致相同),通过提交日志的迭代遍历,实现这点。然而,如果需要版本库的旧版本(或文件旧版本),本地副本就不含有这样的信息。(Git 提供了“浅克隆”的限制选项,这可以从库中得到单一版本,但没有提交历史。)
通过扩展 Mercurial API,遗失对象的提交可能“出错”,请求时从远程服务器下载内容,而直到需要时才下载它们。当然,这意味着,如果中心服务器出现故障或不可用,那么开发者将不能签出老版本的代码;由于提交日志可用,所以新提交的内容可以创建并推送到服务器。(相比之下,Git 影子克隆具有相同的功能,但有差别的提交,这意味着它们只能用于只读目的。)
这些改进,再加上 memcached 层,加快了 Facebook 对 Mercurial 的使用,拉 / 克隆操作和工作目录状态都跑赢了 Git 5 倍。在 remotefilelog 和 hgwatchman 的 Facebook Mercurial 库,两者都是可访问的。尽管构建和方式并不适合每个 Mercurial 用户,但是在日益占主导地位的 Git 开源世界里,它推动了 DVCS(分布式版本控制系统的英文简称)的发展。
感谢侯伯薇对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。
评论