Skip to content
Closed
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
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,6 @@ 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-15 - Optimize DBML import and Frontend GC overhead
**Learning:** Using `sum(1 for ...)` inside a loop to calculate positions causes O(N^2) complexity, and `Array.from(string)` creates unnecessary array allocations leading to GC pressure.
**Action:** Introduced O(1) dictionary counter in `dbml_import.py` and replaced `Array.from` with a `for...of` loop in `handleUtils.ts`.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@
- [FE] `autoInfer.ts`에 대한 단위 테스트 및 UI 컴포넌트 단위 테스트를 추가하여 100% 테스트 커버리지를 유지합니다.
- [FE] ⬇️ **DBML Export**: ERD 다이어그램을 DBML (Database Markup Language) 형식으로 내보낼 수 있는 기능을 추가했습니다. 상단의 DBML 버튼을 클릭하여 다운로드할 수 있습니다.
- [FE] 📚 **Data Dictionary Export**: ERD 테이블/컬럼 메타데이터를 CSV 및 Markdown으로 내보내며, CSV formula injection과 Markdown 렌더링 escape를 적용했습니다.

- [Performance] ⚡ **Bolt**: 백엔드 DBML 임포트 처리에서 O(N^2) 병목을 개선하고, 프론트엔드의 ERD 핸들 ID 생성 시 메모리 가비지 컬렉션(GC) 부하를 최적화했습니다.
7 changes: 6 additions & 1 deletion backend/app/spec/dbml_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def parse_dbml(text: str) -> dict[str, Any]:
fk_specs: list[tuple[str, str, str, str, str, str]] = [] # child s/t/c, parent s/t/c

oid_by_table: dict[tuple[str, str], int] = {}
col_count_by_oid: dict[int, int] = {}
next_oid = 1
current: tuple[str, str] | None = None
in_ignored_block = 0
Expand Down Expand Up @@ -206,12 +207,16 @@ def parse_dbml(text: str) -> dict[str, Any]:
col_name = (cm.group("qname") or cm.group("name")).strip('"')
settings = (cm.group("settings") or "").lower()
oid = oid_by_table[current]

col_position = col_count_by_oid.get(oid, 0) + 1
col_count_by_oid[oid] = col_position

is_pk = bool(re.search(r"\bpk\b|primary\s+key", settings))
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_position, # ⚡ Bolt: Use O(1) dictionary counter instead of O(N^2) inline generator expression for column positions.
"data_type": cm.group("type"),
"is_not_null": is_pk or "not null" in settings,
"has_default": "default:" in settings,
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/erd/handleUtils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
export function sanitizeHandleId(columnName: string): string {
const encoded = Array.from(columnName, (char) => {
// Array.from only yields non-empty Unicode scalars, so codePointAt(0) is defined.
return char.codePointAt(0)!.toString(16).padStart(4, '0')
}).join('-')
const codes: string[] = [];
// ⚡ Bolt: Avoid Array.from(string) to prevent intermediate array allocations and reduce GC overhead.
for (const char of columnName) {
codes.push(char.codePointAt(0)!.toString(16).padStart(4, '0'));
}
const encoded = codes.join('-');

return `c-${encoded || 'empty'}`
return `c-${encoded || 'empty'}`;
}

export function sourceColumnHandleId(columnName: string): string {
Expand Down
Loading