mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-11-14 08:17:26 +01:00
perf: select the covering index for folder counts
This commit is contained in:
1 parent
a02568e265
commit
6a230f73b9
2 files changed
+47
-5
No files matched your search
@@ -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 || []);
|
||||
|
||||
@@ -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([
|
||||
|
||||
Reference in new issue
Block a user