From 48bd3cc66844210c45d9be79f7f7c3dd30378c68 Mon Sep 17 00:00:00 2001 From: Thanniru Sai Teja Date: Mon, 12 Oct 2026 21:43:20 -0700 Subject: [PATCH] perf(memory-graph): fix O(n^2) BFS queue drain in computeClusterAssignments (#1714) --- .../src/__tests__/graph-data-utils.test.ts | 18 ++++++++++++++++++ .../memory-graph/src/hooks/use-graph-data.ts | 6 ++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/memory-graph/src/__tests__/graph-data-utils.test.ts b/packages/memory-graph/src/__tests__/graph-data-utils.test.ts index f98df074..f29ebc71 100644 --- a/packages/memory-graph/src/__tests__/graph-data-utils.test.ts +++ b/packages/memory-graph/src/__tests__/graph-data-utils.test.ts @@ -140,6 +140,24 @@ describe("cluster assignments", () => { expect(assignments.get("a1")?.key).toBe(assignments.get("b1")?.key) }) + + it("merges a wide fan-out of memories relating to one hub into a single cluster", () => { + const hub = makeDocument("doc-hub", [makeMemory({ id: "hub" })]) + const spokes = Array.from({ length: 200 }, (_, i) => + makeDocument(`doc-${i}`, [ + makeMemory({ id: `spoke-${i}`, memoryRelations: { hub: "derives" } }), + ]), + ) + + const assignments = computeClusterAssignments([hub, ...spokes]) + + const hubKey = assignments.get("hub")?.key + expect(hubKey).toBeDefined() + for (let i = 0; i < spokes.length; i++) { + expect(assignments.get(`spoke-${i}`)?.key).toBe(hubKey) + } + expect(assignments.get("hub")?.size).toBe(spokes.length + 1) + }) }) describe("memory orbit placement", () => { diff --git a/packages/memory-graph/src/hooks/use-graph-data.ts b/packages/memory-graph/src/hooks/use-graph-data.ts index e0a3b1b3..deb59b47 100644 --- a/packages/memory-graph/src/hooks/use-graph-data.ts +++ b/packages/memory-graph/src/hooks/use-graph-data.ts @@ -157,10 +157,12 @@ export function computeClusterAssignments( const component: string[] = [] const queue = [startId] + // index dequeue: shift() is O(n) per call, O(n^2) on wide components + let head = 0 visited.add(startId) - while (queue.length > 0) { - const id = queue.shift() as string + while (head < queue.length) { + const id = queue[head++] as string component.push(id) for (const nextId of adjacency.get(id) ?? []) { if (visited.has(nextId)) continue