Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
28 changes: 19 additions & 9 deletions frontend/src/erd/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,11 @@ export function exportDDL(nodes: Node<TableNodeData>[], 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<string, Node<TableNodeData>>();
for (const n of nodes) {
nodesById.set(n.id, n);
}

// Export tables
for (const node of nodes) {
Expand Down Expand Up @@ -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<string, Node<TableNodeData>>();
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<string, number>();
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;
Expand Down