diff --git a/routes/log.js b/routes/log.js index 37f30ba..46772c1 100644 --- a/routes/log.js +++ b/routes/log.js @@ -30,6 +30,17 @@ module.exports = function(pool) { unitsCol ? `sum(f."${unitsCol}")::float8 AS units_total` : `NULL::float8 AS units_total` ].join(', '); + // ?kind=adjustments drops the baseline and reference entries. That is not + // only about what gets listed: the aggregate below joins the whole + // forecast table, and on a real version the load entries own almost + // every row of it -- 2.5M against a few thousand for the adjustments. + // Filtering in the WHERE keeps them out of the join rather than + // totalling them and discarding the answer. + const adjustmentsOnly = req.query.kind === 'adjustments'; + const opFilter = adjustmentsOnly + ? `AND l.operation NOT IN ('baseline', 'reference')` + : ''; + const result = await pool.query(` SELECT l.*, ${aggCols}, $2::text AS value_col, @@ -37,6 +48,7 @@ module.exports = function(pool) { FROM pf.log l LEFT JOIN ${table} f ON f.pf_logid = l.id WHERE l.version_id = $1 + ${opFilter} GROUP BY l.id ORDER BY l.id DESC `, [versionId, valueCol || null, unitsCol || null]); diff --git a/routes/versions.js b/routes/versions.js index 5d92b65..48331c0 100644 --- a/routes/versions.js +++ b/routes/versions.js @@ -102,6 +102,14 @@ ${colDefs}, `; await client.query(ddl); + // pf_logid is how every entry-level operation finds its rows: undo + // deletes by it, the change log aggregates by it, and it is part of the + // grain key. Without an index each of those is a sequential scan of the + // whole forecast table -- 2.5M rows to total two adjustments. + await client.query( + `CREATE INDEX ${table.split('.').pop()}_logid_idx ON ${table} (pf_logid)` + ); + await client.query('COMMIT'); res.status(201).json({ ...version, fc_table: table }); } catch (err) { diff --git a/ui/src/views/Forecast.jsx b/ui/src/views/Forecast.jsx index dac84e3..b15fd28 100644 --- a/ui/src/views/Forecast.jsx +++ b/ui/src/views/Forecast.jsx @@ -1162,12 +1162,13 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio setShowLog(true) setLogLoading(true) try { - const data = await fetch(`/api/versions/${versionId}/log`).then(r => r.json()) // Baseline and reference loads are segment construction, not forecasting. // They are managed in the Baseline view, where they can be edited in place // and their date ranges seen; listing them here only buries the adjustments // this log is for -- and offers an undo that would silently gut the version. - setLogEntries(data.filter(e => !['baseline', 'reference'].includes(e.operation))) + // Asked for by kind so the server does not total rows we are not showing. + const data = await fetch(`/api/versions/${versionId}/log?kind=adjustments`).then(r => r.json()) + setLogEntries(data) } catch (err) { flash(err.message, 'error') } finally {