diff --git a/packages/web/src/lib/server/daily-rollup.ts b/packages/web/src/lib/server/daily-rollup.ts index 44fb4e5c..65df0577 100644 --- a/packages/web/src/lib/server/daily-rollup.ts +++ b/packages/web/src/lib/server/daily-rollup.ts @@ -438,16 +438,22 @@ 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 guarded by Object.hasOwn() instead of Object.keys() in hot paths. + // Impact: Completely avoids array allocation for keys, eliminating 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 +628,16 @@ 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 loops guarded by Object.hasOwn() avoid array allocation completely, eliminating GC overhead in hot paths + 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..73c6a00d 100644 --- a/packages/web/src/lib/server/weekly-report.ts +++ b/packages/web/src/lib/server/weekly-report.ts @@ -394,19 +394,23 @@ 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 루프와 Object.hasOwn()으로 보호된 for...in 루프를 결합하여 N+1 순회를 1회 순회로 통합하고 중간 배열 할당을 완전히 제거했습니다. + // 기대 효과: `thisWeekRollups`의 크기가 클 경우, 불필요한 배열 생성(GC) 오버헤드를 없애고 O(N) 순회를 1/3로 줄여 리포트 생성 성능이 극대화됩니다. let totalAgentCalls = 0 let totalSkillCalls = 0 const distinctSkillsThisWeek = new Set() for (const r of thisWeekRollups) { - for (const k of Object.keys(r.agentCounts)) { + for (const k in r.agentCounts) { + if (Object.hasOwn(r.agentCounts, k)) { totalAgentCalls += r.agentCounts[k] + } } - for (const k of Object.keys(r.skillCounts)) { + for (const k in r.skillCounts) { + if (Object.hasOwn(r.skillCounts, k)) { totalSkillCalls += r.skillCounts[k] distinctSkillsThisWeek.add(k) + } } }