From 02a3a48e011b2daf0957bc389ba96ecf34be407e Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 30 Aug 2026 21:28:32 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0]=20=ED=95=AB=20=ED=8C=A8=EC=8A=A4=EC=97=90?= =?UTF-8?q?=EC=84=9C=20Object.keys=EB=A5=BC=20for...in=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EA=B5=90=EC=B2=B4=20=EB=B0=8F=20=EC=95=88=EC=A0=84=EC=84=B1?= =?UTF-8?q?=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/web/src/lib/server/daily-rollup.ts | 24 ++++++++++---------- packages/web/src/lib/server/weekly-report.ts | 16 +++++++------ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/packages/web/src/lib/server/daily-rollup.ts b/packages/web/src/lib/server/daily-rollup.ts index 44fb4e5c..84e3a77c 100644 --- a/packages/web/src/lib/server/daily-rollup.ts +++ b/packages/web/src/lib/server/daily-rollup.ts @@ -438,16 +438,16 @@ export async function getDailyRollupsForProjects( const userSet = userSetsByDate.get(r.date)! for (const u of r.activeUserIds) userSet.add(u) - // [Bolt: Performance Optimization] Use Object.keys() instead of Object.entries() in hot paths. - // Impact: Avoids array allocation for each key-value pair, significantly reducing GC overhead when aggregating large daily rollups. - for (const k of Object.keys(r.skillCounts)) { - prev.skillCounts[k] = (prev.skillCounts[k] ?? 0) + r.skillCounts[k]! + // [Bolt: Performance Optimization] Use for...in instead of Object.keys() in hot paths. + // Impact: Completely avoids array allocation for each key set, significantly reducing GC overhead when aggregating large daily rollups. + for (const k in r.skillCounts) { + if (Object.hasOwn(r.skillCounts, k)) prev.skillCounts[k] = (prev.skillCounts[k] ?? 0) + r.skillCounts[k]! } - for (const k of Object.keys(r.agentCounts)) { - prev.agentCounts[k] = (prev.agentCounts[k] ?? 0) + r.agentCounts[k]! + for (const k in r.agentCounts) { + if (Object.hasOwn(r.agentCounts, k)) prev.agentCounts[k] = (prev.agentCounts[k] ?? 0) + r.agentCounts[k]! } - for (const k of Object.keys(r.modelTokens)) { - prev.modelTokens[k] = (prev.modelTokens[k] ?? 0) + r.modelTokens[k]! + for (const k in r.modelTokens) { + if (Object.hasOwn(r.modelTokens, k)) prev.modelTokens[k] = (prev.modelTokens[k] ?? 0) + r.modelTokens[k]! } // userStats: userId 기준 sum (지연된 Map 변환) @@ -622,10 +622,10 @@ export function aggregateSummary( totals.cacheCreationTokens += r.cacheCreationTokens totals.estimatedCostUsd += r.estimatedCostUsd for (const u of r.activeUserIds) activeUsers.add(u) - // [Bolt: Performance Optimization] Object.keys() iterations avoid internal array tuples, reducing heap thrashing - for (const k of Object.keys(r.skillCounts)) skillCounts[k] = (skillCounts[k] ?? 0) + r.skillCounts[k]! - for (const k of Object.keys(r.agentCounts)) agentCounts[k] = (agentCounts[k] ?? 0) + r.agentCounts[k]! - for (const k of Object.keys(r.modelTokens)) modelTokens[k] = (modelTokens[k] ?? 0) + r.modelTokens[k]! + // [Bolt: Performance Optimization] for...in iterations avoid internal array tuples and keys array allocation, reducing heap thrashing + for (const k in r.skillCounts) if (Object.hasOwn(r.skillCounts, k)) skillCounts[k] = (skillCounts[k] ?? 0) + r.skillCounts[k]! + for (const k in r.agentCounts) if (Object.hasOwn(r.agentCounts, k)) agentCounts[k] = (agentCounts[k] ?? 0) + r.agentCounts[k]! + for (const k in r.modelTokens) if (Object.hasOwn(r.modelTokens, k)) modelTokens[k] = (modelTokens[k] ?? 0) + r.modelTokens[k]! } // Deterministic tie-break: callCount DESC, skillName ASC (codepoint binary — diff --git a/packages/web/src/lib/server/weekly-report.ts b/packages/web/src/lib/server/weekly-report.ts index eb95d36f..340078f3 100644 --- a/packages/web/src/lib/server/weekly-report.ts +++ b/packages/web/src/lib/server/weekly-report.ts @@ -394,19 +394,21 @@ export async function getWeeklyReport( // Insights — delegation // ⚡ Bolt Optimization: // 병목 지점: 기존 코드는 `thisWeekRollups`를 3번 순회하고, 매 순회마다 Object.values()로 중간 배열을 생성하여 메모리 할당 비용이 발생했습니다. - // 최적화 방법: 단일 for...of 루프와 Object.keys() 순회를 결합하여 N+1 순회를 1회 순회로 통합하고 중간 배열 할당을 제거했습니다. - // 기대 효과: `thisWeekRollups`의 크기가 클 경우, 불필요한 배열 생성 오버헤드와 O(N) 순회를 1/3로 줄여 리포트 생성 성능이 향상됩니다. + // 최적화 방법: 단일 for...of 루프와 for...in 순회를 결합하여 배열 할당을 완전히 제거하고 N+1 순회를 1회로 통합했습니다. + // 기대 효과: `thisWeekRollups`의 크기가 클 경우, 불필요한 배열 생성 오버헤드 및 O(N) 순회를 1/3로 줄이고 GC 오버헤드를 대폭 감소시켜 리포트 생성 성능이 향상됩니다. let totalAgentCalls = 0 let totalSkillCalls = 0 const distinctSkillsThisWeek = new Set() for (const r of thisWeekRollups) { - for (const k of Object.keys(r.agentCounts)) { - totalAgentCalls += r.agentCounts[k] + for (const k in r.agentCounts) { + if (Object.hasOwn(r.agentCounts, k)) totalAgentCalls += r.agentCounts[k] } - for (const k of Object.keys(r.skillCounts)) { - totalSkillCalls += r.skillCounts[k] - distinctSkillsThisWeek.add(k) + for (const k in r.skillCounts) { + if (Object.hasOwn(r.skillCounts, k)) { + totalSkillCalls += r.skillCounts[k] + distinctSkillsThisWeek.add(k) + } } }