From 1cd97bb3a840d6bd48619c48d10e1d626ad9525a Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:23:16 +0000 Subject: [PATCH] =?UTF-8?q?perf(frontend):=20=EB=8C=80=EA=B7=9C=EB=AA=A8?= =?UTF-8?q?=20=EB=B0=B0=EC=97=B4=20spread=20=EB=B0=8F=20=EB=AC=B8=EC=9E=90?= =?UTF-8?q?=EC=97=B4=20=EA=B2=B0=ED=95=A9=EC=9D=84=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=ED=95=98=EC=97=AC=20ERD=20=EA=B2=80=EC=83=89=20=EC=86=8D?= =?UTF-8?q?=EB=8F=84=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * App.tsx: searchMatchedNodeIds의 필터링 로직에서 flatMap과 큰 문자열 join을 제거하고 early-exit for 루프를 적용함. * CHANGELOG.md: 변경사항 기록. * .jules/bolt.md: 성능 개선 사항 기록. --- .jules/bolt.md | 3 +++ CHANGELOG.md | 1 + frontend/src/App.tsx | 34 ++++++++++++++++++++++------------ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index b45f9caa..dba3d532 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -60,3 +60,6 @@ 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-02 - Avoid massive array spreads and allocations in search loops +**Learning:** Using `.flatMap(...)` with array spreading (`...`) and `.join(" ")` on all node properties during every keystroke of a search bar creates massive intermediate strings and arrays. This non-short-circuiting approach causes significant garbage collection overhead, leading to severe input lag for large ERDs. +**Action:** Replace high-level array manipulation in hot search loops with explicit `for` loops that use early `break` or `continue` to short-circuit upon finding a match, preventing unnecessary allocations and string building. diff --git a/CHANGELOG.md b/CHANGELOG.md index 6437e94a..33522799 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,5 +10,6 @@ - 새롭게 추가된 UI 플로우를 위한 기본 테스트 코드(`App.editTable.test.tsx`, `TableNode.test.tsx`) 추가 및 Vitest 프레임워크 셋업 보완. ### 🐛 개선 (Improvements) +- 대규모 ERD 검색 성능 개선: `flatMap`, 배열 복사(`...`), 그리고 긴 문자열 조인(`join(" ")`)을 제거하고 `for` 루프와 `break`/`continue`를 활용하여 조건 성립 즉시 평가를 중단(short-circuit)하도록 개선함으로써 사용자 입력 지연(Input Lag)과 불필요한 메모리 할당(Garbage Collection 압박)을 해결. - 불필요한 백엔드 포맷팅 이슈(`ruff` 포맷) 해결. - 테스트 커버리지를 높이기 위해 기본 단위 테스트 환경(jsdom, @testing-library) 구축 및 활용. diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e99700c5..35305e0a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -182,18 +182,28 @@ 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)) { + 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 matched = 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) + ) { + matched = true; + break; + } + } + + if (matched) { matches.add(node.id); } }