From ac0f6300458a51fb178ecf45c2b26adfa45e4231 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 11 Jun 2026 21:27:58 +0000 Subject: [PATCH] perf: replace reduce/map chains with single for loops in reports and dashboard APIs Refactored multiple `.reduce()` and `.map()` calls into single `for` loops within `weekly-report.ts` and the `sessions` API routes. This reduces the number of iteration passes over large arrays (like Prisma relations) and minimizes intermediate array allocations, improving overall execution speed and memory usage. --- .jules/bolt.md | 3 ++ .../dashboard/sessions/[sessionId]/route.ts | 32 ++++++++++++------- .../[orgSlug]/dashboard/sessions/route.ts | 20 ++++++++---- packages/web/src/lib/server/weekly-report.ts | 27 ++++++++++------ 4 files changed, 54 insertions(+), 28 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..2b1d6f4 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-19 - Replace reduce() loops with single for loops for Prisma include relations +**Learning:** Chained `.reduce()` and `.map()` calls on large in-memory arrays (like Prisma `include` results) cause multiple iteration passes and intermediate array allocations. `Object.values()` also allocates an intermediate array, compounding the issue. +**Action:** Use a single `for` loop to accumulate multiple totals simultaneously. When iterating over objects, use `Object.keys()` in a `for` loop instead of `Object.values()` to avoid intermediate array allocation. diff --git a/packages/web/src/app/api/orgs/[orgSlug]/dashboard/sessions/[sessionId]/route.ts b/packages/web/src/app/api/orgs/[orgSlug]/dashboard/sessions/[sessionId]/route.ts index 35486b8..0c793d9 100644 --- a/packages/web/src/app/api/orgs/[orgSlug]/dashboard/sessions/[sessionId]/route.ts +++ b/packages/web/src/app/api/orgs/[orgSlug]/dashboard/sessions/[sessionId]/route.ts @@ -43,18 +43,26 @@ export async function GET( return forbiddenByRole(access.role, '본인 세션만 열람 가능') } - const totalInput = session.usageRecords.reduce((sum, r) => sum + r.inputTokens, 0) - const totalOutput = session.usageRecords.reduce((sum, r) => sum + r.outputTokens, 0) - const totalCost = session.usageRecords.reduce((sum, r) => sum + (r.estimatedCostUsd ?? 0), 0) - - const usageTimeline: SessionTimelineUsage[] = session.usageRecords.map((r) => ({ - timestamp: r.timestamp.toISOString(), - inputTokens: r.inputTokens, - outputTokens: r.outputTokens, - estimatedCostUsd: r.estimatedCostUsd ?? 0, - model: r.model, - isSubagent: r.isSubagent, - })) + let totalInput = 0 + let totalOutput = 0 + let totalCost = 0 + const usageTimeline: SessionTimelineUsage[] = new Array(session.usageRecords.length) + + for (let i = 0; i < session.usageRecords.length; i++) { + const r = session.usageRecords[i] + totalInput += r.inputTokens + totalOutput += r.outputTokens + totalCost += (r.estimatedCostUsd ?? 0) + + usageTimeline[i] = { + timestamp: r.timestamp.toISOString(), + inputTokens: r.inputTokens, + outputTokens: r.outputTokens, + estimatedCostUsd: r.estimatedCostUsd ?? 0, + model: r.model, + isSubagent: r.isSubagent, + } + } // 각 UsageRecord를 "직전 ASSISTANT 턴"에 귀속시켜 메시지별 토큰/비용/모델 집계. // TOOL 메시지는 건너뛰고 가장 가까운 선행 ASSISTANT로 타고 올라감. diff --git a/packages/web/src/app/api/orgs/[orgSlug]/dashboard/sessions/route.ts b/packages/web/src/app/api/orgs/[orgSlug]/dashboard/sessions/route.ts index 25fbe70..cd11d0c 100644 --- a/packages/web/src/app/api/orgs/[orgSlug]/dashboard/sessions/route.ts +++ b/packages/web/src/app/api/orgs/[orgSlug]/dashboard/sessions/route.ts @@ -33,13 +33,21 @@ const sessionInclude = { type SessionWithInclude = Prisma.ClaudeSessionGetPayload<{ include: typeof sessionInclude }> function getSessionTotals(session: SessionWithInclude) { + let inputTokens = 0 + let outputTokens = 0 + let estimatedCostUsd = 0 + + for (let i = 0; i < session.usageRecords.length; i++) { + const r = session.usageRecords[i] + inputTokens += r.inputTokens + outputTokens += r.outputTokens + estimatedCostUsd += (r.estimatedCostUsd ?? 0) + } + return { - inputTokens: session.usageRecords.reduce((sum, r) => sum + r.inputTokens, 0), - outputTokens: session.usageRecords.reduce((sum, r) => sum + r.outputTokens, 0), - estimatedCostUsd: session.usageRecords.reduce( - (sum, r) => sum + (r.estimatedCostUsd ?? 0), - 0, - ), + inputTokens, + outputTokens, + estimatedCostUsd, } } diff --git a/packages/web/src/lib/server/weekly-report.ts b/packages/web/src/lib/server/weekly-report.ts index ace2f44..a5ae2f7 100644 --- a/packages/web/src/lib/server/weekly-report.ts +++ b/packages/web/src/lib/server/weekly-report.ts @@ -446,17 +446,24 @@ export async function getWeeklyReport( } // Insights — delegation - const totalAgentCalls = thisWeekRollups.reduce( - (sum, r) => sum + Object.values(r.agentCounts).reduce((a, b) => a + b, 0), - 0, - ) - const totalSkillCalls = thisWeekRollups.reduce( - (sum, r) => sum + Object.values(r.skillCounts).reduce((a, b) => a + b, 0), - 0, - ) + let totalAgentCalls = 0 + let totalSkillCalls = 0 const distinctSkillsThisWeek = new Set() - for (const r of thisWeekRollups) { - for (const k of Object.keys(r.skillCounts)) distinctSkillsThisWeek.add(k) + + for (let i = 0; i < thisWeekRollups.length; i++) { + const r = thisWeekRollups[i] + + const agentKeys = Object.keys(r.agentCounts) + for (let j = 0; j < agentKeys.length; j++) { + totalAgentCalls += r.agentCounts[agentKeys[j]] + } + + const skillKeys = Object.keys(r.skillCounts) + for (let j = 0; j < skillKeys.length; j++) { + const k = skillKeys[j] + totalSkillCalls += r.skillCounts[k] + distinctSkillsThisWeek.add(k) + } } const insights: WeeklyInsights = {