Skip to content

Commit c22eb91

Browse files
authored
feat(query-orchestrator): Reduce number of touches for pre-aggregations (#7515)
Cube allows to specify partionGranularity for pre-aggregations, which causes a significant number of requests for updating table touches. This PR introduces a workaround to reduce the number of request for Cache via the LRU cache.
1 parent ad309b1 commit c22eb91

File tree

2 files changed

+66
-7
lines changed

2 files changed

+66
-7
lines changed

packages/cubejs-backend-shared/src/env.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,12 +574,41 @@ const variables: Record<string, (...args: any) => any> = {
574574
.default(8192)
575575
.asInt(),
576576

577+
/**
578+
* Max number of elements
579+
*/
580+
touchPreAggregationCacheMaxCount: (): number => get('CUBEJS_TOUCH_PRE_AGG_CACHE_MAX_COUNT')
581+
.default(8192)
582+
.asInt(),
583+
584+
/**
585+
* Max cache
586+
*/
587+
touchPreAggregationCacheMaxAge: (): number => {
588+
// eslint-disable-next-line no-use-before-define
589+
const touchPreAggregationTimeout = getEnv('touchPreAggregationTimeout');
590+
591+
const maxAge = get('CUBEJS_TOUCH_PRE_AGG_CACHE_MAX_AGE')
592+
.default(Math.round(touchPreAggregationTimeout / 2))
593+
.asIntPositive();
594+
595+
if (maxAge > touchPreAggregationTimeout) {
596+
throw new InvalidConfiguration(
597+
'CUBEJS_TOUCH_PRE_AGG_CACHE_MAX_AGE',
598+
maxAge,
599+
`Must be less or equal then CUBEJS_TOUCH_PRE_AGG_TIMEOUT (${touchPreAggregationTimeout}).`
600+
);
601+
}
602+
603+
return maxAge;
604+
},
605+
577606
/**
578607
* Expire time for touch records
579608
*/
580609
touchPreAggregationTimeout: (): number => get('CUBEJS_TOUCH_PRE_AGG_TIMEOUT')
581610
.default(60 * 60 * 24)
582-
.asInt(),
611+
.asIntPositive(),
583612

584613
/**
585614
* Expire time for touch records

packages/cubejs-query-orchestrator/src/orchestrator/PreAggregations.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {
2626
UnloadOptions,
2727
} from '@cubejs-backend/base-driver';
2828
import { CubeStoreDriver } from '@cubejs-backend/cubestore-driver';
29+
import LRUCache from 'lru-cache';
30+
2931
import { PreAggTableToTempTable, Query, QueryBody, QueryCache, QueryTuple, QueryWithParams } from './QueryCache';
3032
import { ContinueWaitError } from './ContinueWaitError';
3133
import { DriverFactory, DriverFactoryByDataSource } from './DriverFactory';
@@ -1968,6 +1970,8 @@ export class PreAggregations {
19681970

19691971
private readonly getQueueEventsBus: any;
19701972

1973+
private readonly touchCache: LRUCache<string, true>;
1974+
19711975
public constructor(
19721976
private readonly redisPrefix: string,
19731977
private readonly driverFactory: DriverFactoryByDataSource,
@@ -1984,14 +1988,20 @@ export class PreAggregations {
19841988
this.usedTablePersistTime = options.usedTablePersistTime || getEnv('dbQueryTimeout');
19851989
this.externalRefresh = options.externalRefresh;
19861990
this.getQueueEventsBus = options.getQueueEventsBus;
1991+
this.touchCache = new LRUCache({
1992+
max: getEnv('touchPreAggregationCacheMaxCount'),
1993+
maxAge: getEnv('touchPreAggregationCacheMaxAge') * 1000,
1994+
stale: false,
1995+
updateAgeOnGet: false
1996+
});
19871997
}
19881998

1989-
protected tablesUsedRedisKey(tableName) {
1999+
protected tablesUsedRedisKey(tableName: string): string {
19902000
// TODO add dataSource?
19912001
return this.queryCache.getKey('SQL_PRE_AGGREGATIONS_TABLES_USED', tableName);
19922002
}
19932003

1994-
protected tablesTouchRedisKey(tableName) {
2004+
protected tablesTouchRedisKey(tableName: string): string {
19952005
// TODO add dataSource?
19962006
return this.queryCache.getKey('SQL_PRE_AGGREGATIONS_TABLES_TOUCH', tableName);
19972007
}
@@ -2001,17 +2011,37 @@ export class PreAggregations {
20012011
return this.queryCache.getKey('SQL_PRE_AGGREGATIONS_REFRESH_END_REACHED', '');
20022012
}
20032013

2004-
public async addTableUsed(tableName) {
2005-
return this.queryCache.getCacheDriver().set(this.tablesUsedRedisKey(tableName), true, this.usedTablePersistTime);
2014+
public async addTableUsed(tableName: string): Promise<void> {
2015+
await this.queryCache.getCacheDriver().set(
2016+
this.tablesUsedRedisKey(tableName),
2017+
true,
2018+
this.usedTablePersistTime
2019+
);
20062020
}
20072021

20082022
public async tablesUsed() {
20092023
return (await this.queryCache.getCacheDriver().keysStartingWith(this.tablesUsedRedisKey('')))
20102024
.map(k => k.replace(this.tablesUsedRedisKey(''), ''));
20112025
}
20122026

2013-
public async updateLastTouch(tableName) {
2014-
return this.queryCache.getCacheDriver().set(this.tablesTouchRedisKey(tableName), new Date().getTime(), this.touchTablePersistTime);
2027+
public async updateLastTouch(tableName: string): Promise<void> {
2028+
if (this.touchCache.has(tableName)) {
2029+
return;
2030+
}
2031+
2032+
try {
2033+
this.touchCache.set(tableName, true);
2034+
2035+
await this.queryCache.getCacheDriver().set(
2036+
this.tablesTouchRedisKey(tableName),
2037+
new Date().getTime(),
2038+
this.touchTablePersistTime
2039+
);
2040+
} catch (e: unknown) {
2041+
this.touchCache.del(tableName);
2042+
2043+
throw e;
2044+
}
20152045
}
20162046

20172047
public async tablesTouched() {

0 commit comments

Comments
 (0)