GLEP 75:拆分 distfile 镜像目录结构

作者 Michał Górny <mgorny@gentoo.org>,Robin H. Johnson <robbat2@gentoo.org>
类型 标准规范
状态 最终版
版本 1
创建日期 2018-01-26
最后修改日期 2023-09-15
发布历史 2018-01-27, 2019-10-24
GLEP 源码 glep-0075.rst

状态

截至 2019 年 10 月 18 日,Gentoo 基础设施团队已成功部署了filename-hash BLAKE2B 8布局到 Gentoo 镜像上。之前的扁平目录结构已于 2023 年 9 月 14 日移除 [1]

摘要

本 GLEP 描述了将镜像上的 distfile 拆分成多个目录的流程,目的是减少单个目录中的文件数量。

动机

目前,包管理器和 Gentoo 镜像都使用扁平目录结构来存储文件。虽然这种解决方案通常有效,但它扩展性不佳。包含大量文件的目录通常会带来明显的性能损失,除非使用专门为此目的设计的软件文件系统。

根据 2018 年 1 月 26 日 16:23 时 Gentoo 仓库的状态,仓库中共有 62652 个唯一的 distfile。虽然用户实际上只访问其中约 10%,但 distfile 镜像通常存储更多文件——如果旧的 distfile 没有立即被清除,则情况尤其如此。

虽然 Linux 系统上使用的所有文件系统都应该能够处理如此大量的文件,但即使只有几千个文件,它们也可能遭受性能损失。此外,如果镜像启用了目录索引,那么生成索引会带来大量的服务器开销和数据传输。目前,distfiles.gentoo.org 的索引约为 17 MiB。

将 distfile 拆分成多个目录可以通过减少单个目录中的文件数量来避免这些问题。例如,将上述 distfile 集合并拆分为 16 个大致平衡的目录,可以将单个目录中的文件数量减少到约 4000 个。进一步将它们拆分为 256 个目录(16x16)会导致每个目录 200-300 个文件,这应该可以长期避免任何性能问题,即使假设 distfile 数量增长 300%。

规范

镜像布局文件

遵循本规范的镜像应在 distfile 的顶级目录中包含一个layout.conf文件。此文件使用源自 freedesktop 桌面条目规范文件格式的格式 [2]

在使用每个 Gentoo 镜像之前,包管理器应尝试获取(更新)其layout.conf文件并对其进行处理以确定如何使用镜像。如果文件不存在,包管理器应按其为空的情况进行处理。

包管理器应识别下面列出的部分和键。它应该忽略任何无法识别的部分或键——该格式旨在考虑将来的扩展。

本规范目前定义了一个部分[structure]。此部分使用非负顺序整数键定义了一个或多个仓库结构定义。具有0键的定义是最优选的结构。包管理器应忽略任何它无法识别的格式。如果此部分不存在,包管理器应按仅指定flat结构的情况进行处理。

以下结构定义受支持

  • flat表示所有 distfile 都位于顶级目录中的传统扁平结构,
  • filename-hash <algorithm> <cutoffs>表示下面解释的 文件名哈希结构

文件名哈希结构

使用文件名哈希结构时,distfile 会被拆分成多个目录,这些目录的名称源自 distfile 文件名的哈希值。此结构有两个参数:*算法名称* 和 *截止值* 列表。

算法名称必须对应于有效的清单哈希名称。GLEP 74 [3] 中包含了哈希信息的列表,而引入新哈希的策略则由 GLEP 59 [4] 涵盖。

截止值列表指定了一个或多个用冒号(:)分隔的整数,表示用于形成后续子目录名称的哈希位数(从最高有效位开始)。例如,列表4:8表示顶级目录名称使用哈希的最高 4 位形成(产生 2⁴ = 16 个目录),并且这些目录中的每一个都将具有使用哈希的下一 8 位形成的子目录(每个产生 2⁸ = 256 个子目录)。

实现只需要支持截止值为 4 的倍数。对其他值的支持是可选的。

确定 distfile 位置的确切算法如下

  1. 假设 distfile 文件名为 F
  2. 计算 F 的哈希值,并将它的二进制值存储为 H
  3. 对于截止值列表中的每个整数 C
    1. H 中移除 C 个最高有效位,并将它们存储为 V
    2. V 转换为十六进制表示法(数字09和小写字母af),在左侧用零填充到 C/4 位(向上取整),并将其追加到路径,后跟路径分隔符。
  4. 最后,将 F 追加到获得的路径。

特别需要注意的是,当使用嵌套目录时,子目录不会重复父目录中使用的哈希位。

将镜像迁移到哈希结构

由于所有 distfile 镜像都与主 Gentoo 镜像同步,因此只需在主镜像上执行所有必要的更改并等待其他镜像同步即可。建议以下流程

  1. 包含初始layout.conf仅列出flat布局。
  2. 在扁平结构旁边创建新的结构。等待镜像同步。
  3. 一旦所有镜像都接收了新的结构,更新layout.conf以列出filename-hash结构。
  4. 一旦支持新结构的 Portage 版本稳定足够长的时间,从flat中删除回退layout.conf结构和重复的 distfile。

这意味着在迁移期间,distfile 将在镜像上重复存储,因此将占用两倍的空间。从技术上讲,这可以通过使用硬链接或符号链接来避免。

硬链接解决方案使我们能够节省主镜像上的空间。此外,如果镜像使用-H选项,它可以避免再次传输现有文件。但是,此选项众所周知开销很大,并且可能导致严重的服务器负载。如果没有它,所有镜像都需要传输所有现有文件的第二个副本。

如果我们可以依赖镜像使用--linksrsync 选项,符号链接解决方案可能会更可靠。如果没有,符号链接根本不会被传输。

对本地 distfile 使用哈希结构

上面定义的哈希结构也可以用于包管理器使用的本地 distfile 存储。为了实现这一点,包管理器作者需要确保

  1. 在 ebuild 范围内,${DISTDIR}变量指向一个临时目录,其中特定于软件包的 distfile 以扁平结构链接。
  2. 所有工具都已更新以支持嵌套结构。
  3. 包管理器为用户提供了一个工具,使他们能够轻松地操作 distfile,特别是将受限获取软件包的 distfile 添加到相应的子目录中。

为了扩展兼容性,包管理器可以同时支持在扁平结构和嵌套结构中查找 distfile。

基本原理

拆分 distfile 的算法

在考虑可能的算法时,我们牢记以下目标

  • 单个目录中的文件数量不应超过 1000 个,
  • 单个目录中文件的总大小无关紧要,
  • 解决方案最好是面向未来的,
  • 一旦部署,应避免移动 distfile。

还应该注意,目前 Gentoo 中 distfile 数量最多的软件包是 dev-texlive/texlive-latexextra,其 distfile 数量为 8556 个。它们都以texlive-module-的公共前缀开头。此特定前缀被总共 23435 个 distfile 使用。

在 bug #534528 [5] 中发生的最初讨论以及此 GLEP 初始版本 [6] 的邮件列表审查中,列出了四种用于拆分 distfile 的基本思路

  1. 使用文件名开头部分,
  2. 使用文件哈希开头部分,
  3. 使用文件名哈希开头部分,
  4. 使用软件包类别(和软件包名称)。

最初的文件名思路是使用文件名的第一个字符,后面可能跟着更长的部分,例如 PyPI Python 软件包托管历史上使用的思路。其主要优点是简单。用户只需查看 distfile 名称即可轻松确定正确的子目录。遗憾的是,此解决方案不仅非常不均匀,而且无法解决问题。如上所述,TeX Live 软件包共享一个很长的公共前缀,这使得无法将其与其他软件包在固定长度的前缀上正确拆分。

此思路之后,Andrew Barchuk 提出了一个自适应方案 [7]。在此方案中,文件名并非严格地通过公共前缀映射到组,而是每个组包含两个正在使用的前缀之间的所有文件(如字典中)。但是,有人指出,虽然此选项最初可以提供非常均匀的结果,但无法预测未来 distfile 更改将如何影响它,并且将来存在需要更改组的风险。此外,它相对复杂,需要显式列出或获取已使用的组。

另一个选项是使用 distfile 哈希的开头部分。其主要优点是加密哈希算法可以提供更均衡的随机数据拆分。此外,由于哈希存储在清单文件中,因此使用它们对用户没有成本。但是,此解决方案存在三个缺点

  1. 并非 distfile 树中的所有文件都包含在软件包清单文件中。其他文件被注入到镜像中,这些文件将没有明确定义的位置。
  2. 哈希不匹配的用户提供的 distfile(例如,用于获取受限软件包)将放置在错误的子目录中,可能会导致令人困惑的错误。
  3. 新下载的 distfile 的哈希值未知,因此repoman(或等效工具)必须使用临时目录才能将文件定位到相应的子目录中。

使用文件名哈希已被证明可以提供与使用文件哈希类似的平衡。此外,由于文件名是预先知道的,因此此解决方案不会受到上述问题的困扰。虽然需要手动计算哈希,但对短字符串进行哈希不应该导致任何性能问题。

Jason Zaman 建议使用软件包类别(和软件包名称) [8]。但是,此解决方案存在多个问题

  1. 它没有解决像 TeX Live 这样的大型软件包的问题,
  2. 它引入了许多不必要的、很小的目录,
  3. 它需要明确知道哪些 distfile 属于哪个软件包,
  4. 它没有提供针对多个软件包共享的 distfile 问题的明确解决方案,
  5. 它没有提供针对注入的 distfile 问题的解决方案。

综合考虑所有选项,文件名哈希解决方案被选中,因为它可以解决所有上述问题,同时引入的复杂性相对较低,并且具有合理的未来兼容性。

glep-0075-extras/by-filename.png

按文件名的第一个字符划分 distfile 分布(注意:y 轴采用对数刻度)

glep-0075-extras/by-csum.png

按校验和的第一个十六进制数字划分 distfile 分布(x — 内容校验和,+ — 文件名校验和)

glep-0075-extras/by-csum2.png

按校验和的前两个十六进制数字划分 distfile 分布(x — 内容校验和,+ — 文件名校验和)

截止值

原始草案允许任何截止值。这已更改,因为 4 的倍数更容易实现——它们可以从哈希值的十六进制表示中轻松截取。此表示形式通常由哈希函数实现使用,包括 Portage 实用程序函数、pkgcore 实用程序函数 (snakeoil) 和b2sumcoreutils 中的实用程序。

布局文件

在最初的讨论中有人建议使用控制文件。其主要目的是让软件包管理器能够干净地处理迁移并检测如何在整个过程中正确查询镜像。此外,它使将来的更改更容易。

格式行特别旨在尽可能少地硬编码实际算法。因此,我们可以轻松更改使用的哈希或确切的拆分结构,而无需更新软件包管理器甚至提供兼容性布局。

该文件也对将来扩展以提供其他镜像元数据开放。但是,到目前为止尚未确定此功能的明确用途。

哈希算法

哈希算法支持完全延迟到软件包管理器中处理清单文件所需的现有代码中。特别是,建议重用当时清单文件条目中使用的哈希之一。这避免了代码重复并重用了处理哈希升级的现有机制。

在讨论中,有人指出,此特定用例不需要加密强度高的哈希,可以改用更快的算法。但是,鉴于哈希字符串的长度很短,性能不是问题,并且速度不足以证明由此产生的代码重复是合理的。

有人还指出,例如 BLAKE2 哈希系列能够创建任意长度的哈希,而不是截断标准长度的哈希。但是,并非所有 BLAKE2 实现都支持这一点,依赖它可能会降低可移植性,而没有明显的收益。

向后兼容性

镜像兼容性

镜像文件作为不透明的目录结构传播到其他镜像。因此,镜像方面没有向后兼容性问题。

与现有客户端的向后兼容性在 将镜像迁移到哈希结构 部分中详细介绍。通过在过渡期间保留扁平结构,将提供与旧客户端的向后兼容性。

新客户端将获取layout.conf文件以避免将来出现向后兼容性问题。如果遇到旧镜像,软件包管理器将默认为flat结构。

包管理器存储兼容性

软件包管理器存储中保留向后兼容性的确切方法留给软件包管理器作者。但是,建议软件包管理器即使不再是默认设置,也继续支持扁平布局。软件包管理器可以继续从此位置读取文件,也可以自动将其移动到相应的子目录。

参考实现

从 2.3.77 版开始,Portage 已实现对本规范的支持。这包括获取 distfile 和通过emirrordist维护镜像。该实现支持列出的所有布局,以及 Portage 支持的所有哈希函数和 4 的倍数的截止值。

参考文献

[1]错误 784713 - 删除旧的 distfile 镜像布局 (https://bugs.gentoo.org/784713)
[2]桌面条目规范:文件的基本格式 (https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s03.html)
[3]GLEP 74:使用清单文件进行全树验证:校验和算法(信息性)(https://gentoolinux.cn/glep/glep-0074.html#checksum-algorithms-informational)
[4]GLEP 59:Manifest2 哈希策略和安全隐患 (https://gentoolinux.cn/glep/glep-0059.html)
[5]错误 534528 - distfile 应排序到 DISTDIR 的子目录中 (https://bugs.gentoo.org/534528)
[6]Michał Górny。“[pre-GLEP] 拆分 distfile 镜像目录结构”。gentoo-dev 邮件列表,2018-01-26,邮件 ID 1517009079.31015.3.camel@gentoo.org (https://archives.gentoo.org/gentoo-dev/message/cfc4f8595df2edf9a25ba9ecae2463ba)
[7]Andrew Barchuk 回复“使用为每个目录计算的字符范围,以便文件均匀分布”,gentoo-dev 邮件列表,2018-01-28,邮件 ID 1517172228.2114973.1251027256.0A9C8F3C@webmail.messagingengine.com (https://archives.gentoo.org/gentoo-dev/message/611bdaa76be049c1d650e8995748e7b8)
[8]Jason Zaman 的回复,包括“使用与软件包本身相同的目录布局”,gentoo-dev 邮件列表,2018-01-28,邮件 ID 20180128070111.GA17078@meriadoc.perfinion.com (https://archives.gentoo.org/gentoo-dev/message/f26ed870c3a6d4ecf69a821723642975)