尝试重新打包Git仓库以提高性能后出现的问题

10
不久前,我发布了一个问题,询问修复因大量大型二进制文件而变慢的仓库的反馈意见。这个问题(本题不必阅读): 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 分钟。
我打算继续实施我的计划,因此禁用了我们拥有的二进制类型的增量压缩,并对该仓库执行了 git repack -a -d -F 命令。
此后,重新克隆该仓库需要约20分钟,因此情况变得更糟了。但真正的问题是,每当已经克隆了该仓库的人尝试推送提交时,会出现“自动为最佳性能打包仓库”的提示。
有关可能出错的原因以及如何解决的任何想法吗?

我现在才看到这个,但是:我们禁用了二进制类型的增量压缩,并在仓库上运行了git repack -a -d -F命令,你是不是指的是-f而不是-F - clacke
3个回答

5

您的代码库大小和pack.packSizeLimit的低值使得包的数量始终高于gc.autopacklimit。因此,增加其中一个参数以确保垃圾回收程序不会在每次提交时运行。

我不确定packSizeLimit会以何种方式影响内存,但我认为它不会有任何显著影响。如果您的实验结果表明有所不同,请纠正我。直接影响内存使用的参数是pack.windowMemorypack.deltaCacheSize


之前就想到解决这个问题了,但是忘记在帖子上留言了。您的答案是正确的。它尽可能地重新打包,但总是最终得到太多的包。将尝试移除packSizeLimit并检查内存使用情况。我认为您是正确的 :) - anr78

2
您可能想要重试相同的git repack命令,这次使用Git 2.32(2021年第二季度,几乎晚了8年),以及它的新选项--geometric=<n>

"git repack"(man)到目前为止只能将所有内容打包成一个单独的包(或按大小拆分)。

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

查看 提交 14e7b83(2021年3月19日),提交 2a15964提交 13d746a提交 dab3247提交 f25e33c(2021年3月5日),以及提交 0fabafd提交 339bce2提交 c9fff00提交 f62312e(2021年2月22日)由Taylor Blau (ttaylorr)
查看 提交 ccae01c(2021年3月5日)由Junio C Hamano (gitster)
查看 提交 20b031f提交 6325da1提交 fbf20ae提交 60bb5f2(2021年2月22日)由Jeff King (peff)
(由Junio C Hamano -- gitster --合并于提交 2744383,2021年3月24日)
首先:find_kept_pack_entry()

packfile: 引入 'find_kept_pack_entry()'

共同作者:Jeff King
签署者:Jeff King
签署者:Taylor Blau
评审者:Jeff King

未来的调用者将需要一个功能,为给定对象ID填充结构体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) 将创建一个打包文件,其中包含一些打包文件(被包含的打包文件)中的所有对象排除其他一些打包文件中的对象(被排除的打包文件)。 调用者可以自己迭代这些打包文件,并将其找到的对象直接通过stdin传递给'git pack-objects'(man),但是这种方法有一些缺点:
  • 每个想要以这种方式驱动' git pack-objects '的调用者都需要自己实现打包迭代。这迫使调用者考虑细节,例如将对象提供给pack-objects的顺序,而调用者可能不愿意这样做。
  • 如果包含的打包文件中的对象集很大,则需要通过管道发送大量数据,这是低效的。
  • 调用者也必须跟踪被排除的对象,并确保不会发送出现在包含和被排除的打包文件中的任何对象。

但最大的缺点是缺少可达性遍历。
因为调用者直接传递了对象列表,这些对象没有被分配名称哈希,这可能会对增量选择过程产生负面影响,导致' git pack-objects '无法找到好的增量,即使它们存在。

调用者可以自己制定可达性遍历,但以这种方式驱动' git pack-objects '的唯一方法是执行完整遍历,然后在遍历完成后删除被排除的对象。
这可能会对关心性能的调用者产生不利影响,特别是在具有许多对象的存储库中。

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

'git pack-objects --stdin-packs' 期望在stdin上接收一个打包文件名称列表,其中 'pack-xyz.pack' 表示该打包文件已被包含,而 '^pack-xyz.pack' 表示它被排除。结果的打包文件包括至少出现在一个包含的打包文件中的所有对象,并且不出现在任何被排除的打包文件中。

git pack-objects现在在其手册页面中包含以下内容:

--stdin-packs

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

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

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


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

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

建议者:Derrick Stolee
签署者:Taylor Blau
审核者:Jeff King

Often it is useful to both:

  • have relatively few packfiles in a repository, and
  • avoid having so few packfiles in a repository that we repack its entire contents regularly

This patch implements a '--geometric=' option in 'git repack'(man).
This allows the caller to specify that they would like each pack to be at least a factor times as large as the previous largest pack (by object count).

Concretely, say that a repository has 'n' packfiles, labeled P1, P2, ..., up to Pn.
Each packfile has an object count equal to 'objects(Pn)'.
With a geometric factor of 'r', it should be that:

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

for all i in [1, n], where the packs are sorted by

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

Since finding a true optimal repacking is NP-hard, we approximate it along two directions:

  1. We assume that there is a cutoff of packs before starting the repack where everything to the right of that cut-off already forms a geometric progression (or no cutoff exists and everything must be repacked).

  2. We assume that everything smaller than the cutoff count must be repacked.
    This forms our base assumption, but it can also cause even the "heavy" packs to get repacked, for e.g., if we have 6 packs containing the following number of objects:

    1, 1, 1, 2, 4, 32

then we would place the cutoff between '1, 1' and '1, 2, 4, 32', rolling up the first two packs into a pack with 2 objects.
That breaks our progression and leaves us:

2, 1, 2, 4, 32
  ^

(where the '^' indicates the position of our split).
To restore a progression, we move the split forward (towards larger packs) joining each pack into our new pack until a geometric progression is restored.
Here, that looks like:

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

This has the advantage of not repacking the heavy-side of packs too often while also only creating one new pack at a time.
Another wrinkle is that we assume that loose, indexed, and reflog'd objects are insignificant, and lump them into any new pack that we create.
This can lead to non-idempotent results.

现在,git repack 在其手册页面中包含了以下内容:

-g=<因子>

--geometric=<因子>

排列结果包结构,以便每个连续的包含至少 <因子> 倍于下一个最大包的对象数。

git repack 通过确定需要重打包成一个包以确保几何级数的“割”来实现这一点。它选择最小的一组包文件,以便尽可能多地保留较大的包文件(按包含在该包中的对象计数)。

与其他重新打包模式不同,要打包的对象集合是由正在“卷起”的包的集合唯一确定的;换句话说,需要组合的包已确定以恢复几何级数。

当指定了 --unpacked 时,松散的对象隐式地包含在此“卷起”中,而不考虑它们的可达性。这可能会在未来发生变化。这个选项(暗示着一个截然不同的重新打包模式)不能保证与 git repack 的所有其他选项组合一起使用。


结果

  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%

Taylor Blau那里查看“Git 2.33:几何再打包”。
历史上,git repack 有两种操作方式:一种是将所有松散的对象重新打包到一个新的包中(可选删除每个对象的松散副本),另一种是将所有包合并到一个新的包中(可选删除冗余的包)。通常情况下,当存储库中的包数量较少时,Git 的性能更好,因为许多操作与存储库中的包数量成比例。因此,将所有内容打包到一个单独的包中通常是一个好主意。但从历史上来看,繁忙的存储库通常要求将其所有内容打包到一个单独的巨大包中。这是因为可达性位图是服务器端 Git 性能的关键优化,只能描述单个包中的对象。因此,如果您想使用位图有效地覆盖存储库中的许多对象,则这些对象必须存储在同一个包中。我们正在努力消除这个限制(您可以阅读更多有关我们如何做到这一点的信息),但实现一个新的打包方案是实现这一目标的重要步骤,它在相对较少的包和打包最近添加的对象之间进行权衡(换句话说,近似于自上次打包以来添加的新对象)。
为了实现这一点,Git 学习了一种新的“几何”重新打包策略。
引用:
这个想法是确定一组(相对较小的)可以合并在一起的 pack,以便剩余的 pack 基于对象大小形成一个几何级数。换句话说,如果最小的 pack 有 N 个对象,则下一个最大的 pack 至少会有 2N 个对象,以此类推,在每个步骤中加倍(或按任意常数增长)。
git pack-objects --stdin-packs” 的输入验证已经在 Git 2.34(2021年第四季度)中得到了修正。 (man) 请参考以下提交记录:
- commit 561fa03 (于2021年7月9日)
- commit fb20d4b (于2021年6月21日)
这些提交记录由Ævar Arnfjörð Bjarmason (avar)进行。
(于2021年8月24日),Junio C Hamano -- gitster --已将其合并在了commit 5c933f0中。

pack-objects:修复--stdin-packs选项中的段错误

签名作者:Ævar Arnfjörð Bjarmason

339bce2 中添加了 --stdin-packs 选项,修复了一个段错误(segfault)("builtin/pack-objects.c: add '--stdin-packs' option",2021-02-22,Git v2.32.0-rc0 -- merge listed in batch #4)。
read_packs_list_from_stdin() 函数没有检查它正在读取的行是否是有效的包,因此在使用 pack_mtime_cmp() 进行 QSORT() 时,我们将有一个空的 "util" 字段。
"util" 字段用于将包含/排除的包的名称与它们对应的 packed_git 结构相关联。
逻辑错误在于假设我们可以迭代所有包并注释掉我们得到的排除和包含的包,而不是检查我们从 stdin 得到的行。
有一个对于被排除的包的检查,但是对于包含的包只是简单地假定为有效的。
正如我们在测试中所指出的,我们将不会报告第一个坏行,而是根据 string-list.c API 排序最先的行。
在这种情况下,我认为这是可以接受的。
还有 其他替代方法的讨论
即使我们有点懒,让我们断言我们在测试中期望得到的行,因为将来更改此代码的人可能会忽略这种情况,并希望更新测试和注释。
加速重新打包的另一种方法是使用--quiet,这实际上是在Git 2.35(2022年第1季度)中运行的。请参见提交47ca93d提交e4d0c11(2021年12月20日),作者为Derrick Stolee(derrickstolee(由Junio C Hamano -- gitster --合并于提交88a516a,2022年1月5日) “repack”:使“--quiet”禁用进度条 帮助者:Jeff King 签名者:Derrick Stolee 在尝试一些 'git repack'(man) 的想法时,我使用 '--quiet' 运行它,发现仍然会显示某些进度输出。
具体来说,写入多包索引的输出显示了进度。
cmd_repack() 中的 'show_progress' 变量是用 isatty(2) 初始化的,并且不受 '--quiet' 标志的影响。
'--quiet' 标志修改了 po_args.quiet 选项,该选项被转换为 'git pack-objects'(man) 子进程的 '--quiet' 标志。
但是,'show_progress' 用于直接将进度信息发送到编写多包索引的逻辑中,该逻辑不使用子进程。
修复方法是如果 po_opts.quiet 为 true,则将 'show_progress' 修改为 false;否则修改为 isatty(2)
这个新期望简化了稍后检查两者的条件。
更新文档以明确 '-q' 将禁用所有进度,并确保 'git pack-objects' 子进程将收到该标志。

git repack 现在包括在其手册页中:

--quiet

不要在标准错误流中显示任何进度,并将-q选项传递给'git pack-objects'。请参见git pack-objects


在 Git 2.37(2022 年第三季度)中,教会 "git repack --geometric"(man) 更好地使用 --keep-pack,避免在使用 packsize 限制时破坏存储库。
查看 提交 66731ff提交 aab7bea(2022年5月20日),作者为Taylor Blau (ttaylorr)
查看 提交 4b5a808(2022年5月20日),作者为Victoria Dye (vdye)
(由Junio C Hamano -- gitster --提交 16a0e92中合并,2022年6月3日) repack: 尊重 -- 保持包装,使用几何重新包装 共同作者:Taylor Blau
签署者:Victoria Dye
签署者:Taylor Blau 将“repack”更新为使用“--keep-pack”选项忽略在命令行上命名的包。
具体来说,修改“init_pack_geometry()”以使命令行保留的包与具有磁盘上“.keep”文件的包相同(即跳过该包并不将其包含在“geometry”结构中)。
如果没有这个处理,“--keep-pack”包将被包含在“geometry”结构中。
如果该包位于几何分割线之前(至少存在一个其他包和/或松散对象),则“repack”假定该包的内容通过“pack-objects”“卷起”到另一个包中。
然而,由于内部调用的“pack-objects”正确地排除了“--keep-pack”对象,因此它创建的任何新包都不会包含保留的对象。
最后,“repack”将“--keep-pack”删除为“冗余”(因为它假设“pack-objects”创建了一个包含其内容的新包),从而导致可能丢失对象和存储库损坏。
在Git 2.39之前(2022年第四季度),当几何重打包功能与--pack-kept-objects选项一起使用时,我们会丢失标记有.keep文件的包。请参见提交197443e(2022年10月17日)由Taylor Blau(ttaylorr提交。(由Junio C Hamano -- gitster --合并于提交f62c546,2022年10月27日)repack:不要使用 --pack-kept-objects 命令移除带有 .keep 后缀的 pack 文件。” “共同撰写者:Victoria Dye” “签名者:Taylor Blau”

git repack(man) supports a --pack-kept-objects flag which more or less translates to whether or not we pass --honor-pack-keep down to git pack-objects(man) when assembling a new pack.

This behavior has existed since ee34a2b ("repack: add repack.packKeptObjects config var", 2014-03-03, Git v2.0.0-rc0 -- merge).
In that commit, the documentation was extended to say:

[...] Note that we still do not delete `.keep` packs after
`pack-objects` finishes.

Unfortunately, this is not the case when --pack-kept-objects is combined with a --geometric repack.
When doing a geometric repack, we include .keep packs when enumerating available packs only when pack_kept_objects is set.

So this all works fine when --no-pack-kept-objects (or similar) is given.
Kept packs are excluded from the geometric roll-up, so when we go to delete redundant packs (with -d), no .keep packs appear "below the split" in our geometric progression.

But when --pack-kept-objects is given, things can go awry.
Namely, when a kept pack is included in the list of packs tracked by the pack_geometry struct and part of the pack roll-up, we will delete the .keep pack when we shouldn't.

Note that this doesn't result in object corruption, since the .keep pack's objects are still present in the new pack.
But the .keep pack itself is removed, which violates our promise from back in ee34a2b.

But there's more.
Because repack computes the geometric roll-up independently from selecting which packs belong in a MIDX (with --write-midx), this can lead to odd behavior.
Consider when a .keep pack appears below the geometric split (ie., its objects will be part of the new pack we generate).

We'll write a MIDX containing the new pack along with the existing .keep pack.
But because the .keep pack appears below the geometric split line, we'll (incorrectly) try to remove it.
While this doesn't corrupt the repository, it does cause us to remove the MIDX we just wrote, since removing that pack would invalidate the new MIDX.

Funny enough, this behavior became far less noticeable after e4d0c11 ("repack: respect kept objects with '--write-midx -b'", 2021-12-20, Git v2.35.0-rc0 -- merge listed in batch #7), which made pack_kept_objects be enabled by default only when we were writing a non-MIDX bitmap.

But e4d0c11 didn't resolve this bug, it just made it harder to notice unless callers explicitly passed --pack-kept-objects.

The solution is to avoid trying to remove .keep packs during --geometric repacks, even when they appear below the geometric split line, which is the approach this patch implements.


使用 Git 2.39(2022年第四季度),创建多包位图时,无条件删除每个包的位图文件,因为它们永远不会被查询。
参见 提交55d902c(2022年10月17日),由Taylor Blau (ttaylorr)撰写。
(由Junio C Hamano -- gitster --提交c5dd777中合并,2022年10月28日)

builtin/repack.c:删除冗余的基于打包的位图

签署者:Taylor Blau

当我们在重打包后写入MIDX位图时,仓库可能会处于同时存在基于打包和多打包可达性位图的状态。

例如,如果保留了一个打包(通过具有.keep文件或在几何重打包期间未被卷起),并且该打包具有位图文件,则重打包将编写多包索引和位图。

在加载仓库的可达性位图时,始终优先使用多包位图,因此基于打包的位图是多余的。
即使没有传递'-d',让我们无条件地删除它,因为没有实际理由保留两者。
以下补丁就是这样做的。


你是否知道NP难问题的证明? - undefined
@user7427029 我不是。Taylor Blau 负责此事。 - undefined

0

正在重新打包什么?本地克隆还是远程的?请记住,本地和远程是分开的,这可能意味着您正在获取未打包的内容,然后在本地进行打包...需要在本地复制远程的配置(然后再试一次,我可能完全错了)。


如果你在推送时收到“自动打包存储库以获得最佳性能”的消息,那就是远程存储库。 - clacke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接