简体中文 ▾ 主题 ▾ 最新版本 ▾ 多包索引 上次更新于 2.50.0

Git 对象目录包含一个 pack 目录,其中包含 packfile(后缀为“.pack”)和 pack-index(后缀为“.idx”)。pack-index 提供了一种查找对象并导航到其在包中偏移量的方法,但这些必须与 packfile 成对出现。这种配对取决于文件名,因为 pack-index 与其 pack 文件仅在后缀上不同。虽然 pack-index 为每个 packfile 提供快速查找,但随着 packfile 数量的增加,此性能会下降,因为缩写需要检查每个 packfile,并且我们更有可能在我们最近使用的 packfile 上查找失败。对于某些大型仓库,由于存储空间或过长的重新打包时间,将其重新打包成单个 packfile 是不可行的。

多包索引(简称 MIDX)存储对象列表及其在多个 packfile 中的偏移量。它包含

  • packfile 名称列表。

  • 按对象 ID 排序的列表。

  • 第 i 个对象 ID 的元数据列表,包括

    • 一个值 j,指代第 j 个 packfile。

    • 对象在第 j 个 packfile 中的偏移量。

  • 如果需要大偏移量,我们使用另一个类似版本 2 pack-index 的大偏移量列表。

    • 一个可选的伪包序对象列表(与 MIDX 位图一起使用)。

因此,我们可以为任意数量的 packfile 提供 O(log N) 的查找时间。

设计详情

  • MIDX 存储在 .git/objects/pack 目录中名为 multi-pack-index 的文件中。这也可以存储在备用 pack 目录中。它仅指代同一目录中的 packfile。

  • core.multiPackIndex 配置设置必须开启(默认),才能使用 MIDX 文件。将其设置为 false 会阻止 Git 读取 MIDX 文件,即使存在也不读取。

  • 文件格式包含对象 ID 散列函数的参数,因此未来散列算法的更改不需要更改格式。

  • MIDX 每个对象 ID 只保留一条记录。如果一个对象出现在多个 packfile 中,则 MIDX 会选择首选 packfile 中的副本,否则会从最近修改的 packfile 中选择。

  • 如果 pack 目录中存在未在 MIDX 中注册的 packfile,则这些 packfile 将加载到 packed_git 列表和 packed_git_mru 缓存中。

  • pack-index(.idx 文件)保留在 pack 目录中,这样我们就可以删除 MIDX 文件,将 core.midx 设置为 false,或降级而不会丢失任何信息。

  • MIDX 文件格式采用基于块的方法(类似于 commit-graph 文件),允许添加可选数据。

增量多包索引

随着仓库规模的增长,编写包含所有 packfile 的多包索引(MIDX)变得更加昂贵。为了适应这一点,“增量多包索引”功能允许组合多包索引的“链”。

链中的每个独立组件只需包含少量 packfile。追加到链中不会使链的早期部分失效,因此仓库可以通过确定 MIDX 链中每个层的包数量来控制更新 MIDX 链所花费的时间。

设计现状

目前,增量多包索引功能缺少两个重要组件

  • 重写 MIDX 链早期部分的能力(即,将相邻的 MIDX 层集合“压缩”成单个 MIDX)。目前,缩小 MIDX 链的唯一受支持方法是在不带 --split 标志的情况下从头重写整个链。

    目前没有根本性的限制阻碍实现此功能。为了降低复杂性,它在初始实现中被省略,但将在以后添加。

  • 对可达性位图的支持。经典的单一 MIDX 实现确实支持可达性位图(有关更多详细信息,请参阅 gitformat-pack[5] 中标题为“多包索引反向索引”的部分)。

    如上所述,没有根本性的限制阻碍将增量 MIDX 格式扩展以支持可达性位图。下面的设计专门考虑了这一点,并且将在未来的补丁系列中添加对可达性位图的支持。出于与上述相同的原因,它在当前实现中被省略了。

    简而言之,为了支持增量 MIDX 功能的可达性位图,伪包序的概念被扩展到增量 MIDX 链的每个层,以形成一个串联的伪包序。这种串联与链本身以相同的顺序发生(换句话说,对于链 {$H1, $H2, $H3} 的串联伪包序将是 $H1 的伪包序,然后是 $H2 的伪包序,再然后是 $H3 的伪包序)。

    布局将随后被扩展,以便增量 MIDX 链的每个层都可以写入一个 *.bitmap 文件。每个层的位图中的对象都按链中前一层中的对象数量进行偏移。

文件布局

增量 MIDX 不存储单个 multi-pack-index 文件(带可选的 .rev.bitmap 扩展名)在 $GIT_DIR/objects/pack 中,而是采用以下布局存储:

$GIT_DIR/objects/pack/multi-pack-index.d/
$GIT_DIR/objects/pack/multi-pack-index.d/multi-pack-index-chain
$GIT_DIR/objects/pack/multi-pack-index.d/multi-pack-index-$H1.midx
$GIT_DIR/objects/pack/multi-pack-index.d/multi-pack-index-$H2.midx
$GIT_DIR/objects/pack/multi-pack-index.d/multi-pack-index-$H3.midx

multi-pack-index-chain 文件按顺序包含链中增量 MIDX 文件的列表。上面的示例显示了一个链,其 multi-pack-index-chain 文件将包含以下行:

$H1
$H2
$H3

multi-pack-index-$H1.midx 文件包含多包索引链的第一层。multi-pack-index-$H2.midx 文件包含链的第二层,依此类推。

当增量和非增量 MIDX 都存在时,非增量 MIDX 总是首先被读取。

增量 MIDX 的对象位置

在原始的多包索引设计中,我们通过对象在其仓库的单一多包索引中的字典序位置(按对象 ID)来引用对象。在增量多包索引设计中,我们通过对象在 MIDX 链中每个组件的串联字典序中的索引来引用对象。

如果 objects_nr() 是一个返回给定 MIDX 层中对象数量的函数,那么在(例如)$H3 中,位于字典序位置 i 的对象的索引定义为

objects_nr($H2) + objects_nr($H1) + i

(在 C 实现中,这通常计算为 i + m->num_objects_in_base)。

增量 MIDX 的伪包序

多包可达性位图的原始实现,在 gitformat-pack[5] 中(参阅标题为“多包索引反向索引”的部分)大致定义了伪包序如下:

简而言之,MIDX 的伪包是 MIDX 存储的包中对象的去重串联,按包序排列,并且包按 MIDX 序排列(首选包在前)。

在增量 MIDX 设计中,我们将此定义扩展为包含 MIDX 链多个层中的对象。增量 MIDX 的伪包序通过按顺序串联 MIDX 链每个层的伪包序来确定。形式上,两个对象 o1o2 的比较方式如下:

  1. 如果 o1 出现在 MIDX 链中比 o2 更早的层中,则 o1o2 之前排序。

  2. 否则,如果 o1o2 出现在同一个 MIDX 层中,并且该 MIDX 层没有基础层,那么如果 pack(o1)pack(o2) 中的一个被首选而另一个不是,则首选的那个排在非首选的那个之前。如果存在基础层(即,该 MIDX 层不是链中的第一层),那么如果 pack(o1) 在该 MIDX 层的包序中出现得更早,则 o1o2 之前排序。同样,如果 pack(o2) 出现得更早,则情况相反。

  3. 否则,o1o2 出现在同一个包中,因此也在同一个 MIDX 层中。根据它们在包含它们的 packfile 中的偏移量对 o1o2 进行排序。

请注意,首选包是 MIDX 链的属性,而不是各个层本身的属性。从根本上说,我们可以引入每层首选包,但现在我们可以在 MIDX 中的所有包之间执行多包复用,这就不那么重要了。

可达性位图和增量 MIDX

增量 MIDX 链的每个层都可以将其对象(以及同一 MIDX 链中任何先前层中的对象)表示在自己的 *.bitmap 文件中。

属于增量 MIDX 链的 *.bitmap 文件的结构与非增量 MIDX 位图或经典单包位图的结构相同。由于对象被添加到增量 MIDX 的伪包序的末尾(见上文),因此在追加到 MIDX 链的末尾时可以扩展位图。

(注意:类似地,可以将 MIDX 增量层的连续序列及其 *.bitmap 文件压缩到单个层和 *.bitmap 中,但这尚未实现。)

使用的对象位置在伪包序中是全局的,因此后续层将分别在其四个类型位图中拥有(例如)m->num_objects_in_base0 位。这是因为我们只为与位图直接对应的层中存在的对象写入类型位图条目)。

另请注意,增量 MIDX 链中最新层所涉及的位图用于存储关于可达性查询中相关对象和不相关对象的可达性信息。较早的位图层仅用于从该层查找提交和伪合并位图,以及该层中对象的类型级位图。

为了简化实现,类型级位图同时迭代,并且它们的结果进行逻辑或操作,以避免递归调用内部位图函数。

未来工作

  • 如果多包索引扩展为存储“稳定对象顺序”(一个函数 Order(hash) = 整数,对于给定的散列是常量,即使多包索引更新),那么 MIDX 位图可以独立于 MIDX 更新。

  • packfile 可以通过使用空文件标记为“特殊”,这些空文件共享初始名称但将“.pack”替换为“.keep”或“.promisor”。我们可以在多包索引中添加一个可选的数据块,用于记录有关 packfile 的标志信息。这允许新的状态,例如重新打包重新增量化,有助于在多包环境中进行包维护。按对象类型(提交、树、blob 等)组织 packfile 并使用此元数据来帮助维护也可能很有用。

[0] https://bugs.chromium.org/p/git/issues/detail?id=6 Chromium 工作项:多包索引 (MIDX)

[2] https://lore.kernel.org/git/alpine.DEB.2.20.1803091557510.23109@alexmv-linux/ Git Merge 2018 贡献者峰会笔记(包括 MIDX 讨论)

scroll-to-top