perf: select the covering index for folder counts

This commit is contained in:
Codex committed 2026-11-11 10:04:03 +00:00
1 parent a02568e265
commit 6a230f73b9
2 files changed
+47 -5

No files matched your search

+14 -2
View File
@@ -22,7 +22,7 @@ export async function folderCounts(repo: Repository, requestedPath: unknown): Pr
const prefix = originalPath ? originalPath + "/" : "";
const escapedPrefix = prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const childDepth = originalPath ? originalPath.split("/").length : 0;
const rows = await FileModel.aggregate<{ _id: string; count: number }>([
const aggregate = () => FileModel.aggregate<{ _id: string; count: number }>([
{ $match: {
repoId: repo.repoId,
treeGeneration: repo.model.treeGeneration || { $exists: false },
@@ -35,7 +35,19 @@ export async function folderCounts(repo: Repository, requestedPath: unknown): Pr
{ $arrayElemAt: [{ $split: ["$path", "/"] }, childDepth] },
] } : { $arrayElemAt: [{ $split: ["$path", "/"] }, 0] }, count: { $sum: 1 } } },
{ $limit: FILE_LISTING_LIMIT + 1 },
]).option({ maxTimeMS: 2000, allowDiskUse: true }).exec();
]).option({ maxTimeMS: 2000, allowDiskUse: true });
let rows: { _id: string; count: number }[];
try {
// The short planner trial can favor the size-first index despite its
// document fetches. Legacy snapshots still use the default planner.
rows = await aggregate().option(repo.model.treeGeneration ? { hint: "file_counts_covering" } : {}).exec();
} catch (error) {
// During an additive rollout the index may not be ready yet. Retry only
// the missing-hint planning error, never timeouts or execution failures.
if (!repo.model.treeGeneration || !(error instanceof Error) || !("code" in error) || error.code !== 2 ||
!/hint provided does not correspond to an existing index/.test(error.message || "")) throw error;
rows = await aggregate().exec();
}
if (rows.length > FILE_LISTING_LIMIT) throw new AnonymousError("file_listing_too_large", { httpStatus: 413 });
const counts: Record<string, number> = Object.create(null);
const compiled = compileTerms(repo.options.terms || []);
+33 -3
View File
@@ -17,6 +17,22 @@ describe("folder count request validation", () => {
expect(error.message).to.equal("invalid_path"); expect(error.httpStatus).to.equal(400);
});
}
for (const error of [Object.assign(Error("operation exceeded time limit"), { code: 50 }),
Object.assign(Error("unrelated bad value"), { code: 2 })]) {
it(`does not retry ${error.message}`, async () => {
const original = File.aggregate; let calls = 0;
File.aggregate = () => {
calls++;
return { option() { return this; }, exec: async () => { throw error; } };
};
try {
const caught = await folderCounts({ repoId: "fixture", model: { treeGeneration: "active" } }, "")
.then(() => null, error => error);
expect(caught).to.equal(error); expect(calls).to.equal(1);
} finally { File.aggregate = original; }
});
}
});
(uri ? describe : describe.skip)("scoped folder counts with MongoDB", function () {
@@ -50,17 +66,31 @@ describe("folder count request validation", () => {
await insert(Array.from({ length: 2000 }, (_, i) => ({ name: `file${i}`, path: `src/folder${i % 20}`, size: i % 2 })));
await insert([{ name: "excluded-directory", path: "" },
{ name: "old", path: "old", size: 1, treeGeneration: "retired" }]);
let pipeline;
let pipeline, query;
const aggregate = File.aggregate;
File.aggregate = function (stages, ...args) { pipeline = stages; return aggregate.call(this, stages, ...args); };
File.aggregate = function (stages, ...args) {
pipeline = stages; query = aggregate.call(this, stages, ...args); return query;
};
try { expect(await folderCounts(repo, "")).to.deep.equal({ "": 2000, src: 2000 }); }
finally { File.aggregate = aggregate; }
const explained = await File.aggregate(pipeline).option({ hint: "file_counts_covering" }).explain("executionStats");
expect(query.options.hint).to.equal("file_counts_covering");
const explained = await File.aggregate(pipeline).option(query.options).explain("executionStats");
const cursor = explained.stages?.find(stage => stage.$cursor)?.$cursor || explained;
expect(cursor.executionStats.totalDocsExamined).to.equal(0);
expect(cursor.executionStats.totalKeysExamined).to.be.at.least(2000);
});
it("falls back before the covering index exists and uses it after creation", async () => {
await insert([{ name: "one", path: "src", size: 1 }]);
await File.collection.dropIndex("file_counts_covering");
try {
expect(await folderCounts(repo, "")).to.deep.equal({ "": 1, src: 1 });
} finally {
await File.collection.createIndex({ repoId: 1, treeGeneration: 1, path: 1, size: 1 }, { name: "file_counts_covering" });
}
expect(await folderCounts(repo, "")).to.deep.equal({ "": 1, src: 1 });
});
it("preserves counts for legacy repositories without a generation", async () => {
repo.model.treeGeneration = undefined; await repo.model.save();
await File.collection.insertMany([