diff --git a/.jules/bolt.md b/.jules/bolt.md index f1a8c146..7441e5de 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -77,3 +77,9 @@ 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-25 - Avoid new Set(array.map(...)) for Set Initializations +**Learning:** Using `new Set(array.map(item => item.id))` creates a completely unnecessary intermediate O(N) array of IDs. This forces the garbage collector to immediately clean up the mapped array once the Set is constructed, leading to memory spikes and GC pauses. +**Action:** Replace `new Set(array.map(...))` with `const set = new Set();` and an iterative `for (const item of array) { set.add(item.id); }` loop to reduce intermediate garbage allocations to zero. +## 2024-07-25 - Avoid O(N^2) Loop Scans with O(1) Dictionaries +**Learning:** In loops processing hierarchical structures (like columns of a table), using list comprehensions or `sum()` over the entire collection to count items belonging to a parent (e.g. `sum(1 for c in columns if c["parent_id"] == oid)`) creates an O(N^2) bottleneck. +**Action:** Replace `sum(...)` over accumulated collections with an O(1) dictionary counter (e.g. `col_pos = count_by_oid.get(oid, 0) + 1; count_by_oid[oid] = col_pos`) initialized outside the loop to reduce complexity to O(N). diff --git a/backend/app/spec/dbml_import.py b/backend/app/spec/dbml_import.py index b93454a9..57a16cab 100644 --- a/backend/app/spec/dbml_import.py +++ b/backend/app/spec/dbml_import.py @@ -135,6 +135,7 @@ def parse_dbml(text: str) -> dict[str, Any]: current: tuple[str, str] | None = None in_ignored_block = 0 in_indexes = False + column_position_by_oid: dict[int, int] = {} for raw_line in text.splitlines(): # ReDoS guard: no legitimate DBML line approaches this length; capping @@ -207,11 +208,14 @@ def parse_dbml(text: str) -> dict[str, Any]: settings = (cm.group("settings") or "").lower() oid = oid_by_table[current] is_pk = bool(re.search(r"\bpk\b|primary\s+key", settings)) + col_pos = column_position_by_oid.get(oid, 0) + 1 + column_position_by_oid[oid] = col_pos + columns.append( { "relation_oid": oid, "column_name": col_name, - "column_position": sum(1 for c in columns if c["relation_oid"] == oid) + 1, + "column_position": col_pos, "data_type": cm.group("type"), "is_not_null": is_pk or "not null" in settings, "has_default": "default:" in settings, diff --git a/frontend/src/erd/businessGroups.ts b/frontend/src/erd/businessGroups.ts index f60edd24..52054516 100644 --- a/frontend/src/erd/businessGroups.ts +++ b/frontend/src/erd/businessGroups.ts @@ -41,7 +41,15 @@ export function uniqueBusinessGroupId( existingGroups: BusinessGroup[], ): string { const baseId = buildBusinessGroupId(name); - const existingIds = new Set(existingGroups.map((group) => group.id)); + + // ⚡ Bolt: Use an iterative `for...of` loop to build the Set directly. + // This avoids the O(N) intermediate array allocation and garbage collection overhead + // that occurs when using `new Set(existingGroups.map(...))`. + const existingIds = new Set(); + for (const group of existingGroups) { + existingIds.add(group.id); + } + if (!existingIds.has(baseId)) return baseId; let suffix = 2; while (existingIds.has(`${baseId}_${suffix}`)) {