From d56ebd16b616557ba1ca69694650beaa604b9d36 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Mon, 27 Jul 2026 02:14:39 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20Array=20.find()?= =?UTF-8?q?=20in=20ERD=20Export=20DDL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit optimizes a nested array linear search (`Array.find()`) inside the `exportDDL` graph loop which searches for matching source/target handle IDs against node columns. By precomputing an `O(1)` Map that caches handle IDs for the nodes on demand, it converts an `O(E * C)` algorithm constraint to an `O(N * C + E)` lookup constraint, providing significant performance speedups for large entity relationship diagram exports. --- .jules/bolt.md | 3 +++ frontend/src/erd/export.ts | 39 +++++++++++++++++++++++++++++++------- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index f1a8c146..117b763e 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -77,3 +77,6 @@ Optimized metric route processing to O(N) by creating a mapping of routes direct ## 2024-07-13 - [Optimize Export Dictionary FK lookups] **Learning:** Found O(N * C * E) performance bottleneck in ERD export dictionaries due to repeated array searching with `edges.some()` inside a nested loop over nodes and columns. **Action:** Replace repeated linear array scans for edges by precomputing O(1) Set lookups of foreign key column handles per node before looping. +## 2024-07-27 - Optimize Array .find() in ERD Export DDL +**Learning:** In `exportDDL`, finding source/target handle columns inside `fkColumnsForEdge` used repeated `O(N)` `Array.find()` calls inside the `edges` loop, creating `O(E * C)` complexity which degrades performance for large graphs. +**Action:** Precompute and cache `Map` structures mapping column handles to column names per node before evaluating edges, transforming the lookup to `O(1)` and significantly accelerating the DDL generation. diff --git a/frontend/src/erd/export.ts b/frontend/src/erd/export.ts index 62ce7219..fd0c0b32 100644 --- a/frontend/src/erd/export.ts +++ b/frontend/src/erd/export.ts @@ -59,6 +59,8 @@ function fkColumnsForEdge( edge: Edge, sourceNode: Node, targetNode: Node, + sourceHandleMap?: Map, + targetHandleMap?: Map, ): { sourceColumns: string[]; targetColumns: string[] } | null { const data = edge.data as ForeignKeyEdgeData | undefined; const sourceColumns = data?.sourceColumns?.filter(Boolean) || []; @@ -67,12 +69,12 @@ function fkColumnsForEdge( return { sourceColumns, targetColumns }; } - const sourceHandleColumn = (sourceNode.data.columns || []) - .find((column) => sourceColumnHandleId(column.column_name) === edge.sourceHandle) - ?.column_name; - const targetHandleColumn = (targetNode.data.columns || []) - .find((column) => targetColumnHandleId(column.column_name) === edge.targetHandle) - ?.column_name; + const sourceHandleColumn = sourceHandleMap + ? sourceHandleMap.get(edge.sourceHandle || '') + : (sourceNode.data.columns || []).find((column) => sourceColumnHandleId(column.column_name) === edge.sourceHandle)?.column_name; + const targetHandleColumn = targetHandleMap + ? targetHandleMap.get(edge.targetHandle || '') + : (targetNode.data.columns || []).find((column) => targetColumnHandleId(column.column_name) === edge.targetHandle)?.column_name; if (sourceHandleColumn && targetHandleColumn) { return { sourceColumns: [sourceHandleColumn], targetColumns: [targetHandleColumn] }; } @@ -128,12 +130,35 @@ export function exportDDL(nodes: Node[], edges: Edge[]): string { } // Export foreign keys + // ⚡ Bolt: Cache handle maps to optimize O(N^2) `.find()` lookup complexity for large graphs. + const handleMapCache = new Map>(); + for (const edge of edges) { const sourceNode = nodesById.get(edge.source); const targetNode = nodesById.get(edge.target); if (sourceNode && targetNode) { - const fkColumns = fkColumnsForEdge(edge, sourceNode, targetNode); + let sourceMap = handleMapCache.get(sourceNode.id); + if (!sourceMap) { + sourceMap = new Map(); + for (const c of sourceNode.data.columns || []) { + sourceMap.set(sourceColumnHandleId(c.column_name), c.column_name); + sourceMap.set(targetColumnHandleId(c.column_name), c.column_name); + } + handleMapCache.set(sourceNode.id, sourceMap); + } + + let targetMap = handleMapCache.get(targetNode.id); + if (!targetMap) { + targetMap = new Map(); + for (const c of targetNode.data.columns || []) { + targetMap.set(sourceColumnHandleId(c.column_name), c.column_name); + targetMap.set(targetColumnHandleId(c.column_name), c.column_name); + } + handleMapCache.set(targetNode.id, targetMap); + } + + const fkColumns = fkColumnsForEdge(edge, sourceNode, targetNode, sourceMap, targetMap); const constraintName = edge.label ? edge.label : `fk_${edge.source}_${edge.target}`; const sourceTable = quoteSqlIdentifier(sourceNode.data.title || sourceNode.id); const targetTable = quoteSqlIdentifier(targetNode.data.title || targetNode.id);