diff --git a/.jules/bolt.md b/.jules/bolt.md index b45f9caa..d4d55185 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -60,3 +60,7 @@ Optimized metric route processing to O(N) by creating a mapping of routes direct ## 2026-06-25 - Avoid Map allocations in frontend ERD loops and mutate asyncpg records in-place **Learning:** The frontend `snapshotToGraph` iterates over thousands of columns to generate the graph, so repeated lookups and redundant collection assignments increase GC pressure. Backend snapshot column dictionaries are freshly instantiated for the payload, so `add_column_examples` can safely fill missing fields in place. **Action:** Reuse existing collections while aggregating relational data, create `Map`/`Set` entries only on first use, and check for missing example fields before calling expensive inference helpers. + +## 2025-01-20 - Avoid array map allocations when initializing large Maps +**Learning:** Initializing Maps with `new Map(array.map(...))` creates large intermediate arrays in memory (`[key, value][]`), resulting in unnecessary memory allocation, high GC pressure, and significantly worse performance during iterative processes (like diagram exports). +**Action:** Prefer explicitly initializing a `new Map()` and populating it within a standard `for...of` loop over `new Map(array.map(...))` for high-frequency or large-scale data processing in the frontend. \ No newline at end of file diff --git a/frontend/src/erd/export.ts b/frontend/src/erd/export.ts index b172947e..793fcb82 100644 --- a/frontend/src/erd/export.ts +++ b/frontend/src/erd/export.ts @@ -92,7 +92,11 @@ export function exportDDL(nodes: Node[], edges: Edge[]): string { let ddl = '-- Generated DDL\n\n'; // Bolt: Use map for O(1) node lookup instead of O(N) array find - const nodesById = new Map(nodes.map(n => [n.id, n])); + // Bolt: Avoid array allocations via .map() when building Maps + const nodesById = new Map>(); + for (const n of nodes) { + nodesById.set(n.id, n); + } // Export tables for (const node of nodes) { @@ -281,19 +285,25 @@ export function exportDiagramSvg( snapshot?: SnapshotJson | null, ): string { // Bolt: Use map for O(1) node lookup instead of O(N) array find - const nodesById = new Map(nodes.map(n => [n.id, n])); + // Bolt: Avoid array allocations via .map() when building Maps + const nodesById = new Map>(); + for (const n of nodes) { + nodesById.set(n.id, n); + } const width = 280; const headerHeight = 34; const rowHeight = 22; const padding = 40; const indexes = indexesByRelation(snapshot); - const heights = new Map( - nodes.map((node) => { - // ponytail: cap rendered index rows; add full index export when the canvas carries index nodes. - const indexRows = Math.min((indexes.get(node.id)?.length || 0) + (node.data.indexes?.length || 0), 8); - return [node.id, headerHeight + rowHeight * ((node.data.columns?.length || 0) + indexRows + (indexRows ? 1 : 0))]; - }), - ); + + // Bolt: Avoid Map allocations with .map() inside loops for large graph exports + const heights = new Map(); + for (const node of nodes) { + // ponytail: cap rendered index rows; add full index export when the canvas carries index nodes. + const indexRows = Math.min((indexes.get(node.id)?.length || 0) + (node.data.indexes?.length || 0), 8); + heights.set(node.id, headerHeight + rowHeight * ((node.data.columns?.length || 0) + indexRows + (indexRows ? 1 : 0))); + } + let minX = 0; let minY = 0; let maxX = width;