+ )}
+ >
)}
);
diff --git a/src/lib/services/nitro-cleanup.ts b/src/lib/services/nitro-cleanup.ts
index 5fda1c97..2efe4321 100644
--- a/src/lib/services/nitro-cleanup.ts
+++ b/src/lib/services/nitro-cleanup.ts
@@ -217,14 +217,21 @@ interface DirectoryRead {
lastModified: number;
}
+/**
+ * How many `stat` calls run in parallel while reading an asset directory.
+ * Directories can hold hundreds of thousands of files; serial `stat`ing them
+ * is the single biggest cost of the scan, so the work is spread over a pool.
+ */
+const DIR_STAT_CONCURRENCY = 32;
+
async function readAssetDir(
dir: string,
regex: RegExp,
): Promise {
const names = await fs.readdir(dir).catch(() => [] as string[]);
const entries: DirectoryRead[] = [];
- for (const name of names) {
- if (!regex.test(name)) continue;
+ await runPool(names, DIR_STAT_CONCURRENCY, async (name) => {
+ if (!regex.test(name)) return;
let size = 0;
let mtimeMs = 0;
try {
@@ -240,7 +247,7 @@ async function readAssetDir(
size,
lastModified: mtimeMs,
});
- }
+ });
return entries;
}
@@ -321,7 +328,7 @@ export async function scanFakeBrokenNitros(): Promise {
const fake: NitroCleanupEntry[] = [];
const broken: NitroCleanupEntry[] = [];
- await runPool(entries, 8, async (entry) => {
+ await runPool(entries, 16, async (entry) => {
// A .nitro that no DB item maps to is a leftover / fake bundle.
if (!validStems.has(entry.base.toLowerCase())) {
fake.push(entry);