尝试重新打包git repo以提高性能后的问题

时间:2012-11-13 19:33:01

标签: git

不久之前,我发布了一个问题,要求就修复由于大量二进制文件很慢的仓库的计划提供反馈。这个问题(不是必读的):Fixing up a git repo that is slowed because of big binary files

我遵循了我的计划,并经历了意想不到的副作用。

我们回购的新鲜克隆最初耗时2-3小时。想出服务器开始交换,并在做完 git config pack.windowMemory 100m&& git config pack.packSizeLimit 200m ,克隆时间下降到~15分钟。

我认为我仍然会执行剩余的计划,所以我为我们的二进制类型禁用了delta compresson,并在repo上运行了 git repack -a -d -F 。 / p>

在此之后,回购的新鲜克隆需要大约20分钟,所以它实际上变得更糟。但真正的问题是每次有人已经克隆了repo尝试推送提交时,他们会“自动打包存储库以获得最佳性能。”。

关于可能出错的问题以及如何/应该如何修复的想法?

3 个答案:

答案 0 :(得分:4)

您的回购邮件的大小和pack.packSizeLimit的低值可能会使包的数量始终高于gc.autopacklimit。因此,增加其中任何一个以确保gc不会运行每次提交。

我不确定packSizeLimit会以什么方式影响记忆,但我不相信它会产生任何重大影响。如果您的实验显示不正确,请纠正我。直接影响内存使用的参数是pack.windowMemorypack.deltaCacheSize

答案 1 :(得分:0)

什么是重新包装?本地克隆,远程?请记住,本地和远程是单独的,这可能意味着您正在获取解压缩的东西然后在本地打包...需要在本地复制远程的配置(并再试一次,我可能会完全关闭碱)。

答案 2 :(得分:0)

您可能想重试相同的 git repack,这次使用 Git 2.32(2021 年第二季度,将近 8 年之后)及其新的 --geometric=<n> 选项:

"git repack"(man) 到目前为止只能将阳光下的所有东西重新打包成一个包(或按大小拆分)。

引入了一种更聪明的策略来降低重新打包存储库的成本。

参见 commit 14e7b83(2021 年 3 月 19 日)、commit 2a15964commit 13d746acommit dab3247commit f25e33c(2021 年 3 月 5 日)和 commit 0fabafdcommit 339bce2commit c9fff00commit f62312e(2021 年 2 月 22 日)由 Taylor Blau (ttaylorr)
请参阅 commit ccae01cJunio C Hamano (gitster)(2021 年 3 月 5 日)。
请参阅 commit 20b031fcommit 6325da1commit fbf20aecommit 60bb5f2Jeff King (peff)(2021 年 2 月 22 日)。
(由 Junio C Hamano -- gitster --commit 2744383 合并,2021 年 3 月 24 日)


首先:find_kept_pack_entry()

<块引用>

packfile:引入“find_kept_pack_entry()

合著者:Jeff King
签字人:Jeff King
签字人:Taylor Blau
审核人:Jeff King

<块引用>

未来的调用者将需要一个函数来为给定的对象 ID 填充“struct pack_entry”,但从它在任何保留包中的位置开始。

特别是,一种新的 'git repack'(man) 模式可确保生成的包按对象计数形成几何级数,将标记它不想要的包重新打包为“保持在核心中”,并且一旦它访问任何保留的包中的对象,它就会想要停止可达性遍历。
但是,它不想在非保留包或 .keep 包处停止遍历。

明显的替代方法是 'find_pack_entry()',但这还不够,因为它只返回它找到的第一个包,它可能会或可能不会被保留(并且 mru 缓存使它无法预测你是哪个如果有选择,就会得到)。

简而言之,您可以遍历所有包以寻找每个包中的对象,但它会随着包的数量而扩展,这可能会令人望而却步。

引入'find_kept_pack_entry()',一个类似于'find_pack_entry()'的函数,但只填充保留包中的对象。


那么:git pack-objects --stdin-packs

<块引用>

builtin/pack-objects.c:添加“--stdin-packs”选项

推荐人:Jeff King
签字人:Taylor Blau
审核人:Jeff King

<块引用>

在即将到来的提交中,'git repack'(man) 将要创建一个包,其中包含某些包(包含的包)中的所有对象<强>排除某些其他包中的任何对象(排除的包)。

这个调用者可以自己迭代这些包,并将它找到的对象直接通过标准输入提供给 'git pack-objects'(man),但这种方法有一些缺点:

  • 它要求每个想要以这种方式驱动“git pack-objects”的调用者自己实现包迭代。
    这会迫使调用者考虑一些细节,例如将什么顺序的对象提供给包装对象,而调用者可能宁愿不这样做。
  • 如果包含的包中的对象集很大,则需要通过管道发送大量数据,这是低效的。
  • 调用方也被迫跟踪排除的对象,并确保它不会发送出现在包含包和排除包中的任何对象。

但最大的缺点是缺乏可达性遍历。
因为调用者直接传入一个对象列表,这些对象没有得到分配给它们的 namehash,这会对 delta 选择过程产生负面影响,导致 'git pack-objects' 甚至无法找到好的 delta当它们存在时。

调用者可以自己制定可达性遍历,但以这种方式驱动“git pack-objects”的唯一方法是进行完整遍历,然后在遍历完成后移除排除包中的对象。< br/> 这可能对关心性能的调用者不利,尤其是在具有许多对象的存储库中。

引入 'git pack-objects --stdin-packs'(man) 来解决这四个问题。

'git pack-objects --stdin-packs' 需要 stdin 上的包名称列表,其中 'pack-xyz.pack' 表示该包已包含,而 '^pack-xyz.pack' 表示该包已排除。
生成的包包含至少一个包含的包中存在的所有对象,并且不存在于任何排除的包中。

git pack-objects 现在包含在其 man page 中:

<块引用>

--stdin-packs

读取包文件的基本名称(例如,pack-1234abcd.pack) 来自标准输入,而不是对象名称或修订版 参数。

生成的包包含列在列表中的所有对象 包含的包(那些不是以 ^ 开头的),不包括任何 排除包中列出的对象(以 ^ 开头)。

--revs 不兼容,或暗示 --revs 的选项(例如 --all),但兼容的 --unpacked 除外。


最后:git repack --geometric=<n>

<块引用>

builtin/repack.c:添加“--geometric”选项

推荐人:Derrick Stolee
签字人:Taylor Blau
审核人:Jeff King

<块引用>

通常对两者都有用:

  • 存储库中的包文件相对较少,并且
  • 避免存储库中的打包文件太少,以至于我们会定期重新打包其全部内容

此补丁在“git repack(man) 中实现了“--geometric=' 选项。
这允许调用者指定他们希望每个包至少是前一个最大包的因子倍(按对象计数)。

具体来说,假设存储库有“n”个包文件,标记为 P1、P2、...,直到 Pn。
每个包文件的对象计数等于“objects(Pn)”。
几何因子为“r”时,应该是:

objects(Pi) > r*objects(P(i-1))

对于 [1, n] 中的所有 i,包按以下顺序排序

objects(P1) <= objects(P2) <= ... <= objects(Pn).

由于找到真正的最佳重新打包是 NP-hard,我们沿两个方向对其进行近似:

  1. 我们假设在开始重新打包之前有一个包的截断,其中截断右边的所有东西已经形成了一个几何级数(或者不存在截断,一切都必须是重新打包)。

  2. 我们假设所有小于截止计数的东西都必须重新打包。
    这构成了我们的基本假设,但它也可能导致甚至“重”包被重新打包,例如,如果我们有 6 个包含以下对象数量的包:

    1、1、1、2、4、32

然后我们将截断点放在 '1, 1' 和 '1, 2, 4, 32' 之间,将前两个包卷成一个包含 2 个对象的包。
这打破了我们的进步并离开了我们:

2, 1, 2, 4, 32
  ^

(其中“^”表示我们拆分的位置)。
为了恢复进程,我们将拆分向前移动(朝向更大的包) 将每个包加入我们的新包直到几何级数 已恢复。
在这里,看起来像:

2, 1, 2, 4, 32  ~>  3, 2, 4, 32  ~>  5, 4, 32  ~> ... ~> 9, 32
  ^                   ^                ^                   ^

这样做的优点是不会过于频繁地重新打包重装包,同时一次只创建一个新包。
另一个问题是我们假设松散的、索引的和 reflog 的对象是无关紧要的,并将它们混入我们创建的任何新包中。
这可能会导致非幂等结果。

git repack 现在包含在其 man page 中:

<块引用>

-g=<factor>

--geometric=<factor>

排列产生的包结构,使每个连续的包 包含至少 <factor> 倍的对象数作为 下一个最大的包。

git repack 通过确定需要的包文件的“剪切”来确保这一点 重新包装成一个以确保几何级数。它 选择最小的一组包文件,以便与许多较大的包文件一样 可能会留下包文件(按该包中包含的对象计数) 完好无损。

与其他重新打包模式不同,要打包的对象集是确定的 唯一地通过“卷起”的一组包;换句话说, 确定需要组合的包以恢复几何 进展。

当指定 --unpacked 时,松散对象隐式包含在 这种“汇总”,而不考虑它们的可达性。这是主题 将来改变。此选项(意味着完全不同的 重新打包模式)不能保证与所有其他组合一起使用 git repack 的选项)。


Results

  Test                                        HEAD^                   HEAD
  -----------------------------------------------------------------------------------------------
  5303.5: repack (1)                          57.34(54.66+10.88)      56.98(54.36+10.98) -0.6%
  5303.6: repack with kept (1)                57.38(54.83+10.49)      57.17(54.97+10.26) -0.4%
  5303.11: repack (50)                        71.70(88.99+4.74)       71.62(88.48+5.08) -0.1%
  5303.12: repack with kept (50)              72.58(89.61+4.78)       71.56(88.80+4.59) -1.4%
  5303.17: repack (1000)                      217.19(491.72+14.25)    217.31(490.82+14.53) +0.1%
  5303.18: repack with kept (1000)            246.12(520.07+14.93)    217.08(490.37+15.10) -11.8%
<块引用>

--stdin-packs 情况,它的扩展性好一点(尽管 即使是 1,000 包也没有那么多):

  5303.7: repack with --stdin-packs (1)       0.00(0.00+0.00)         0.00(0.00+0.00) =
  5303.13: repack with --stdin-packs (50)     3.43(11.75+0.24)        3.43(11.69+0.30) +0.0%
  5303.19: repack with --stdin-packs (1000)   130.50(307.15+7.66)     125.13(301.36+8.04) -4.1%