Skip to content
Draft
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
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
"packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0",
"pnpm": {
"overrides": {
"browserslist": "4.28.9",
"deepmerge-ts": "8.0.2",
"@babel/core": "7.29.7",
"esbuild": "0.28.1",
"hono": "^4.12.34",
Expand Down
33 changes: 33 additions & 0 deletions packages/web/src/lib/server/daily-rollup.bench.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, it, expect } from 'vitest'
import { aggregateSummary } from './daily-rollup'

describe('daily-rollup performance', () => {
const generateLargeRollups = (count: number) => {
return Array.from({ length: count }, (_, i) => ({
projectId: 'proj-1',
date: new Date(`2024-01-${(i % 31) + 1}`).toISOString(),
sessionCount: 100,
turnCount: 500,
inputTokens: 10000,
outputTokens: 5000,
cacheReadTokens: 1000,
cacheCreationTokens: 500,
estimatedCostUsd: 0.5,
activeUserIds: Array.from({ length: 50 }, (_, j) => `user-${j}`),
activeUserCount: 50,
skillCounts: Object.fromEntries(Array.from({ length: 100 }, (_, j) => [`skill-${j}`, Math.floor(Math.random() * 10)])),
agentCounts: Object.fromEntries(Array.from({ length: 5 }, (_, j) => [`agent-${j}`, Math.floor(Math.random() * 20)])),
modelTokens: { 'gpt-4': 10000, 'gpt-3.5': 5000 },
userStats: []
}))
}

it('benchmark aggregateSummary', () => {
const rollups = generateLargeRollups(10000)
const start = Date.now()
const result = aggregateSummary(rollups)
const end = Date.now()
console.log(`aggregateSummary with 10000 rollups took ${end - start}ms`)
expect(result).toBeDefined()
})
})
36 changes: 24 additions & 12 deletions packages/web/src/lib/server/daily-rollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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] Iterate own enumerable counters without materializing the Object.keys() result array in hot paths.
// Impact: Avoids intermediate array allocation 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 변환)
Expand Down Expand Up @@ -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] Iterate own enumerable counters without materializing the Object.keys() result array 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 —
Expand Down
20 changes: 12 additions & 8 deletions packages/web/src/lib/server/weekly-report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,20 +393,24 @@ export async function getWeeklyReport(

// Insights — delegation
// ⚡ Bolt Optimization:
// 병목 지점: 기존 코드는 `thisWeekRollups`를 3번 순회하고, 매 순회마다 Object.values()로 중간 배열을 생성하여 메모리 할당 비용이 발생했습니다.
// 최적화 방법: 단일 for...of 루프와 Object.keys() 순회를 결합하여 N+1 순회를 1회 순회로 통합하고 중간 배열 할당을 제거했습니다.
// 기대 효과: `thisWeekRollups`의 크기가 클 경우, 불필요한 배열 생성 오버헤드와 O(N) 순회를 1/3로 줄여 리포트 생성 성능이 향상됩니다.
// 병목 지점: 기존 코드는 `thisWeekRollups`를 3번 순회하고, 매 순회마다 배열을 생성하여 메모리 할당 비용이 발생했습니다.
// 최적화 방법: 단일 for...of 루프와 for...in 순회를 결합하여 N+1 순회를 1회 순회로 통합하고 Object.keys()의 중간 배열 생성 없이 고유 속성 카운터를 순회합니다.
// 기대 효과: `thisWeekRollups`의 크기가 클 경우, 불필요한 중간 배열 할당을 피하고 O(N) 순회를 1/3로 줄여 줍니다.
let totalAgentCalls = 0
let totalSkillCalls = 0
const distinctSkillsThisWeek = new Set<string>()

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)
}
}
}

Expand Down
65 changes: 36 additions & 29 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading