Contents

Sandbox 存储性能优化:NAS 选型与本地盘构建

一个新项目还没有开始修改代码,准备过程就先等了六七秒。拆开日志以后发现,其中接近一半时间花在初始 Git 推送上。

2026 年 4 月 6 日,测试环境两次新项目准备的平均耗时是 6.66 秒,负责推送的 push_remote 步骤平均用了 2.90 秒,占约 43.6%。沿着这次推送继续看,Gitea 服务端的仓库目录挂在 NAS 上,文件存储由此进入了排查范围。

后面我们又测了不同文件系统上的依赖安装和构建,遇到了“包已经缓存,安装仍然很慢”的问题。这篇文章从 Git push 开始,沿着这些测量与实现,讲到 NAS 选型,以及本地盘上的依赖和编译。

1. 先从一次 Git push 看起

1.1. 新项目为什么先要推送代码

当时的新项目初始化流程,会先把模板解压到 Sandbox,创建本地 Git 仓库并提交初始代码,再推送到 Gitea。Gitea 是保存项目 Git 仓库的服务,后续可以从这里取得已经提交的版本。只有这套准备流程完成,项目才能继续使用。

我们在预热流程,也就是 warmup 中记录了各步骤耗时。下面两条是 2026 年 4 月 6 日测试环境的成功样本,均为新项目的模板初始化路径,时间单位为秒。

样本warmup 总耗时init_gitpush_remote推送步骤占比
第一次6.4370.0502.93545.6%
第二次6.8900.0472.87341.7%
平均6.6630.0492.90443.6%

占比按未四舍五入的原始时间计算,平均行使用两次推送耗时之和除以两次总耗时之和。这是两个现场样本,不代表长期平均延迟。

init_git 包含建仓、配置提交身份、git add 和 git commit,这两次只花了约 50 毫秒和 47 毫秒。随后的 push_remote 则用了约 2.9 秒,等待主要集中在后一步。

这两个步骤都从沙箱代理服务发起远程执行开始计时,直到脚本返回。push_remote 执行初始代码推送,包含远程执行开销、Git 客户端处理、网络和服务端等待;前一步的本地提交不算在里面。因此,不能把它直接当作纯网络上传时间,也不能拿新项目首次提交的结果代表任意大小仓库的提交与推送。

1.2. 推送在等待什么

Git push 会向远端发送它尚未拥有的对象,并请求更新分支引用。服务端需要接收相关对象,检查这次更新是否允许,再让目标分支指向新的提交。Git push、服务端 receive-pack

因此,客户端在等待推送结果时,等待的内容还包括服务端对仓库的处理。如果仓库保存在网络文件系统上,这条路径就涉及服务端的文件操作。

当时检查 Gitea 部署配置,确认 /data 挂载了 NAS,其中保存 Gitea 的仓库数据。这条线索把存储纳入了排查,但还不能把那 2.90 秒全部归因于 NAS:我们没有在这两个样本中继续测出网络、客户端和服务端 I/O 各占多少。

接下来需要区分两个存储位置:Gitea 保存已经提交的 Git 版本,Sandbox 工作区保存当前正在修改的工程文件。 它们都可以使用 NAS,但访问发生在不同位置,优化其中一处,不会自动改变另一处。

1.3. 工程工作区也需要持久保存

Agent 在 Sandbox 中修改代码、安装依赖,再执行构建。Sandbox 是承载这些操作的隔离执行环境,里面有项目目录、Node.js 和构建工具。用户下一次回来继续修改时,执行任务的容器可能已经换了一个。

源码需要跨实例保存。例如,Agent 给游戏增加了暂停菜单,修改了 src/menu.ts,还增加了一个依赖。容器回收以后,这次修改和对应的依赖版本都需要保留下来。

后来的 NAS 工作区方案,把网络文件存储通过 NFS 协议挂载到 Sandbox 中。挂载以后,程序仍然通过普通目录访问它。新容器挂载同一个项目目录,就可以继续处理原来的工程,包括尚未提交到 Git 的修改。

Git 推送经过 Gitea 服务端访问仓库存储;NAS 工程工作区则由先后创建的 Sandbox 直接挂载,两处存储分别承载版本和当前文件
图 1:Git 仓库存储与工程工作区是两个位置,需要分别观察

这个布局很直接:源码、package.json、锁文件、node_modules 和 dist 都在同一个工程目录里,现有工具基本不用调整。持久化的问题解决了,安装和构建也随之使用了网络文件系统。

读取一个依赖文件,可能先要查找目录项、确认权限、读取文件属性,然后才是内容。客户端会缓存一部分信息;缓存未命中或需要向服务端确认时,这些操作就要经过网络。对于包含大量小文件的 node_modules,文件总量不大,也可能有很多次访问。

1.4. 第一个方案:换一种文件系统

既然安装和构建都在 NAS 上,首先可以尝试改善这条存储路径。这样不需要调整工程目录,也不用让包管理器适应新的布局。

阿里云的 NAS 有多种规格。按目前的产品划分,通用型包含容量型、高级型和性能型,极速型又分标准型与高级型。各类产品提供的容量、时延、吞吐和 IOPS 规格不同,选型需要结合项目的实际操作。通用型 NAS、极速型 NAS

类型主要特点对这个问题需要核对什么
通用容量型 NAS面向容量与成本较敏感的共享文件场景小文件时延、实际使用容量对应的吞吐
通用高级型、性能型 NAS提供不同的时延、吞吐和 IOPS 规格单个项目能否利用这些能力,安装与构建是否改善
极速型 NAS面向较低时延和较高 IOPS 的文件访问当前地域、规格和计算平台的挂载支持
CPFS并行文件存储,面向较大规模的并发 I/O客户端、协议服务、网络配置和接入条件

CPFS 也进入过我们的测试范围。不过,文件系统的性能只是选型的一部分,还要看 Sandbox 能否接入。当时 Sandbox 使用函数计算 FC,可以配置 NAS 挂载;CPFS 实验则需要先创建协议服务与导出目录,再在 ECS 上安装 CPFS-NFS 客户端并挂载导出的路径。接通 ECS,并不等于可以直接把 FC 的 NAS 地址换成 CPFS。FC 存储选型、CPFS-NFS 挂载说明

接下来需要回答的,就是换存储到底能改善多少。连续读写大文件的吞吐,不能直接换算成安装几万个依赖文件需要的时间,因此我们拿已有的前端模板执行安装和构建。

2. 换了文件系统,构建快了多少

2026 年 6 月 12 日,我们用四种前端模板做了两轮测试,表中分别记为 2D、3D、Empty3D(空的 3D 工程)、Canvas。以下时间均为秒。

这一轮测的是工程目录中的 npm install 和 npm run build,与前面的初始 Git 推送是不同负载。现有记录没有 Git push 在各文件系统上的同口径对照,下面的数据只用于比较安装和构建。命令也保留当时的 npm 口径,不改写成后来的 pnpm 生产流程。

2.1. 第一轮:容量型反而安装得更快

NAS 测试使用新加坡区域的 ecs.c9i.xlarge,4 核 8 GiB,Node.js 22.16.0、npm 10.9.2。每个项目先安装依赖,再连续构建三次。CPFS 随后补跑,也使用 Node.js 22.16.0、npm 10.9.2。

下面是第一轮记录。这轮 NAS 测试共用了 ECS 本地的 npm 缓存,性能型先跑,容量型后跑,安装条件并不完全一致。

项目文件系统安装首次 build三次 build 中位数安装+首次 build
2DNAS 性能型29.9245.8404.02235.764
2DNAS 容量型16.9115.4423.84522.353
2DCPFS22.8813.8043.50726.685
3DNAS 性能型14.3412.8801.99317.221
3DNAS 容量型8.8752.7181.92811.593
3DCPFS11.1051.9381.78413.043
Empty3DNAS 性能型5.7231.4421.2597.165
Empty3DNAS 容量型5.4731.4221.2036.895
Empty3DCPFS3.0801.1901.1404.270
CanvasNAS 性能型1.3011.2980.9822.599
CanvasNAS 容量型1.2601.4380.9722.698
CanvasCPFS0.8520.7250.6201.577

2D 的结果最明显:容量型安装用了 16.911 秒,性能型用了 29.924 秒。检查测试过程后,发现 npm 缓存保存在 ECS 的 /root/.npm,后测的一组能够复用前面下载过的内容。

因此,这两行数字不能直接证明容量型更快。缓存是已经确认的干扰因素,但没有单独控制它做实验,也无法判断这约 13 秒的差异有多少由缓存造成。CPFS 的补跑同样没有满足后来要求的完整隔离条件。

构建数据也需要分开看。两个 NAS 的中位数比较接近,例如 3D 是 1.993 秒和 1.928 秒,Canvas 是 0.982 秒和 0.972 秒。每组只有三次连续执行,还不足以作稳定的性能排名;首次 build 紧接着安装,也不等于清空文件缓存后的冷读测试。

这轮先暴露出来的是测法的问题。要继续比较文件系统,得让每一组从独立环境开始。

2.2. 第二轮:隔离环境后再测

第二轮改成“文件系统类型 × 项目”各用一台新 ECS、一套新文件系统,避免共用 npm 缓存。规格也调整为更接近当时 Sandbox 的 2 核 4 GiB:ecs.c9i.large、Ubuntu 20.04、Node.js 22.22.3、npm 10.9.8,NAS 使用 NFSv3 挂载。

CPU 和软件版本与第一轮不同,所以这一轮内部比较,不能拿两轮数字计算升级收益。

项目文件系统安装build 1 / 2 / 3build 中位数安装+首次 build
2DNAS 容量型31.935.66 / 5.05 / 4.945.0537.59
2DNAS 性能型39.645.69 / 7.50 / 5.155.6945.33
3DNAS 容量型21.732.91 / 2.56 / 2.592.5924.64
3DNAS 性能型21.632.84 / 2.51 / 2.462.5124.47
CanvasNAS 容量型1.441.28 / 0.96 / 0.920.962.72
CanvasNAS 性能型1.161.16 / 0.89 / 0.870.892.32

有些组合没有跑完:Empty3D 在两种 NAS 上都遇到 npm install 的 ERESOLVE 依赖解析错误,项目本身声明使用 pnpm,直接以 npm 安装并不完全符合原来的依赖约束;CPFS 独立组合则停在资源与挂载准备阶段。它们没有有效构建时间,也不能用第一轮的 CPFS 数字补上。

极速型 NAS 没有参加这两轮测试。全部阶段记录,包括三次 build 和失败退出码,保留在测试数据 CSV 中。

2.3. 安装仍然占了大头

这六组成功结果,没有显示出换成性能型 NAS 后各个项目都明显变快。更值得继续追的是时间分布:2D 在容量型上的安装占“安装+首次构建”的约 85%,3D 对应约 88%。

即使只看 2D,把 5.66 秒的编译继续压缩,前面的 31.93 秒安装仍然存在。要缩短用户等待的时间,需要继续拆解依赖准备。

安装依赖会下载包,因此一个直接的方案是预热包缓存,尽量离线安装。我们后来的镜像已经这样做了,但工作区中的 node_modules 仍在 NAS 上。接下来的问题变成了:包内容已经在本地,安装还需要做哪些文件操作?

3. 包已经缓存了,安装还在做什么

3.1. 缓存与依赖树是两个位置

安装大致包含读取包与锁文件、获取包内容、建立项目依赖树,以及执行安装脚本几个过程。缓存能够复用包内容,项目自己的依赖目录仍然需要准备。

以 pnpm 为例,需要区分三个位置:

  • 内容存储 store:保存可以复用的包文件;
  • 项目的 node_modules/.pnpm:项目使用的依赖文件和目录布局;
  • node_modules 中的链接与入口:让程序按照依赖关系找到模块。

pnpm 使用内容存储和链接组织依赖。建立和读取项目依赖树时,仍然要操作项目目录;store 与项目分属不同文件系统时,也不能假定跨文件系统硬链接总能成立。pnpm 依赖目录结构

本地包缓存提供包内容,NAS 上的 node_modules 仍然承担目录创建、属性查询和构建读取
图 2:缓存命中以后,NAS 上的依赖目录仍有文件操作

8 月检查构建链路时,镜像里的预热 store 已经在本地盘,安装器也支持优先离线安装。但 node_modules 和 .pnpm 还在 NAS。包可以不重新下载,目录创建、文件复制、链接与属性检查仍可能经过网络文件系统。

构建阶段也会反复查找模块、读取 package.json、解析源码,再生成临时文件和 dist。依赖留在 NAS 上时,读取依赖的成本会继续影响构建,只调整输出目录还不够。

3.2. 连查看目录也变慢了

2026 年 8 月 8 日的一次排查,进一步把范围指向了文件访问。工作区确认是 NFSv3 挂载,现场记录中,find 用了约 117 秒,git status 约 166 秒,部分目录扫描超过 60 秒。

这些命令不负责把源码编译成产物。它们也变慢,说明需要检查安装和编译之外共同使用的文件系统路径。

约 42 分钟的现场累计统计中,有下面这些 NFS 请求:

操作次数含义
SETATTR42,110设置权限、时间等文件属性
LOOKUP22,871查找目录中的文件或子目录
ACCESS21,662检查访问权限
CREATE11,362创建文件
WRITE11,583写入文件内容

这些是多个操作累计的计数,不是一次安装的调用次数。不过,它们解释了为什么不能只看传输带宽:查目录、检查权限、创建文件,每一步都可能有等待。依赖文件多了,即使单个文件很小,这些步骤也要反复执行。

现场同时存在外部包下载慢的问题,手工执行的 pnpm 命令还曾把 store 建在 NAS 上。下载、缓存和项目目录需要分别检查,已有证据不足以把所有等待都算在 NAS 上。但对于已经确认留在 NAS 的依赖树,可以提出一个更具体的改法:把它移到本地。

4. 先把 node_modules 搬到本地

4.1. 哪些文件可以不持久保存

源码、素材和依赖约束需要保留;node_modules 则可以根据 package.json、锁文件和工具版本重新生成。它被频繁读写,却不一定需要和源码使用同一种存储。

因此,可以让 NAS 继续保存工程,把 node_modules 放到 Sandbox 本地盘。工作区里的 node_modules 变成软链接,工具访问这个路径时,实际读取本地目录。

本地盘的选择还取决于部署环境:有独立 SSD 时,可以把依赖目录放在 SSD 对应的挂载点;只有容器本地临时盘时,也可以验证同样的分工。两者的容量、性能和生命周期需要按实际环境确认,目录叫 /tmp 并不能说明底层一定是 SSD。

4.2. 实验结果:安装改善大,构建改善小

我们在 8 月评审过《低权限环境下 node_modules 本地快盘托管方案》,附录保留了一组 2026 年 6 月的实验。它先在本地目录安装依赖,再通过软链接让工作区使用这份 node_modules。

原实验中,/workspace 和 /scratch 是同一个 NFSv3 文件系统,/tmp 是约 9.9 GiB 的容器本地 overlay,没有独立本地 SSD。下面比较的是 NFS 路径与容器本地路径。

操作与规模NFS 直接使用本地目录+软链接两者耗时比
安装 express 等,70 多个依赖5.3 秒1.3 秒4.08
安装 91 个依赖,9,164 个文件54.4 秒7.5 秒7.25
安装 431 个依赖,22,302 个文件166 秒31 秒5.35
431 个依赖项目的 Vite build6.05 秒4.29 秒1.41
431 个依赖的安装耗时为 NFS 166 秒、本地 31 秒;构建耗时为 6.05 秒与 4.29 秒,两个子图独立刻度
图 3:同一份补充记录中,安装和构建的变化幅度不同

在 431 个依赖的记录里,安装从 166 秒降到 31 秒;Vite build 从 6.05 秒降到 4.29 秒。移动依赖目录影响了两部分,安装阶段的变化更大。构建还包含解析、转换、压缩等 CPU 工作,不能用安装的耗时比代表整个发布流程的收益。

这份附录没有保留完整的 CPU 规格、缓存控制和重复轮数,属于方案验证资料,也不是前面几个模板的同口径复测。它提供了继续实现本地依赖目录的依据,生产项目的具体收益还需要重新测量。原记录见方案文档的实机验证附录,revision 7。

4.3. 软链接又被安装器删掉了

有了这组结果,接下来的工作是把目录布局接入现有工具。最先遇到的限制,就在依赖安装本身。

假设目录是这样的:

/workspace/game/node_modules
    → /local/game/project/node_modules

软链接保存目标路径,但包管理器不一定保留它。补充实验中的 npm 11 在安装的 reify 阶段发现 node_modules 是软链接,会移除它,再在原位置创建目录。于是,看起来已经搬到本地的依赖,又写回了 NFS。npm ci 也明确包含先清理既有 node_modules 的行为。npm ci 的行为说明

要保留本地布局,安装命令应当直接在本地工程目录执行,成功以后再由平台建立工作区链接。处理已有依赖目录前,还需要确认它属于可重建数据,并停止正在使用它的进程。

构建工具有另一类限制:部分框架检查工程根目录与依赖的实际路径,外置依赖可能触发“超出文件系统根目录”的错误。只挪 node_modules,源码和依赖仍分处两个工程根,这套结构并不一定适合所有工具。

因此,下一步把源码副本也放进本地,让源码、依赖和构建命令位于同一个工程根。NAS 继续保留原工程,本地副本负责执行这一轮构建。

5. 在本地准备一个完整工程

5.1. 两份目录如何分工

本地工程副本需要源码,但不负责替代 NAS 保存用户修改。每次构建前,从 NAS 同步本轮输入;依赖安装、临时文件和编译都在本地完成;构建成功后,再把产物交回持久层。

NAS 保存源码、锁文件、Git 与状态,本地工程副本保存源码镜像、node_modules、缓存和构建输出,成功产物回写 NAS
图 4:NAS 保存持久工程,本地工程副本负责安装与编译,路径经过简化
内容主要保存位置用途
源码、素材、package.json、锁文件NAS保存用户修改和下一次安装的依赖约束
Git 元数据、项目运行记录NAS供提交、恢复和查询使用
本轮源码与配置的副本本地工程目录提供一致的构建输入与相对路径
npm cache、pnpm store本地盘或镜像内的预热目录复用包内容,缺失时可以重新获取
node_modules、编译缓存、构建中的 dist本地工程目录承接安装与编译中的反复读写
成功后的 dist回写 NAS,再进入原发布流程交给上传与版本管理

同步时排除 NAS 上的旧 node_modules、旧 dist 和无关运行目录,避免把大量可重建文件复制回来。源码中的删除也要同步,否则已经从 NAS 删除的旧文件,可能继续留在本地参与编译。

如果一个 Sandbox 承载多个项目,本地目录还要按项目隔离,防止它们修改同一份依赖。整个本地副本的生命周期跟随当前实例,实例回收后可以重建。

5.2. 一个 npm 项目怎样执行

下面用 npm 展示最基本的过程。假设项目已有有效的 package-lock.json,构建期间没有并发修改,输出目录是 dist。本地目录每次新建,暂不考虑缓存复用。

workspace=/workspace/game
local_root=$(mktemp -d /tmp/game-build.XXXXXX)
local_project="$local_root/project"
mkdir -p "$local_project"

# 同步构建输入,跳过可以重新生成的内容。
rsync -a \
  --exclude='/.git' \
  --exclude='/node_modules' \
  --exclude='/dist' \
  --exclude='/.combos' \
  "$workspace/" "$local_project/"

# 安装和构建都从本地工程根开始。
(
  set -e
  cd "$local_project"
  npm ci --cache "$local_root/npm-cache"
  npm run build
)

产物出现在 $local_project/dist。平台检查命令退出状态与产物后,再把它交回持久层。这个示例说明目录关系,没有包含并发锁、缓存复用和发布过程。

已有项目使用 pnpm 时,应保留原来的锁文件与版本。采用本地目录,并不需要改用 npm 重新解析依赖。我们实际的主路径使用 sandbox-pnpm-install,先尝试离线安装,缺包时按策略联网补齐。

monorepo、本地 file: 依赖,以及会访问相邻目录的安装脚本,还要求同步完整的依赖范围。复制工程时需要保持这些相对关系,不能只带走一份 package.json。

5.3. 接入原来的构建入口

实现时,我们把本地 I/O 接入了依赖安装和构建两个统一入口,分别由 sandbox-pnpm-install 和 sandbox-build 负责,已有的持久工作区与后续发布流程继续使用。

依赖准备先检查本地目录是否可用、空间是否足够,再同步输入和安装。成功后,将工作区的 node_modules 链接到本地依赖目录。构建入口核对依赖状态,刷新源码,然后从本地工程根执行原来的 build.sh。

构建入口依次取得锁、同步 NAS 源码、准备本地依赖、执行编译,将成功产物写入 NAS 暂存目录并交给发布流程
图 5:统一入口管理构建输入、本地执行和产物交接

部分旧项目的 build.sh 会再次执行 npm ci 或 pnpm install。统一入口已经准备过依赖,如果脚本再来一遍,就会重复安装。因此,当时为这次构建进程加入命令包装,跳过约定中的重复安装命令;run build 等其他调用继续交给原来的包管理器。这项兼容处理限定在已有构建协议内。

编译成功后,先把本地 dist 复制到 NAS 的暂存目录。复制完成,再将它重命名为本次产物目录,后续上传、Git 提交和版本记录继续走原流程。

这里要分别处理编译失败和回写失败。编译失败,本轮没有可上传的结果;回写失败,即使本地编译已经完成,也还没有把产物交给发布流程。构建入口会清除旧输出,防止上传器在本轮失败后误用上一次的 dist。

暂存目录让消费者不会读到复制了一半的产物,但“编译、上传、版本发布”仍然是多个步骤,并没有组成一个文件系统事务。最终预览还要等后续发布完成。

5.4. 哪些修改需要写回

本地副本带来了一个新的边界:在本地改了文件,不代表用户的持久工程已经变化。

普通构建根据现有锁文件准备依赖。用户要求增加或删除依赖时,package.json 与锁文件的改动需要保存到 NAS,成为下次恢复的输入;只更新本地 node_modules,换一个实例就会丢失这次变化。

因此,修改依赖要作为单独的操作处理,检查并写回对应文件;写回前还应确认原文件没有被其他操作更新。构建脚本如果会生成需要长期保存的源码,也要约定这些文件的写回范围,不能将整个本地副本直接覆盖回 NAS。

到这里,安装和编译已经有了统一的本地执行路径,NAS 上保留的是持久输入与完成后的产物。但还有一个条件需要处理:本地目录只属于当前实例。用户下一次继续工作时,NAS 上的成功记录可能还在,本地文件却已经不在了。

6. 实例换了,依赖怎样恢复

6.1. 成功记录与真实目录

假设原实例已经完成依赖安装,NAS 保存了成功标记。原实例被回收后,新实例挂载同一份 NAS,可以看到旧记录,但没有原实例的本地依赖。

原实例回收后,NAS 仍保存源码和旧标记;新实例检查本地文件、重建依赖并更新链接和就绪标记
图 6:旧实例的成功记录,不能代替新实例中的依赖目录

软链接也可能保存在 NAS 上。容器更换后,链接文件仍在,它指向的本地目录却不存在。只检查链接或“安装成功”状态,都会漏掉这个情况。

实际实现判断依赖是否就绪时,会一起核对记录是否属于当前实例、package.json 与锁文件的摘要、本地依赖目录、pnpm 布局标记,以及软链接是否指向当前目录。条件不满足,就重新建立本地副本、安装依赖、修复链接,再更新当前实例的记录。

Node.js 与包管理器版本也需要固定。如果以后把依赖缓存扩展到跨实例共享,还要考虑操作系统、CPU 架构、Node ABI 等兼容条件,尤其是包含原生扩展的包。当前实例内的一份成功记录,不能直接扩展成跨机器复用的依据。

6.2. 恢复过程还会并发执行

异步预热和用户触发的构建可能同时准备依赖。如果只给安装加锁,两个进程仍可能在前面的同步阶段相互干扰:一个正在准备本地布局,另一个同时删除或替换其中的文件。

为此,锁需要覆盖共享目录的准备过程,让源码同步、依赖安装和状态更新按照约定执行。

本地环境不可用时,还需要回到原来的 NAS 工作区。回退不能只切换工作目录:如果 node_modules 仍是指向已消失本地目录的链接,继续安装可能出现 ENOTDIR。因此,回退时还要处理失效链接,让依赖重新安装到可用的工作区目录中。

情况处理方式
本地目录不可写、空间不足或布局不兼容记录原因,按策略回到工作区路径
同一实例已因本地环境问题回退保留该实例的回退结果,避免反复尝试同一条失败路径
TypeScript 报错、源码语法错误返回编译错误,由调用方修复源码
实例已重建重新检查目录和依赖,允许新环境再次尝试本地模式

长时间保留实例时,还要限制缓存占用。检查容量也需要关注可用 inode:剩余字节足够,并不保证还能创建大量文件。

7. 下一步:重新测量完整构建

回看已有数据,可以确认两件事:当时测过的容量型与性能型 NAS 没有给所有模板带来一致的明显改善;补充实验把依赖放到本地后,安装耗时变化远大于构建耗时变化。沿着这个方向,我们把本地执行接入了统一安装、构建与恢复流程。

不过,现有测量还缺少最终生产链路在同一条件下的完整前后对照。前面 166 秒降到 31 秒的数字,不能直接作为这套完整方案的上线收益。

本地副本减少了安装与编译中的网络文件操作,也增加了源码同步和产物回写。因此,下一轮计时需要覆盖:等待执行、同步输入、依赖准备、实际编译、产物回写,以及后续上传和验证。否则,单看本地 build 变快,仍然无法判断用户少等了多久。

测试场景也要分开:新实例首次构建、同一实例重复构建、只修改源码、修改依赖,以及实例丢失后的恢复。每组固定代码、锁文件、工具版本和计算规格,记录缓存条件,核对最终发布的产物。

这样才能继续回答下一个问题:依赖和编译移到本地以后,整条链路中最长的等待又落在了哪里。