Git与大文件
情况
我有两个服务器,生产和发展。 在生产服务器上,有两个应用程序和多个(6)数据库(MySQL),我需要分发给开发人员进行testing。 所有源代码都存储在开发服务器的GitLab中 ,开发人员只能使用此服务器,并且无法访问生产服务器。 当我们发布一个应用程序,主要login到生产,并从Git拉新版本。 数据库很大(每个数据超过500M),我需要尽可能地将它们分发给开发人员进行testing。
可能的解决scheme
-
在将数据库转储到单个文件的备份脚本之后,执行一个将每个数据库推送到其自己的分支的脚本。如果开发者想更新他的本地副本,开发人员就会select其中一个分支。这一个被发现不工作。
-
生产服务器上的Cron每天保存二进制日志,并将它们推送到该数据库的分支中。 所以,在分支中,每天都会有文件被修改,开发者会拖拽他没有的文件。 当前的SQL转储将以另一种方式发送给开发人员。 当存储库的大小变得过大时,我们会将全部转储发送给开发人员,并清除存储库中的所有数据,并从头开始。
问题
- 解决scheme可能吗?
- 如果git是推送/从仓库中取出,是否上传/下载整个文件,或只是改变它们(即增加新的行或编辑当前的)?
-
Git可以pipe理这么大的文件吗?没有。 -
如何设置存储库中保存了多less个修订版本?与新解决scheme无关。 - 有没有更好的解决scheme? 我不想强迫开发者通过FTP或类似的东西下载这样的大文件。
rsync可能是一个很好的select,有效地更新数据库的开发人员副本。
它使用增量algorithm来递增更新文件。 这样它只会传输已更改或新的文件的块。 他们当然仍然需要先下载完整的文件,但后来的更新会更快。
基本上,你得到一个类似的增量更新作为一个混帐抓取没有不断扩大的初始副本,git克隆会给。 损失没有历史,但听起来像你不需要。
rsync是大多数Linux发行版的标准部分,如果你需要它在Windows上有一个打包的端口可用: http : //itefix.no/cwrsync/
要将数据库推送给开发人员,可以使用类似于以下的命令:
rsync -avz path/to/database(s) HOST:/folder
或者开发人员可以通过以下方式提取他们需要的数据库:
rsync -avz DATABASE_HOST:/path/to/database(s) path/where/developer/wants/it
2017年更新:
微软正在为微软/ GVFS做出贡献:Git虚拟文件系统允许Git处理“ 全球最大的回购 ”
(即:Windows代码库,这是大约3.5M的文件,并检查到一个Git仓库回购,导致约300GB的回购,除了成千上万的拉请求,除了成千上万的拉请求外,还在440个分支上每天产生1760个“实验室版本”validation构build)
GVFS将git仓库下的文件系统虚拟化,这样git和所有的工具都可以看到什么是正常的仓库,但是GVFS只是在需要的时候下载对象。
GVFS的某些部分可能是上游(Git本身)贡献的。
但与此同时, 所有新的Windows开发现在(2017年8月)在Git上 。
2015年4月更新:GitHubbuild议: 宣布Git大型文件存储(LFS)
使用git-lfs (请参阅git-lfs.github.com )和支持它的服务器: lfs-test-server ,可以将元数据仅存储在git repo中,而将大型文件存储在别处。
请参阅git-lfs / wiki /教程 :
git lfs track '*.bin' git add .gitattributes "*.bin" git commit -m "Track .bin files"
原始答案:
关于大文件的git限制 ,你可以考虑bup (在GitMinutes#24中详细介绍)
bup的devise突出了三个限制git回购的问题:
- 巨大的文件 ( Packer的xdelta只在内存中,这对于大文件来说是不好的)
- 数量庞大的文件 ,这意味着,每blob一个文件,并慢
git gc
生成一个packfile。 - 巨大的packfiles ,packfile索引效率低下,从(巨大的)packfile中检索数据。
处理大文件和xdelta
git无法处理大文件的主要原因是它通过
xdelta
来运行它们 ,这通常意味着它会尝试将文件的全部内容一次加载到内存中 。
如果没有这样做,即使只改变了该文件的几个字节,也必须存储每个单个文件的每个修订版本的全部内容。
这将是一个非常低效的磁盘空间使用 ,而git以其惊人的高效的存储格式而闻名。不幸的是,
xdelta
对于小文件非常适用,对于大文件来说,速度非常慢,对内存要求也很高 。
对于git的主要目的,即。 pipe理你的源代码,这不是一个问题。什么bup而不是xdelta是我们所说的“
hashsplitting
。
我们想要一种通用的方式来高效地备份可能以小的方式更改的大文件,而不是每次都存储整个文件。 我们一次读取一个字节的文件,计算最后128个字节的滚动校验和。
rollsum
似乎做得很好。 你可以在bupsplit.c
find它 。
基本上,它将最后读取的128个字节转换为一个32位整数。 然后我们要做的就是取最小的13位,如果它们都是1,我们认为这是一个块的结束。
这平均每2^13 = 8192 bytes
发生一次,所以平均块大小是8192字节。
我们将这些文件分成基于滚动校验和的块。
然后,我们分别存储每个块(由其sha1sum索引)作为一个git blob。通过散列分割,无论在文件中间添加,修改或删除多less数据,受影响的块前后的所有块都是完全相同的。
对于散列分割algorithm而言,所有重要的是32字节的“分隔符”序列,而单个更改最多只能影响一个分隔符序列或两个分隔符序列之间的字节。
就像魔术一样,散列分块algorithm每次都会以相同的方式分块文件,即使不知道它以前是如何分块的。下一个问题不太明显:在将一系列块存储为git blob后,如何存储它们的序列? 每个blob都有一个20字节的sha1标识符,这意味着blob的简单列表将是
20/8192 = 0.25%
的文件长度。
对于一个200GB的文件,这只是序列数据的488兆。我们使用我们所说的“扇出”来进一步扩展hashsplitalgorithm。 我们不是只检查校验和的最后13位,而是使用额外的校验和位来产生额外的分裂。
你最终得到的是一个实际的blob树 – 哪个git“树”对象是理想的代表。
处理大量的文件和git gc
git被devise用于处理相对较less变化的合理大小的存储库 。 您可能会认为您经常更改源代码,并且git处理比
svn
可以处理的更频繁的更改。
但这不是我们所说的“经常”的那种。#1杀手是它向存储库添加新对象的方式:它为每个blob创build一个文件。 然后你运行'git gc',并把这些文件合并成一个文件 (使用高效的xdelta压缩,忽略任何不再相关的文件)。
'
git gc
'速度很慢 ,但是对于源代码库来说,所产生的超高效存储(以及相关的真正快速访问存储文件)是值得的。
bup
不这样做。 它只是直接写包装文件。
幸运的是,这些包文件仍然是git格式的,所以git可以在写完之后很高兴地访问它们。
处理巨大的存储库(意味着巨大的数据包文件)
Git实际上并不是用来处理超大型的存储库的 。
大多数git仓库都足够小,合理的将它们合并到一个包文件中是合理的,通常git gc
最终会做到这一点。大包装文件中有问题的部分不是包装文件本身,git的目的是希望所有包装的总体尺寸大于可用内存,一旦它可以处理,它可以同样有效地处理任何数量的数据。
问题是packfile索引(.idx
)文件 。git中的每个packfile(
*.pack
)都有一个关联的idx
(*.idx
),这是一个git对象散列和文件偏移的sorting列表。
如果你正在寻找一个基于它的sha1的特定对象,你打开idx,二进制search它来find正确的散列,然后获取相关的文件偏移量,在packfile中寻找偏移量,并读取对象内容。二进制search的性能大约为
O(log n)
与包中散列的数量,并具有优化的第一步(您可以在其他地方阅读),将其稍微提高到O(log(n)-7)
。
不幸的是, 当你有很多包的时候 ,这个问题会有一些分解 。为了提高这类操作的性能,bup引入了
midx
(发音为“midix”,简写为“multi-idx”)文件。
顾名思义,他们一次索引多个包。
你真的,真的不希望大量的二进制文件签入你的Git仓库。
您添加的每个更新将累积地添加到您的存储库的整体大小,这意味着您的Git回购将花费越来越多的时间来克隆和使用越来越多的磁盘空间,因为Git在本地存储分支的整个历史logging,这意味着当某人签出分支时,他们不必下载最新版本的数据库; 他们还必须下载每个以前的版本。
如果您需要提供较大的二进制文件,请将它们分别上传到某个服务器,然后使用URL在文本文件中进行检查,以便开发人员可以下载较大的二进制文件。 FTP实际上是更好的select之一,因为它是专门为传输二进制文件而devise的,尽pipeHTTP可能更直接。
你可以看看像git-annex这样的解决scheme,它是关于使用gitpipe理(大)文件,而不检查文件内容到git(!)
(2015年2月: 像GitLab这样的托pipe服务本地集成 :
请参阅“ GitLab是否通过git-annex
或其他方式支持大文件? ”)
git不pipe理大文件,正如Amber在她的回答中所解释的那样。
这并不意味着git有一天会做得更好。
从GitMinutes第9集 ( 2013年5月,也见下文) ,从皮夫(杰夫金) ,在36'10“:
(成绩单)
还有一个大型的仓库领域,人们有兴趣存储,你知道,20或30或40 GB,有时甚至是TB大小的仓库,是的,它来自有大量的文件,但很多来从真正的大文件和非常大的二进制文件中处理不太好的文件。
这是一个开放的问题。 有几个解决scheme:git-annex可能是最成熟的那些,他们基本上不把资产放在git中,他们把大资产放在资产服务器上,并把指针放到git中。
我想做类似这样的事情,资产在概念上是在git中,也就是说,该对象的SHA1是进入树中的SHA1的一部分,进入提交ID和所有这些东西。
所以从git的angular度来看,它是存储库的一部分,但是在低于对象存储层的层次上,在概念性历史图下面,我们已经有多种存储对象的方式:我们有松散的对象 ,有包装对象 ,我想也许有一个新的方式来存储一个对象,这就是说,“我们没有在这里,但它是由资产服务器可用”,或类似的东西。( Thomas Ferris Nicolaisen )哦,很酷…
像
git-annex
这样的问题是:一旦你使用它们,你就会被locking在你当时所做的决定上。 你知道,如果你决定哦200 MB是大的,我们将存储在一个资产服务器,然后,你决定,应该已经300 MB ,好运气:这是永远编码在你的历史。
因此, 从概念上讲 ,在git层面上,这个对象是在 git仓库中,而不是一些指向它的指针,而不是指向资产服务器的指针, 实际的对象在那里,然后以低成本的方式来处理这些细节。在存储层面,这样就可以让你自由地做出很多不同的决定,甚至可以稍后改变你的决定,如何确定你想要在磁盘上存储的东西。
目前还不是一个高优先级的项目…
3年后的2016年4月,“ Git Minutes 40 ” 收录了 31 岁时 GitHub对Michael Haggerty的采访(谢谢Christian Couder采访 )。
他专门从事后台参考工作已经有一段时间了 。
他把大卫·特纳 ( David Turner )的后期工作视为目前最有趣的工作。 (请参阅David的git / git fork的当前“ pluggable-backends
”分支 )
(成绩单)
Christian Couder(CD):目标是让git refs存储在数据库中,例如? Michael Haggerty(MH):是的,我认为这是两个有趣的方面:第一个是简单地插入不同的源input参考。 条目引用存储在文件系统中,作为松散引用和打包引用的组合。
松散引用是每个引用一个文件,打包引用是一个包含许多引用列表的大文件。所以这是一个很好的系统,特别是对于本地的使用。 因为它对普通人没有任何实际的性能问题,但是它确实有一些问题,就像在引用被删除后你不能存储引用reflogs一样,因为可能与已经创build的类似的新引用有冲突名。 引用名称存储在文件系统中也有一个问题,所以你可以使用名字相似但大小写不同的引用。
所以这些都是可以通过具有不同的参考后端系统来解决的事情。
David Turner补丁系列的另一个方面是将参考文件存储在名为lmdb的数据库中,这是一个非常快速的基于内存的数据库,与文件后端相比具有一些性能优势。
[关于更快打包和参考补丁广告的其他考虑]
从git-stashed代码引用的文件的辅助存储是大多数人去的地方。 git-annex
看起来相当全面,但许多商店只是使用FTP或HTTP(或S3)存储库来存储大文件,如SQL转储。 我的build议是通过填充一些元数据(特别是校验和(可能是SHA))到哈希中以及date,将git repo中的代码绑定到辅助存储中的文件的名称。
- 因此,每个辅助文件都有一个基本名称,date和SHA(对于某些版本n)和。
- 如果你有文件的更新率,那么只使用SHA会产生一个很小但是真正的哈希冲突威胁,因此包含一个date(纪元时间或ISOdate)。
- 将结果文件名放入代码中,以便辅助块被包括在内,特别是通过引用。
- 以这样一种方式来构造名称,即可以轻松地编写一个小脚本来对所有aux文件名进行git grep,以便任何提交的列表都是微不足道的。 这也允许旧版本在某个时候退役,并且可以与部署系统集成,以便在从git repo激活代码之前将新的辅助文件投入生产,而不会破坏旧版本。
把巨大的文件塞进git(或者大部分的repos)会在一段时间之后影响git的性能 – 例如, git clone
实际上不应该花20分钟。 而通过引用来使用这些文件意味着一些开发人员根本就不需要下载大块(与git clone
形成鲜明的对比),因为大部分开发人员只能在生产环境中部署代码。 当然,你的里程可能会有所不同。