From b30a0ec859a9ceffe10b896b7ce1c3823d1bffbd Mon Sep 17 00:00:00 2001 From: Vadim Kharin Date: Thu, 30 Jul 2026 15:21:56 +0300 Subject: [PATCH 1/4] fix(query-orchestrator): stop leaking partition/timeSeries cache entries across refresh ticks PreAggregationPartitionRangeLoader.partitionPreAggregations()/partitionRanges() memoized via the query-scoped compilerCacheFn, keyed by JSON.stringify(buildRange) / dateRange. For a live pre-aggregation this range drifts forward on every scheduled refresh, so each tick added a brand-new, never-evicted entry to the shared QueryCache backing compilerCacheFn (its nested storage object has no eviction of its own, unlike the outer per-query LRU). Over the life of a long-running process with recurring scheduled refreshes this grows without bound, confirmed via Pyroscope inuse_objects profiling on platform-analytics showing ~85% of live heap objects rooted in this exact code path. Move the memoization to a cache scoped to the loader instance itself, which is already recreated per refresh tick, so entries are naturally released when the tick completes instead of accumulating forever. CF-2019 --- .../src/orchestrator/PreAggregations.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregations.ts b/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregations.ts index d660be85bc42b..e51215c8000d0 100644 --- a/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregations.ts +++ b/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregations.ts @@ -1503,6 +1503,26 @@ export class PreAggregationPartitionRangeLoader { protected compilerCacheFn: (subKey: string[], cacheFn: () => T) => T; + /** + * Memoizes `partitionPreAggregations()`/`partitionRanges()` results for the + * lifetime of this loader instance only (as opposed to `compilerCacheFn`, + * which is backed by a cache shared across the entire lifetime of the + * parent query and is never pruned per-key). `buildRange`/`dateRange` drift + * forward every time this is recomputed for a live pre-aggregation, so + * routing this through the shared cache added one permanent, never-evicted + * entry per refresh tick, growing without bound for the life of the + * process. See CF-2019. + */ + private readonly localCache: Record = {}; + + private localCacheFn(subKey: string[], cacheFn: () => T): T { + const key = subKey.join(''); + if (!(key in this.localCache)) { + this.localCache[key] = cacheFn(); + } + return this.localCache[key]; + } + public constructor( private readonly redisPrefix: string, private readonly driverFactory: DriverFactory, @@ -1807,7 +1827,7 @@ export class PreAggregationPartitionRangeLoader { public async partitionPreAggregations(): Promise { if (this.preAggregation.partitionGranularity && !this.preAggregation.expandedPartition) { const { buildRange, partitionRanges } = await this.partitionRanges(); - return this.compilerCacheFn(['partitions', JSON.stringify(buildRange)], () => partitionRanges.map(range => this.partitionPreAggregationDescription(range, buildRange))); + return this.localCacheFn(['partitions', JSON.stringify(buildRange)], () => partitionRanges.map(range => this.partitionPreAggregationDescription(range, buildRange))); } else { return [this.preAggregation]; } @@ -1827,7 +1847,7 @@ export class PreAggregationPartitionRangeLoader { // use last partition so outer query can receive expected table structure. dateRange = [buildRange[1], buildRange[1]]; } - const partitionRanges = this.compilerCacheFn(['timeSeries', this.preAggregation.partitionGranularity, JSON.stringify(dateRange), `${this.preAggregation.timestampPrecision}`], () => PreAggregationPartitionRangeLoader.timeSeries( + const partitionRanges = this.localCacheFn(['timeSeries', this.preAggregation.partitionGranularity, JSON.stringify(dateRange), `${this.preAggregation.timestampPrecision}`], () => PreAggregationPartitionRangeLoader.timeSeries( this.preAggregation.partitionGranularity, dateRange, this.preAggregation.timestampPrecision From d55ac5a0ff8c0f6665c895249dced10314f96f71 Mon Sep 17 00:00:00 2001 From: Vadim Kharin Date: Mon, 3 Aug 2026 11:00:51 +0300 Subject: [PATCH 2/4] bump version --- packages/cubejs-query-orchestrator/package.json | 2 +- packages/cubejs-server-core/package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cubejs-query-orchestrator/package.json b/packages/cubejs-query-orchestrator/package.json index 3d7f08cf8c756..8d02be27e4adb 100644 --- a/packages/cubejs-query-orchestrator/package.json +++ b/packages/cubejs-query-orchestrator/package.json @@ -2,7 +2,7 @@ "name": "@codefresh-io/cubejs-backend-query-orchestrator", "description": "Cube.js Query Orchestrator and Cache", "author": "Cube Dev, Inc.", - "version": "0.35.46", + "version": "0.35.47", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", diff --git a/packages/cubejs-server-core/package.json b/packages/cubejs-server-core/package.json index 519b3e7c2fd60..0df127804bf26 100644 --- a/packages/cubejs-server-core/package.json +++ b/packages/cubejs-server-core/package.json @@ -2,7 +2,7 @@ "name": "@codefresh-io/cubejs-backend-server-core", "description": "Cube.js base component to wire all backend components together", "author": "Cube Dev, Inc.", - "version": "0.35.47", + "version": "0.35.48", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -33,7 +33,7 @@ "@cubejs-backend/cloud": "^0.35.43", "@cubejs-backend/dotenv": "^9.0.2", "@cubejs-backend/native": "^0.35.47", - "@cubejs-backend/query-orchestrator": "npm:@codefresh-io/cubejs-backend-query-orchestrator@0.35.46", + "@cubejs-backend/query-orchestrator": "npm:@codefresh-io/cubejs-backend-query-orchestrator@0.35.47", "@cubejs-backend/schema-compiler": "^0.35.47", "@cubejs-backend/shared": "npm:@codefresh-io/cubejs-backend-shared@0.35.43", "@cubejs-backend/templates": "^0.35.43", From ca899c2d1aca11072f3a6a5f3dc399225b28139d Mon Sep 17 00:00:00 2001 From: Vadim Kharin Date: Mon, 3 Aug 2026 11:14:40 +0300 Subject: [PATCH 3/4] bump version --- packages/cubejs-server-core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cubejs-server-core/package.json b/packages/cubejs-server-core/package.json index 0df127804bf26..710747f3428ce 100644 --- a/packages/cubejs-server-core/package.json +++ b/packages/cubejs-server-core/package.json @@ -33,7 +33,7 @@ "@cubejs-backend/cloud": "^0.35.43", "@cubejs-backend/dotenv": "^9.0.2", "@cubejs-backend/native": "^0.35.47", - "@cubejs-backend/query-orchestrator": "npm:@codefresh-io/cubejs-backend-query-orchestrator@0.35.47", + "@cubejs-backend/query-orchestrator": "npm:@codefresh-io/cubejs-backend-query-orchestrator@0.35.46", "@cubejs-backend/schema-compiler": "^0.35.47", "@cubejs-backend/shared": "npm:@codefresh-io/cubejs-backend-shared@0.35.43", "@cubejs-backend/templates": "^0.35.43", From 8411199da78ee14e96a8405e411e81e998848205 Mon Sep 17 00:00:00 2001 From: Vadim Kharin Date: Mon, 3 Aug 2026 12:08:29 +0300 Subject: [PATCH 4/4] bump version --- packages/cubejs-server-core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cubejs-server-core/package.json b/packages/cubejs-server-core/package.json index 710747f3428ce..0df127804bf26 100644 --- a/packages/cubejs-server-core/package.json +++ b/packages/cubejs-server-core/package.json @@ -33,7 +33,7 @@ "@cubejs-backend/cloud": "^0.35.43", "@cubejs-backend/dotenv": "^9.0.2", "@cubejs-backend/native": "^0.35.47", - "@cubejs-backend/query-orchestrator": "npm:@codefresh-io/cubejs-backend-query-orchestrator@0.35.46", + "@cubejs-backend/query-orchestrator": "npm:@codefresh-io/cubejs-backend-query-orchestrator@0.35.47", "@cubejs-backend/schema-compiler": "^0.35.47", "@cubejs-backend/shared": "npm:@codefresh-io/cubejs-backend-shared@0.35.43", "@cubejs-backend/templates": "^0.35.43",