From cceb5f1784553648cea0ce917005e9eb5665b970 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:25:01 +0000 Subject: [PATCH 1/9] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[performance=20improvem?= =?UTF-8?q?ent]=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A7=91=EA=B3=84=20?= =?UTF-8?q?=ED=95=AB=20=ED=8C=A8=EC=8A=A4=EC=97=90=EC=84=9C=20=EB=B0=B0?= =?UTF-8?q?=EC=97=B4=20=ED=95=A0=EB=8B=B9=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/bolt.md | 3 +++ packages/web/src/lib/server/daily-rollup.ts | 16 ++++++++-------- packages/web/src/lib/server/weekly-report.ts | 6 +++--- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 57daf471..4650b37b 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -3,3 +3,6 @@ **Learning:** `Date.parse(value)` returns the timestamp primitive directly, while `new Date(value).getTime()` also constructs a `Date` object. Both use the same ECMAScript string-parsing semantics for these call sites. **Action:** In frequently executed paths that only need a timestamp primitive, prefer `Date.parse(value)`. Treat the allocation reduction as a bounded micro-optimization unless a committed benchmark establishes a larger runtime effect. +## 2023-10-27 - [for...in 객체 순회 최적화] +**Learning:** Object.keys()는 반복마다 배열을 생성하여 대량 데이터 집계(hot path) 시 가비지 컬렉션 부하를 야기함. +**Action:** 대규모 데이터 집계(daily-rollup, weekly-report) 핫 패스에서는 `Object.keys()` 대신 `for...in`과 `Object.hasOwn()`을 결합하여 배열 할당 및 GC 오버헤드를 원천 차단할 것. diff --git a/packages/web/src/lib/server/daily-rollup.ts b/packages/web/src/lib/server/daily-rollup.ts index 44fb4e5c..3afd8698 100644 --- a/packages/web/src/lib/server/daily-rollup.ts +++ b/packages/web/src/lib/server/daily-rollup.ts @@ -438,15 +438,15 @@ 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. + // [Bolt: Performance Optimization] Use for...in with Object.hasOwn() instead of Object.keys() 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)) { + 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)) { + 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)) { + for (const k in r.modelTokens) if (Object.hasOwn(r.modelTokens, k)) { prev.modelTokens[k] = (prev.modelTokens[k] ?? 0) + r.modelTokens[k]! } @@ -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 loops with Object.hasOwn() completely avoid array allocations for keys, heavily 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..4a9371cd 100644 --- a/packages/web/src/lib/server/weekly-report.ts +++ b/packages/web/src/lib/server/weekly-report.ts @@ -394,17 +394,17 @@ export async function getWeeklyReport( // Insights — delegation // ⚡ Bolt Optimization: // 병목 지점: 기존 코드는 `thisWeekRollups`를 3번 순회하고, 매 순회마다 Object.values()로 중간 배열을 생성하여 메모리 할당 비용이 발생했습니다. - // 최적화 방법: 단일 for...of 루프와 Object.keys() 순회를 결합하여 N+1 순회를 1회 순회로 통합하고 중간 배열 할당을 제거했습니다. + // 최적화 방법: 단일 for...of 루프와 for...in 순회를 결합하여 N+1 순회를 1회 순회로 통합하고 중간 배열 할당을 제거했습니다. // 기대 효과: `thisWeekRollups`의 크기가 클 경우, 불필요한 배열 생성 오버헤드와 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) } From f9d417d8b7bf24f1bed9986f1f057ab86da887e1 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:40:35 +0000 Subject: [PATCH 2/9] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[performance=20improvem?= =?UTF-8?q?ent]=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A7=91=EA=B3=84=20?= =?UTF-8?q?=ED=95=AB=20=ED=8C=A8=EC=8A=A4=EC=97=90=EC=84=9C=20=EB=B0=B0?= =?UTF-8?q?=EC=97=B4=20=ED=95=A0=EB=8B=B9=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 +++- pnpm-lock.yaml | 65 ++++++++++++++++++++++++++++---------------------- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index d085ba62..fccff836 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,9 @@ "undici": "^7.29.0", "minimatch": "^10.0.0", "@hono/node-server": "^2.0.5", - "body-parser": "^2.3.0" + "body-parser": "^2.3.0", + "browserslist": "4.28.9", + "deepmerge-ts": "8.0.2" } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6dfd315f..a5b413c7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,8 @@ overrides: minimatch: ^10.0.0 '@hono/node-server': ^2.0.5 body-parser: ^2.3.0 + browserslist: 4.28.9 + deepmerge-ts: 8.0.2 pnpmfileChecksum: qsp27c6veblwg3gxusbbzrumtm @@ -1939,8 +1941,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.10.33: - resolution: {integrity: sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==} + baseline-browser-mapping@2.11.21: + resolution: {integrity: sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -1962,8 +1964,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.2: - resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + browserslist@4.28.9: + resolution: {integrity: sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -2006,6 +2008,9 @@ packages: caniuse-lite@1.0.30001793: resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + caniuse-lite@1.0.30001810: + resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -2278,9 +2283,9 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - deepmerge-ts@7.1.5: - resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} - engines: {node: '>=16.0.0'} + deepmerge-ts@8.0.2: + resolution: {integrity: sha512-uqbvqLUMrc6p0MO+WBRtTxY55hmyh94WRwI5a++PZe54X+bfVh59FSN7uWCBCW1CCVjzjnrwzfI8zidE2obMMw==} + engines: {node: '>=16.9.0'} deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} @@ -2369,8 +2374,8 @@ packages: effect@3.21.0: resolution: {integrity: sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==} - electron-to-chromium@1.5.364: - resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==} + electron-to-chromium@1.5.424: + resolution: {integrity: sha512-rTOe76LHREmnLOWc0NlakB7AlnWjhTNflk4YE3gnIBPbSXLiY+VDEgwYl4wAYdBPD7eq9NQq6TR8lvd9Fls2CA==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -3673,8 +3678,8 @@ packages: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - node-releases@2.0.46: - resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==} + node-releases@2.0.54: + resolution: {integrity: sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==} engines: {node: '>=18'} npm-run-path@4.0.1: @@ -4526,11 +4531,11 @@ packages: until-async@3.0.2: resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + update-browserslist-db@1.3.2: + resolution: {integrity: sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==} hasBin: true peerDependencies: - browserslist: '>= 4.21.0' + browserslist: 4.28.9 uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -4845,7 +4850,7 @@ snapshots: dependencies: '@babel/compat-data': 7.29.7 '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.2 + browserslist: 4.28.9 lru-cache: 5.1.1 semver: 6.3.1 @@ -5659,7 +5664,7 @@ snapshots: '@prisma/config@6.19.3(magicast@0.3.5)': dependencies: c12: 3.1.0(magicast@0.3.5) - deepmerge-ts: 7.1.5 + deepmerge-ts: 8.0.2 effect: 3.21.0 empathic: 2.0.0 transitivePeerDependencies: @@ -6359,7 +6364,7 @@ snapshots: balanced-match@4.0.4: {} - baseline-browser-mapping@2.10.33: {} + baseline-browser-mapping@2.11.21: {} bcryptjs@2.4.3: {} @@ -6389,13 +6394,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.28.2: + browserslist@4.28.9: dependencies: - baseline-browser-mapping: 2.10.33 - caniuse-lite: 1.0.30001793 - electron-to-chromium: 1.5.364 - node-releases: 2.0.46 - update-browserslist-db: 1.2.3(browserslist@4.28.2) + baseline-browser-mapping: 2.11.21 + caniuse-lite: 1.0.30001810 + electron-to-chromium: 1.5.424 + node-releases: 2.0.54 + update-browserslist-db: 1.3.2(browserslist@4.28.9) bundle-name@4.1.0: dependencies: @@ -6443,6 +6448,8 @@ snapshots: caniuse-lite@1.0.30001793: {} + caniuse-lite@1.0.30001810: {} + ccount@2.0.1: {} chai@5.3.3: @@ -6660,7 +6667,7 @@ snapshots: deep-is@0.1.4: {} - deepmerge-ts@7.1.5: {} + deepmerge-ts@8.0.2: {} deepmerge@4.3.1: {} @@ -6740,7 +6747,7 @@ snapshots: '@standard-schema/spec': 1.1.0 fast-check: 3.23.2 - electron-to-chromium@1.5.364: {} + electron-to-chromium@1.5.424: {} emoji-regex@10.6.0: {} @@ -8406,7 +8413,7 @@ snapshots: fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 - node-releases@2.0.46: {} + node-releases@2.0.54: {} npm-run-path@4.0.1: dependencies: @@ -8999,7 +9006,7 @@ snapshots: '@dotenvx/dotenvx': 1.70.0 '@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76) '@types/validate-npm-package-name': 4.0.2 - browserslist: 4.28.2 + browserslist: 4.28.9 commander: 14.0.3 cosmiconfig: 9.0.1(typescript@5.9.3) dedent: 1.7.2 @@ -9491,9 +9498,9 @@ snapshots: until-async@3.0.2: {} - update-browserslist-db@1.2.3(browserslist@4.28.2): + update-browserslist-db@1.3.2(browserslist@4.28.9): dependencies: - browserslist: 4.28.2 + browserslist: 4.28.9 escalade: 3.2.0 picocolors: 1.1.1 From 0b30d49eb60f4122d726d9d371e6914e5d5f7830 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:53:11 +0000 Subject: [PATCH 3/9] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[performance=20improvem?= =?UTF-8?q?ent]=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A7=91=EA=B3=84=20?= =?UTF-8?q?=ED=95=AB=20=ED=8C=A8=EC=8A=A4=EC=97=90=EC=84=9C=20=EB=B0=B0?= =?UTF-8?q?=EC=97=B4=20=ED=95=A0=EB=8B=B9=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 28d2bf4cd8d6b0744d2b2db3a4e3e8d07d4b6b04 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 8 Sep 2026 22:00:45 +0000 Subject: [PATCH 4/9] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[performance=20improvem?= =?UTF-8?q?ent]=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A7=91=EA=B3=84=20?= =?UTF-8?q?=ED=95=AB=20=ED=8C=A8=EC=8A=A4=EC=97=90=EC=84=9C=20=EB=B0=B0?= =?UTF-8?q?=EC=97=B4=20=ED=95=A0=EB=8B=B9=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From e1be1eb0a2fb6852f3e6171ee0d6dd7c039e3a90 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 8 Sep 2026 22:21:49 +0000 Subject: [PATCH 5/9] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[performance=20improvem?= =?UTF-8?q?ent]=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A7=91=EA=B3=84=20?= =?UTF-8?q?=ED=95=AB=20=ED=8C=A8=EC=8A=A4=EC=97=90=EC=84=9C=20=EB=B0=B0?= =?UTF-8?q?=EC=97=B4=20=ED=95=A0=EB=8B=B9=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 73451d98a2d72bd80b255bcee0b2f75ba9e39a29 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 8 Sep 2026 22:43:35 +0000 Subject: [PATCH 6/9] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[performance=20improvem?= =?UTF-8?q?ent]=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A7=91=EA=B3=84=20?= =?UTF-8?q?=ED=95=AB=20=ED=8C=A8=EC=8A=A4=EC=97=90=EC=84=9C=20=EB=B0=B0?= =?UTF-8?q?=EC=97=B4=20=ED=95=A0=EB=8B=B9=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From cfab35e43e66cb826a4d6816dfe19950c234ebe8 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 8 Sep 2026 23:07:19 +0000 Subject: [PATCH 7/9] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[performance=20improvem?= =?UTF-8?q?ent]=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A7=91=EA=B3=84=20?= =?UTF-8?q?=ED=95=AB=20=ED=8C=A8=EC=8A=A4=EC=97=90=EC=84=9C=20=EB=B0=B0?= =?UTF-8?q?=EC=97=B4=20=ED=95=A0=EB=8B=B9=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 8a6abedb5beed86f81241e0f4d9d38c2021291af Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 8 Sep 2026 23:30:41 +0000 Subject: [PATCH 8/9] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[performance=20improvem?= =?UTF-8?q?ent]=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A7=91=EA=B3=84=20?= =?UTF-8?q?=ED=95=AB=20=ED=8C=A8=EC=8A=A4=EC=97=90=EC=84=9C=20=EB=B0=B0?= =?UTF-8?q?=EC=97=B4=20=ED=95=A0=EB=8B=B9=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From f348e6d1f932a2318d7e9303d00e3f15f56c2f75 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 16:44:08 +0900 Subject: [PATCH 9/9] repair(perf): restore canonical Bolt guidance --- .jules/bolt.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 4650b37b..57daf471 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -3,6 +3,3 @@ **Learning:** `Date.parse(value)` returns the timestamp primitive directly, while `new Date(value).getTime()` also constructs a `Date` object. Both use the same ECMAScript string-parsing semantics for these call sites. **Action:** In frequently executed paths that only need a timestamp primitive, prefer `Date.parse(value)`. Treat the allocation reduction as a bounded micro-optimization unless a committed benchmark establishes a larger runtime effect. -## 2023-10-27 - [for...in 객체 순회 최적화] -**Learning:** Object.keys()는 반복마다 배열을 생성하여 대량 데이터 집계(hot path) 시 가비지 컬렉션 부하를 야기함. -**Action:** 대규모 데이터 집계(daily-rollup, weekly-report) 핫 패스에서는 `Object.keys()` 대신 `for...in`과 `Object.hasOwn()`을 결합하여 배열 할당 및 GC 오버헤드를 원천 차단할 것.