From 3185771081ee030fa17cdab2c14587503e50e3ec Mon Sep 17 00:00:00 2001 From: voiys Date: Mon, 9 Nov 2026 23:53:35 +0100 Subject: [PATCH] Stop re-counting pending items after every resolved item resolveEnvValues counted the keys of the pending-items object each time an item finished, which is O(n^2) in the number of items. It now keeps a counter. 3,000-item schema, load + resolve: about 245 ms to 50 ms. --- .bumpy/faster-large-schema-resolve.md | 5 +++++ packages/varlock/src/env-graph/lib/env-graph.ts | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 .bumpy/faster-large-schema-resolve.md diff --git a/.bumpy/faster-large-schema-resolve.md b/.bumpy/faster-large-schema-resolve.md new file mode 100644 index 000000000..888e1ff27 --- /dev/null +++ b/.bumpy/faster-large-schema-resolve.md @@ -0,0 +1,5 @@ +--- +"varlock": patch +--- + +Resolving a large schema no longer slows down quadratically with the number of items (3,000 items: about 245 ms to 50 ms) diff --git a/packages/varlock/src/env-graph/lib/env-graph.ts b/packages/varlock/src/env-graph/lib/env-graph.ts index c6a89a42d..c6360b1a5 100644 --- a/packages/varlock/src/env-graph/lib/env-graph.ts +++ b/packages/varlock/src/env-graph/lib/env-graph.ts @@ -933,18 +933,23 @@ export class EnvGraph { // - false = not yet started // - items are removed when completed const itemsToResolveStatus = _.fromPairs(keysToResolve.map((key) => [key, false])); + // kept as a counter, because counting the object's keys after every item is O(n^2) + let itemsRemaining = Object.keys(itemsToResolveStatus).length; // code is a bit awkward here because we are resolving items in parallel // and need to continue resolving dependent items as each finishes const deferred = new Promise((resolve, reject) => { const markItemCompleted = (itemKey: string) => { - delete itemsToResolveStatus[itemKey]; + if (itemKey in itemsToResolveStatus) { + delete itemsToResolveStatus[itemKey]; + itemsRemaining--; + } if (reverseAdjList[itemKey]) { // eslint-disable-next-line no-use-before-define reverseAdjList[itemKey].forEach((depKey) => startResolveItem(depKey)); } - if (_.keys(itemsToResolveStatus).length === 0) resolve(); + if (itemsRemaining === 0) resolve(); }; const resolveItem = async (itemKey: string) => {