From f250898a3b66948294e54ce52b71769e9d4a427b Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Wed, 26 Jun 2024 15:01:50 +0200 Subject: [PATCH] Optimized stats aggregation code for Admin dashboard fix https://linear.app/tryghost/issue/SLO-168/rangeerror-maximum-call-stack-size-exceeded - this code takes the API output and reduces it down to collect together stats per date - the current code is recursive, and we've seen errors with the recursion hitting a `RangeError: Maximum call stack size exceeded` error - as well as that, we're doing a lot of array concat'ing and cloning, which burns memory and CPU time - instead, we can just use `.reduce` - the new implementation is much faster than the existing one (1ms vs 85ms) and uses no recursion, so those errors should go away - I've also verified that the output is the same between the two functions --- ghost/admin/app/utils/merge-stats-by-date.js | 47 +++++++++----------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/ghost/admin/app/utils/merge-stats-by-date.js b/ghost/admin/app/utils/merge-stats-by-date.js index 797c4f0d9e..bdd881f897 100644 --- a/ghost/admin/app/utils/merge-stats-by-date.js +++ b/ghost/admin/app/utils/merge-stats-by-date.js @@ -1,31 +1,26 @@ -export default function mergeDates(list, entry) { - const [current, ...rest] = list; +export default function mergeStatsByDate(list) { + const reducedStatsByDate = list.reduce((acc, current) => { + const currentDate = current.date; - if (!current) { - return entry ? [entry] : []; - } + if (!acc[currentDate]) { + acc[currentDate] = { + date: currentDate, + count: 0, + positiveDelta: 0, + negativeDelta: 0, + signups: 0, + cancellations: 0 + }; + } - if (!entry) { - return mergeDates(rest, { - date: current.date, - count: current.count, - positiveDelta: current.positive_delta, - negativeDelta: current.negative_delta, - signups: current.signups, - cancellations: current.cancellations - }); - } + acc[currentDate].count += current.count; + acc[currentDate].positiveDelta += current.positive_delta; + acc[currentDate].negativeDelta += current.negative_delta; + acc[currentDate].signups += current.signups; + acc[currentDate].cancellations += current.cancellations; - if (current.date === entry.date) { - return mergeDates(rest, { - date: entry.date, - count: entry.count + current.count, - positiveDelta: entry.positiveDelta + current.positive_delta, - negativeDelta: entry.negativeDelta + current.negative_delta, - signups: entry.signups + current.signups, - cancellations: entry.cancellations + current.cancellations - }); - } + return acc; + }, {}); - return [entry].concat(mergeDates(list)); + return Object.values(reducedStatsByDate); }