Arc是用于单一存储库的版本控制系统。 Yandex报告

版本控制系统长期以来一直是开发人员的日常工具。 在大型单一存储库中,对它们的要求非常具体。 因此,公司要么像Facebook在Mercurial上和Microsoft在Git上那样调整现有解决方案,要么开发自己的系统:Google上的Piper和CitC以及Yandex上的Arc VCS。

在报告中,开发人员Vladimir Kikhtenko kikht讲述了Yandex为什么需要其自己的版本控制系统以及其工作方式。 从普通开发人员的角度考虑:如何访问源代码,预留分支进行开发,以及将更改集成到通用代码库中。 我们深入了解-我们了解数据的内部表示形式及其在具有工作副本的虚拟文件系统中的显示方式。 我们将讨论在虚拟文件系统中实现VCS功能以及延迟加载数据时遇到的困难。 让我们谈谈如何确保存储库的服务器基础结构的可靠性。 最后,您可以看到该报告的非正式记录。

-大家下午好,我叫弗拉基米尔。 你们都听到过关于不写自行车的演讲。 我的报告将在路障的另一侧。

实际上,Yandex有一个包含大量代码的单一存储库。 我们得出的结论是,我们正在开发自己的版本控制系统。



我们如何过上这样的生活? 从历史上看,这个单一存储库与我们一起生活在SVN中。 它实践基于主干的开发。 没有分支,只有很少的例外。 所有代码必须首先进入主干,然后变得完整。

随着存储库的增长,使用它的唯一可能方法是选择性结帐,因为SVN支持它。 向您自己上传整个存储库并不是完全不可能的,但是使用它非常困难。



我们问题的规模是多少? 以下是一些数字:600万次提交,几乎200万个个人文件。 整个存储库历史记录的总大小为2 TB。 为了弄清楚这些数字与其他典型存储库的含义,以下为图表。 GitHub中位数是GitHub上存储库的中位数,1 MB。 GitHub上的第90个百分位数是我的同事所说的“母亲的女友的儿子的资料库”。 其他都是著名的大型存储库。



据我所知,世界上最大的存储库位于Google。 根据2015年的一篇文章对它的大小进行了估算-大概从那时起它们就开始增长。 如您所见,标度是对数的。 可以看出我们也很大。

尝试下载整个存储库时,不同的版本控制系统如何工作? 自然,我们没有立即开始开发版本控制系统。 我们试图将我们的存储库转换为不同的系统。 最严重的尝试是Mercurial。 而且典型操作时间的结果仍然不适合我们。



不幸的是,在准备报告期间,git-svn无法转换我们的整个存储库。 转换了少量提交的一部分,因此我无法估计与历史记录相关的操作数量。 在一个细分市场中,它们速度很快,但对于600万次提交的效果如何尚不十分清楚。

最后是我们的版本控制系统的编号。 您可以立即获得一份工作副本。 在第一次启动时,日志操作会稍慢一些;在第二次启动时,一切都会快速进行。

和最后一位数字。 由于我们的版本控制系统会延迟加载所有数据,因此磁盘上只会存储我们真正使用过的源代码。 这远比下载整个下载要少。



我们是如何实现的? 主要功能:我们创建的工作副本不是磁盘上的真实文件。 这是一个虚拟文件系统。 在Linux和Mac上,这是通过保险丝完成的,而在Windows中则是通过ProjFS完成的。 我们会延迟加载所有数据,因此会根据实际需要使用磁盘空间,因此我们不会尝试提前加载所有内容。 并且我们对服务器执行各种繁重的操作。 特别是-日志的操作等等。



我们的版本控制系统的界面大体上重复了Git,因此我不会显示典型的工作流程。 想象一下Git。 一切都是一样的:以相同的方式支持签出以获取所需的修订版本,分支以创建分支,提交以提交,存储。 这种方法能带来什么? 我们大大降低了进入门槛。 Yandex内部和外部的大多数开发人员都可以使用Git。 他们不必学习任何新知识。

另一方面,我们没有放弃替代Git的目标。 我将在稍后详细讨论。 支持各种各样的git团队似乎很疯狂,我们几乎不需要它们。



我会告诉您一些内幕,以及一切的运作方式。 让我们从数据模型开始。 我们的数据模型与地理模型非常相似,但有一些差异。 同样,我们在内部创建的所有对象都是不可变的,它们通过其内容的哈希值进行寻址,并且在内部存储在平面缓冲区中。



结构是什么样的? 有提交对象,每个提交都有一个单独的或几个祖先。 并以此方式构建一些DAG(有向无环图)故事。



我们拥有的和没有立即出现在Git中的是世代号。 使用简单的算法,我们考虑到树根的一定距离。 我们为什么需要这个? 一旦固定,这些都缝在对象的结构中,再也不会改变。

对于版本控制系统,一个相当重要的操作是为两次提交找到最小的公共祖先。 在基本版本中,它可以简单地通过从大约两点开始的宽度变化来实现,用一个或另一个符号标记到达那里的所有提交,一旦他们找到同时具有这两个符号的提交,则是最不常见的祖先。

天真的实现将如何工作? 像这样:四处寻找所需的提交。



问题出在B上,这是多余的。 似乎我们不能研究它,但是我们看着它。 通过示例,我们在分支和主干之间的区别越多,我们发现的额外提交也就越多。 在单一存储库的情况下,当对主干的提交率足够高时,此距离可能会很大。 并且将有成千上万的此类额外提交。





在有世代号的情况下,我们可以在进行爬网时使用优先级队列,并且爬网将如下所示:一次-立即找到所需的内容。



这是我们模型之间差异的一个例子。 在Git中,以前支持此功能,他们使用世代号时间戳,但这仅在创建提交的时间与提交图一致的情况下才有效。



不幸的是,我们的存储库历史记录并非如此。 有些提交是由于另一个存储库的迁移而导致的,时间开始倒退。 在Git中,此功能在某些时候受到支持,但是并不总是适用于此,因为在Git中,您可以在本地用另一个替换提交对象。 模型的抗扰性因此而受到影响,因此那些没有记录的世代号有时不适用于其中所写的内容,这是不正确的。 我们没有这样的问题。

此优化的另一个优点是它完全是本地的。 要使用这些数字,我们不需要完整的提交图。 通常,我们根本就没有它,因为我们懒洋洋地装载着它。 我们懒惰的负载越少,我们的生活就越好。

除了提交,该模型与Git非常相似。 每个提交都指向该树的某个对象,该树由记录组成,每个记录要么是另一棵树,所以目录层次结构显示在此处,或者是一个Blob(某个文件)。 再加上我们有一个BlobRef之类的东西,当文件很大时,我们将其分成几部分并呈现在一个特殊的对象中。 就是这样,就像在Git中一样。



我们在Git中不喜欢什么? 我们称此为复制信息。 如果文件是以某种提交方式复制的,则Git不会以任何方式保存此信息,然后在显示差异和状态时尝试通过试探法将其还原。 我们将此信息保存在图中。 记录可能具有指向另一个提交的复制信息链接,也指向该提交中存储库内部的路径,通过该链接,我们知道此文件已在此提交中复制。

也有重复数据删除功能,例如,此Blob仅存储一次。 但是重复数据删除将是完全相同的,因为文件的内容没有改变;它将通过哈希进行重复数据删除。

后端如何安排? 如果Git具有分布式版本控制系统,则不需要任何后端。 当GitHub出现故障时,我们会特别敏锐。 我们清楚地了解到,Git不需要后端。 我们的系统是客户端服务器,它将所有数据存储在服务器上,并且需要服务器可用性才能下载那些尚未在客户端上的对象。



我们存储在Yandex数据库中的所有数据。 这是一个非常酷的数据库,可为事务提供必要的可靠性级别。 它拥有我们需要的一切,而这件事使我们摆脱了许多问题。

由于这一点,后端本身完全是无状态的,整个状态都在数据库中,因此我们可以很容易地根据需要扩展后端。

对于与客户端(服务器间)的交互,我们使用gRPC,今天有关于它的详细报告。



我们的系统如何与SVN集成? SVN存储库继续存在。 而且,我们的版本控制系统还没有自给自足。 她在这部分如何工作? 最初,有一些Converter组件监视SVN存储库的状态,并将SVN提交转换为我们的版本控制系统Arc提交。

接下来,有一个客户端,该客户端挂载工作副本并转到服务器以获取数据。 开发人员提交内容时,会将其首先发送到Arc服务器,但是要使这些更改进入主分支即主分支,它们必须通过池请求系统和代码检查系统。 这是另一个监视Arc分支的服务,如果更新了分支,则将池请求发送到我们的系统代码审查。 接下来是代码审查系统,当确定需要合并此补丁时,将其提交到SVN。 并非很简单:它在其中添加了一定数量的元数据,该提交实际上是Arc这样的分支的合并。 然后,此提交已经可以看到转换器,可以在转换器中找到此元数据,并在Arc服务器中创建提交。 这是提交的周期。 因此,尽管我们不能没有SVN,但是因为我们在SVN中有主干。

主分支一直与我们的服务器保持同步,但是我们不允许直接对其进行提交。



关于后端的可靠性。 当然,我们计划所有Yandex开发人员都将使用此功能,因此对我们来说重要的是它不会损坏。 这是一个内部索引标准:我们的服务必须在任何数据中心的故障中生存。 版本控制系统也不例外。 在此,YDB支持这一事实,我们为此而大为节省。 而且我们的后端是无状态的,不同部分的实现方式略有不同。 在Arc对象上运行的服务器在分支上运行,它们是无状态的,可复制的。 从SVN不断转换的转换器根据双活方案进行复制。 有多个转换器同时工作,它们同时转换,并且在尝试更新Arc分支时,它们解决了冲突。 一个成功,另一个失败。 他正在尝试进一步改变一些东西。

池请求服务由主从复制。 有一个主要的工作。 如果失败,则通过YDB选择一个新的。 信号量是一件很棒的事情,它为访问性,可靠性提供了严格的保证。 对信号量的访问已完全序列化。 我们将信号灯用于池请求发现服务和选择领导者。

关于客户端的工作原理。 这是我们版本控制系统中最困难的部分,因为有一个虚拟文件系统。 实际上,我们被迫自己执行文件上的所有操作。 我将介绍一些基本操作,用手指粗略地描述一下在进行操作时内部发生的情况。



例如,我们打开了一个文件进行记录。 当我们打开文件进行写入时,我们找到了对象模型的对应blob。 如有必要,请从服务器上载一些内容。 如果我们在特殊存储区中物理地创建文件,则将发送到该文件的所有其他进一步请求。 因此,在提交本地化更改之前(在Git中称为未暂存),它们将进入临时存储。 我们称此类文件为实物。



如果我们打开文件进行读取,那么我们将无法实现任何东西,而只能直接从blob中提供数据。



这是将文件添加到索引的时刻。 此时,您需要查看我们是否实现了某些东西。 是否有已更改的文件。 如果是,请为其创建一个blob并将其保存在索引中。



下一个操作是电弧状态。 有趣的是,在这样的大小的常规版本控制系统中,速度很慢,因为它必须遍历整个文件树。 我们不必遍历整个文件树,因为所有对更改文件的请求都通过我们的保险丝驱动器进行,并且我们立即知道哪些文件值得检查更改。 我们检查我们成功写入索引的内容,并打印答案。



投入时间。 一切似乎都很清楚。 有一个索引,我们已经为这些对象创建了blob,创建了与该状态相对应的树对象,创建了新的提交对象,并将其写入对象存储。



接下来,我们将工作副本切换到新提交。 这是一个棘手的操作,可以使用checkout命令清楚地完成。 在这里您可能会认为我们所有的本地更改似乎都已实现,因此可以假定我们应该退还新提交中未实现的文件。 就是这样。 所有后续操作都简单地发送到另一棵树和Blob。



为什么这不起作用? 第一个版本是关于这个的。 问题出在各种棘手的操作中,例如电弧复位–软。 他们为我们切换了树状切换,但没有实现文件。 他们继续存在于神圣的地方。 我们还有未跟踪和忽略的文件,它们也需要以特殊方式进行处理。 在这个地方,我们收集了很多耙,最终得出的结论是,我们仍然需要在结帐期间获取一棵树(现在是一个工作副本),获取我们要切换到的提交树,获取索引,并对其进行整洁的整理等一下

但是,就算法的复杂性而言,我们在这里没有损失任何东西:所有这些局部更改树都与我们所做的更改成比例。 因此,我们不应该使用这些操作来遍历整个存储库,它们仍然可以很快地完成工作。

同时,我们正在做一些魔术,以便为文件赋予的时间戳或多或少是正确的。 如果我们只是将文件存储在文件系统中,它将对其进行监视,并且时间总是在不断前进。 在这里,我们自己必须以某种方式记住用户在什么时候看到的文件。 而且,如果他改用较早的提交,请不要​​再给他较早的时间。 因为组装系统和所有IDE都还没有做好准备,所以它们带走了很多东西。



在我们的版本控制系统中,支持基于主干的开发。 首先,我已经说过:所有更改都通过池请求和主干。 还有两点。 我们没有小组分支机构的支持。 在Arc中创建的分支绑定到特定用户,只有该用户才能在此处提交。 这使我们避免了长期存在的分支。 在SVN中,这不是特别的,因为创建分支很不方便。 在Arc中进行操作很方便,而且如果不受控制,我们担心我们的单一存储库的某些部分会离开它们的分支并在那里进行开发。 这与我们想要的模型相反。



其次,我们没有合并命令。 分支机构的所有合并均在我们的严格控制下进行。 我们现在正在为发行版开发分支,也可以在其中合并。 这也可能不是由某些用户团队执行,而是由服务器机制执行。



我们有什么计划? 20%的单一存储库开发人员已经在使用我们的版本控制系统。 我们已经从某种婴儿状态中脱颖而出,这是一个被严重使用的系统,根本不可能像这样把它扔掉。 最终目标是成为Yandex中的主要版本控制系统。 我们必须以某种方式说服其余80%的开发人员我们相当稳定,可靠和可用。 显然,为此,您需要修复所有错误并完成Git中的那些功能。

当然,从某些角度来看,我们计划变得自给自足,放弃转换器或将其部署在相反的方向,以便首先将所有更改移交给Arc,然后由最持久的程序员移至SVN。

现在,我们面临着巨大的挑战-在自动装配,CI和其他管道中集成版本控制系统。 挑战是人们精神不振,他们缓慢键入代码并缓慢提交。 他们下载代码的速度太慢了。 并且机器人被剥夺了这个缺点。

— , CI Arc, - . , . . , ++- , , . .

. « Git». : Git. , , .

. Git . , . - . , checkout reset, . , , . : Git. « , ». Git .

. Git, git begin-wave-stash?

:
— .

— , Git ? — , , , . , . Git . , . .

Source: https://habr.com/ru/post/zh-CN482926/


All Articles