ADAMANT IPFS Node v0.1.0 引入了面向生产环境的存储生命周期管理,能够限制磁盘增长、清理失败的上传任务、区分持久化内容与可回收缓存、在 ADAMANT 节点集之间进行确定性副本放置、修复缺失副本,并在升级过程中完整保留所有既有的 CID。
为什么需要此更新
在之前的实现中,数据块在请求限制条件明确前就可能被写入块存储(blockstore)。中断或被拒绝的上传任务可能会残留数据块,成功的上传任务则会被永久固定且缺乏过期策略,此外还缺乏明确的复制仲裁或修复流程。这导致我们无法可靠地回答以下问题:上传任务会消耗多少磁盘空间、哪些文件是持久的或可回收的、如果请求在导入过程中断开连接会发生什么、哪些节点负责特定的 CID、节点在不删除已确认内容的情况下能否回收空间,以及集群升级后现有文件是否依然可用。
准入先于存储
上传任务在消耗无限制的磁盘空间之前就会被拒绝。并发上传受到 storage.maxConcurrentUploads 的限制(返回 429)。聚合请求大小受 storage.maxRequestSizeBytes (413) 限制,该限制同时针对 Content-Length 和实际流式传输的字节数进行校验,因为分块请求不会声明最终大小。由 storage.diskReserveBytes 强制执行的磁盘预留机制会在可用空间不足时返回 507。每个请求的文件数受 maxFileCount (400) 限制,单个文件大小受 uploadLimitSizeBytes (400) 限制。并发请求会原子化地预留磁盘空间,因此多个上传任务不会重复占用相同的可用空间额度。
每个请求都拥有一个上传会话,用于跟踪其创建的数据块。解析器拒绝、导入失败、路由错误、严格仲裁失败或客户端断开连接只会移除那些新创建的数据块。预先存在的数据块、被其他并发上传任务保留的数据块以及已固定的(pinned)数据块均会被保留。
明确的文件生命周期
/adm/files 下的数据库支持注册表记录了每个已知 CID 的生命周期和存储统计信息。temporary 状态表示正在等待确认或事务结算的上传任务。confirmed 表示受策略保护的持久化内容。expired 表示已被释放并可在存储压力下被回收的内容。pinned 和 heldLocally 与逻辑状态分开跟踪。
生命周期转换、固定操作、注册表写入、上传清理、副本结算、修复和垃圾回收均通过基于 CID 的锁和存储范围的回收租约进行协调。故障补偿机制会将固定状态和注册表记录恢复至观察到的基准,而不是使其处于矛盾状态。
默认的 storage.confirmationRequired: false 保持了现有的 API 契约,即上传内容立即变为持久化状态。启用确认机制的部署将为废弃的上传任务提供可配置的 TTL(生存时间),并必须调用经过身份验证的确认端点。
基于压力的生命周期感知型垃圾回收
释放固定状态与删除数据块是有意区分开的两个决策。已释放的文件会保留在块存储中,并可继续免费提供读取服务。只有当块存储超过配置的高水位线或文件系统进入磁盘预留状态时,数据块才会被删除。这避免了丢弃有用的缓存后又不得不重新获取的情况。
垃圾回收器具有多项安全属性。本节点持有的已确认内容绝不会被选中进行驱逐。在开始任何删除操作之前,会先修复已确认文件缺失的保护机制。如果某次运行无法验证持久化内容,则会在执行任何破坏性操作前中止。部分 GC 失败会保留注册表记录,以便下一次扫描可以安全重试。干运行(dry-run)模式会在不更改固定状态或数据块的情况下报告精确的释放和保留计划。计划任务的扫描是受限且递进的,而不是重复扫描整个注册表。
记录的默认值为:高水位线 50 GiB,低水位线 40 GiB,磁盘预留 5 GiB,每 15 分钟执行一次计划扫描。所有值均可配置。计划 GC 默认启用,但在空间高于安全阈值时不会执行任何删除操作。操作员可以使用以下命令检查计划:
bash
curl —fail-with-body
-X POST
-H “x-api-key: $ADMIN_API_KEY”
“https://ipfs.example.org/api/storage/gc?dryRun=true”
基于现有 libp2p 网络的复制
复制通过 /adamant/replication/1.0.0 运行,而不是通过额外的 HTTP 服务。libp2p 握手可证明远程对等节点的身份,因此复制不需要共享 API 密钥、第二个公共端口或单独的集群守护进程。只有在 nodes 列表中列出的对等节点发起的、使本节点负责内容的请求才会被接受。控制消息经过长度帧处理且受到限制。副本事务会记录其源对等节点,且只有该对等节点可以结算它们。
持有者通过 CID 的汇合哈希(rendezvous hashing)进行选择。每个拥有相同成员列表的节点无需中央协调器即可独立计算出相同的持有者集合。默认的放置策略是:新内容保留 4 个副本,180 天后保留 3 个副本,一年后保留 2 个副本。副本数量受实际网络规模限制,因此如果一个 3 节点网络被要求保留 4 个副本,它会在每个可用节点上放置一个副本。放置策略根据文件存续时间而非最后访问时间进行缩减,因为跟踪读取操作会产生关于用户何时检索文件的元数据。
严格的上传持久性是可选的。当启用 replication.requireQuorumOnUpload 时,本地准入和远程副本将形成一个支持回滚的事务:对等节点暂存副本,源节点验证配置的确认仲裁,然后提交或中止所有已准备好的副本。严格配置要求 ackQuorum >= 2,确保成功意味着至少存在一个远程副本。
修复、移交与检索
修复作业会在传输数据之前询问对等节点是否已拥有该 CID 以及是否有足够的空间。摄入过程受并发数、请求大小、磁盘预留、超时和每个对等节点预算的限制。处于当前持有者集合之外的节点会将其持久化副本移交给指定的持有者,并仅在这些持有者确认已拥有该文件后才释放自己的固定状态。如果所有远程持有者随后消失而数据块仍保留在本地,该节点将重新承担责任,而不是让最后的可恢复副本消失。
读取操作也使用放置信息。在提供 CID 服务之前,节点会直接连接到预期持有该 CID 的对等节点,而不是依赖于已经连接的 Bitswap 对等节点。周期性的对等连接(peering)确保了配置的网格在启动后保持可用。这对 ADAMANT Messenger 很重要:发送方和接收方通常使用不同的基础设施节点,因此接收方的首次读取通常会落在一个非指定持有者的节点上。
现有文件与 CID 的保留
升级过程不会重新导入、重写或重命名已存储的内容。CID 生成方式与之前的架构保持兼容,因此现有的消息链接将继续指向相同的文件。在启动时,生命周期注册表之前的固定记录会被回填为已确认记录。它们的 DAG 大小会在离线状态下测量,不完整的内容会被上报,而不是被静默注册为持久化状态。API 可以在后台回填继续进行的同时启动。
有一个容量影响非常重要:无法恢复旧版固定记录的原始上传时间,因此回填的文件最初被视为“新鲜”内容,并进入最广泛的放置层级。操作员应为现有内容集规划集群容量,而不仅仅是为未来的上传任务规划。修复进程以受限的递进批次处理该内容集,而不是试图一次性复制所有内容。
目前仅提供 /adamant/replication/1.0.0,因此此版本旨在用于协调一致的集群范围升级。GET /api/storage/metrics 会公开活跃的协议版本,从而使混合部署可见。
操作可见性与访问边界
公共只读路由公开容量和生命周期状态,但不包含文件名、CID 清单或对等拓扑:GET /api/file/:cid/status、GET /api/storage/metrics 和 GET /api/storage/policy。管理性变更操作(如确认、释放、按需 GC、修复、固定管理和 libp2p 拓扑操作)需要配置 x-api-key。
存储报告包含已固定和可回收的字节数、文件系统可用性、预留和可用容量、生命周期计数、暂存的副本事务、作业状态和复制健康状况。它提供了足够的信息来验证升级并监控后续的收集和修复过程,而无需暴露私有的操作细节。
操作员应审查的默认设置
默认设置适用于专用存储卷,并保留了当前的即时上传行为。聚合上传大小默认为 512 MiB,并发上传为 32,磁盘预留为 5 GiB,临时 TTL 为 24 小时,GC 计划为每 15 分钟,修复计划为每 30 分钟,新鲜内容放置为 4 个副本。上传确认仲裁默认为 1(尽力而为的复制),严格上传仲裁默认禁用。每位操作员在部署前都应审查容量、水位线、成员列表和放置层级。
验证
合并后的实现通过了 232 个单元测试、102 个集成测试、生产环境 TypeScript 构建、ESLint 和 Prettier 检查、生产依赖审计以及 Semgrep SAST 和 Semgrep OSS 扫描。它还在一个四节点网络上进行了测试:16 个文件在新鲜状态时被放置在 3 个持有者上,在进入下一个层级后收敛到恰好 2 个持有者,随后在 64 次成功的跨节点读取中从所有 4 个节点读取并验证了字节一致性。未引入任何新的运行时依赖。