diff --git a/.jules/bolt.md b/.jules/bolt.md index b45f9caa..5e0ef58b 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. + +## 2024-07-03 - [Avoid Array Spreads and flatMap in Hot Path Search Filters] +**Learning:** In frontend performance, massive array spreads and `.flatMap()` in heavily executed loops (like React's search filters over hundreds of nodes and columns) cause unnecessary memory allocations and garbage collection overhead. +**Action:** Replace array-based operations with explicit `for` loops that use early returns or `break` (short-circuiting). This significantly reduces memory pressure and execution time by skipping further evaluation once a match is found. diff --git a/CHANGELOG.md b/CHANGELOG.md index 6437e94a..0969035c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,3 +12,8 @@ ### πŸ› κ°œμ„  (Improvements) - λΆˆν•„μš”ν•œ λ°±μ—”λ“œ ν¬λ§·νŒ… 이슈(`ruff` 포맷) ν•΄κ²°. - ν…ŒμŠ€νŠΈ 컀버리지λ₯Ό 높이기 μœ„ν•΄ κΈ°λ³Έ λ‹¨μœ„ ν…ŒμŠ€νŠΈ ν™˜κ²½(jsdom, @testing-library) ꡬ좕 및 ν™œμš©. + +### λ³€κ²½ 사항 (μ„±λŠ₯ κ°œμ„ ) +* **ν”„λ‘ νŠΈμ—”λ“œ μ΅œμ ν™”:** ERD λ‹€μ΄μ–΄κ·Έλž¨μ˜ ν…Œμ΄λΈ” 검색 ν•„ν„°(App.tsx의 `searchMatchedNodeIds` λ‚΄λΆ€) μ„±λŠ₯을 크게 κ°œμ„ ν–ˆμŠ΅λ‹ˆλ‹€. + * 기쑴의 λ°°μ—΄ 볡사(array spread) 및 `.flatMap()`, `.join()` 연산을 μ œκ±°ν•˜κ³  λͺ…μ‹œμ μΈ `for` 루프와 단락 평가(short-circuit) 기법을 μ μš©ν–ˆμŠ΅λ‹ˆλ‹€. + * 이둜 인해 λ§€ 타이핑 μ‹œλ§ˆλ‹€ λ°œμƒν•˜λŠ” κ±°λŒ€ν•œ λ©”λͺ¨λ¦¬ ν• λ‹Ή 및 κ°€λΉ„μ§€ μ»¬λ ‰μ…˜ λΆ€ν•˜κ°€ 쀄어듀어, λŒ€κ·œλͺ¨ ν…Œμ΄λΈ” λ Œλ”λ§ μ‹œ 응닡 속도와 UI μ„±λŠ₯이 κ°œμ„ λ˜μ—ˆμŠ΅λ‹ˆλ‹€. diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e99700c5..ca9828d5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -182,18 +182,29 @@ export default function App() { if (!normalizedNodeSearch) return new Set(); const matches = new Set(); for (const node of nodes) { - const haystack = [ - node.data.title, - node.data.comment ?? "", - ...node.data.columns.flatMap((column) => [ - column.column_name, - column.data_type, - column.column_comment ?? "", - ]), - ] - .join(" ") - .toLocaleLowerCase(); - if (haystack.includes(normalizedNodeSearch)) { + // ⚑ Bolt: Avoid massive array spreads and .flatMap in hot paths. + // Use explicit loops to short-circuit matching early without allocating memory. + if (node.data.title.toLocaleLowerCase().includes(normalizedNodeSearch)) { + matches.add(node.id); + continue; + } + if (node.data.comment?.toLocaleLowerCase().includes(normalizedNodeSearch)) { + matches.add(node.id); + continue; + } + + let matchedInColumns = false; + for (const column of node.data.columns) { + if ( + column.column_name.toLocaleLowerCase().includes(normalizedNodeSearch) || + column.data_type.toLocaleLowerCase().includes(normalizedNodeSearch) || + column.column_comment?.toLocaleLowerCase().includes(normalizedNodeSearch) + ) { + matchedInColumns = true; + break; + } + } + if (matchedInColumns) { matches.add(node.id); } }