From 3e9c3ac83b20ad2cf27b15cb4af9bac2f6393c33 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 2 Jul 2026 21:53:38 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=84=B1?= =?UTF-8?q?=EB=8A=A5=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - App.tsx의 `searchMatchedNodeIds` 내부에서 배열 전개 연산자, `.flatMap()`, `.join(" ")`을 사용하던 방식을 제거함 - 대신 명시적 `for` 루프를 사용하고 일치하는 요소가 있으면 즉시(short-circuit) 반환하도록 수정하여, 매 키 입력마다 발생하던 대규모 메모리 할당 및 GC 부하를 제거함 - .jules/bolt.md 에 관련 성능 패턴 내용 업데이트 --- .jules/bolt.md | 4 ++++ CHANGELOG.md | 5 +++++ frontend/src/App.tsx | 35 +++++++++++++++++++++++------------ 3 files changed, 32 insertions(+), 12 deletions(-) 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); } }