Skip to content

Commit 47b256d

Browse files
willhausmanpaveltiunov
authored andcommitted
feat: hooks for dynamic schemas (cube-js#287). Thanks to @willhausman!
* hook for defining a custom context per request. * use lru-cache for compilerApis * docs for create options * contextToDataSourceId option * Revert "hook for defining a custom context per request." This reverts commit 2a28077. * revert requestToContext create option
1 parent dac62c0 commit 47b256d

File tree

5 files changed

+62
-10
lines changed

5 files changed

+62
-10
lines changed

docs/Cube.js-Backend/@cubejs-backend-server-core.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,15 @@ Both [CubejsServerCore](@cubejs-backend-server-core) and [CubejsServer](@cubejs-
4949
driverFactory: (context: DriverContext) => BaseDriver,
5050
externalDriverFactory: (context: RequestContext) => BaseDriver,
5151
contextToAppId: (context: RequestContext) => String,
52+
contextToDataSourceId: (context: RequestContext) => String,
5253
repositoryFactory: (context: RequestContext) => String,
5354
checkAuthMiddleware: (req: ExpressRequest, res: ExpressResponse, next: ExpressMiddleware) => any,
5455
queryTransformer: (query: Object, context: RequestContext) => Object,
5556
preAggregationsSchema: String | (context: RequestContext) => String,
5657
schemaVersion: (context: RequestContext) => String,
58+
compilerCacheSize: Number,
59+
maxCompilerCacheKeepAlive: Number,
60+
updateCompilerCacheKeepAlive: Boolean,
5761
telemetry: Boolean,
5862
allowUngroupedWithoutPrimaryKey: Boolean,
5963
orchestratorOptions: {
@@ -175,6 +179,17 @@ CubejsServerCore.create({
175179
})
176180
```
177181

182+
### contextToDataSourceId
183+
184+
`contextToDataSourceId` is a function to determine a DataSource Id which is used to override the `contextToAppId` caching key for managing connection pools.
185+
186+
```javascript
187+
CubejsServerCore.create({
188+
contextToAppId: ({ authInfo }) => `CUBEJS_APP_${authInfo.user_id}`,
189+
contextToDataSourceId: ({ authInfo }) => `CUBEJS_APP_${authInfo.tenantId}`
190+
});
191+
```
192+
178193
### repositoryFactory
179194

180195
This option allows to customize the repository for Cube.js data schema files. It
@@ -260,6 +275,18 @@ CubejsServerCore.create({
260275
});
261276
```
262277

278+
### compilerCacheSize
279+
280+
Maximum number of compiled schemas to persist with in-memory cache. Defaults to 250, but optimum value will depend on deployed environment. When the max is reached, will start dropping the least recently used schemas from the cache.
281+
282+
### maxCompilerCacheKeepAlive
283+
284+
Maximum length of time in ms to keep compiled schemas in memory. Default keeps schemas in memory indefinitely.
285+
286+
### updateCompilerCacheKeepAlive
287+
288+
Providing `updateCompilerCacheKeepAlive: true` keeps frequently used schemas in memory by reseting their `maxCompilerCacheKeepAlive` every time they are accessed.
289+
263290
### allowUngroupedWithoutPrimaryKey
264291

265292
Providing `allowUngroupedWithoutPrimaryKey: true` disables primary key inclusion check for `ungrouped` queries.

packages/cubejs-server-core/core/index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ declare module '@cubejs-backend/server-core' {
99
checkAuthMiddleware?: (req: any, res: any, next: any) => any;
1010
queryTransformer?: () => (query: any, context: any) => any;
1111
contextToAppId?: any;
12+
contextToDataSourceId?: (context: any) => string;
1213
devServer?: boolean;
1314
dbType?: DatabaseType;
1415
driverFactory?: DriverFactory;
@@ -18,6 +19,9 @@ declare module '@cubejs-backend/server-core' {
1819
preAggregationsSchema?: string | (() => string)
1920
repositoryFactory?: any;
2021
schemaPath?: string;
22+
compilerCacheSize?: number;
23+
maxCompilerCacheKeepAlive?: number;
24+
updateCompilerCacheKeepAlive?: boolean;
2125
}
2226

2327
export interface DriverFactory {

packages/cubejs-server-core/core/index.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const ApiGateway = require('@cubejs-backend/api-gateway');
33
const crypto = require('crypto');
44
const fs = require('fs-extra');
55
const path = require('path');
6+
const LRUCache = require('lru-cache');
67
const CompilerApi = require('./CompilerApi');
78
const OrchestratorApi = require('./OrchestratorApi');
89
const FileRepository = require('./FileRepository');
@@ -122,14 +123,24 @@ class CubejsServerCore {
122123
() => options.externalDbType;
123124
this.preAggregationsSchema =
124125
typeof options.preAggregationsSchema === 'function' ? options.preAggregationsSchema : () => options.preAggregationsSchema;
125-
this.appIdToCompilerApi = {};
126+
this.compilerCache = new LRUCache({
127+
max: options.compilerCacheSize || 250,
128+
maxAge: options.maxCompilerCacheKeepAlive,
129+
updateAgeOnGet: options.updateCompilerCacheKeepAlive
130+
});
126131
this.dataSourceIdToOrchestratorApi = {};
127132
this.contextToAppId = options.contextToAppId || (() => process.env.CUBEJS_APP || 'STANDALONE');
133+
this.contextToDataSourceId = options.contextToDataSourceId || this.defaultContextToDataSourceId.bind(this);
128134
this.orchestratorOptions =
129135
typeof options.orchestratorOptions === 'function' ?
130136
options.orchestratorOptions :
131137
() => options.orchestratorOptions;
132138

139+
// proactively free up old cache values occassionally
140+
if (options.maxCompilerCacheKeepAlive) {
141+
setInterval(() => this.compilerCache.prune(), options.maxCompilerCacheKeepAlive);
142+
}
143+
133144
const { machineIdSync } = require('node-machine-id');
134145
let anonymousId = 'unknown';
135146
try {
@@ -263,20 +274,22 @@ class CubejsServerCore {
263274

264275
getCompilerApi(context) {
265276
const appId = this.contextToAppId(context);
266-
if (!this.appIdToCompilerApi[appId]) {
267-
this.appIdToCompilerApi[appId] = this.createCompilerApi(
277+
let compilerApi = this.compilerCache.get(appId);
278+
if (!compilerApi) {
279+
compilerApi = this.createCompilerApi(
268280
this.repositoryFactory(context), {
269281
dbType: (dataSourceContext) => this.contextToDbType({ ...context, ...dataSourceContext }),
270282
externalDbType: this.contextToExternalDbType(context),
271283
schemaVersion: this.options.schemaVersion && (() => this.options.schemaVersion(context)),
272284
preAggregationsSchema: this.preAggregationsSchema(context)
273285
}
274286
);
287+
this.compilerCache.set(appId, compilerApi);
275288
}
276-
return this.appIdToCompilerApi[appId];
289+
return compilerApi;
277290
}
278291

279-
contextToDataSourceId(context) {
292+
defaultContextToDataSourceId(context) {
280293
return `${this.contextToAppId(context)}_${context.dataSource}`;
281294
}
282295

packages/cubejs-server-core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"cross-spawn": "^6.0.5",
3939
"fs-extra": "^7.0.1",
4040
"jsonwebtoken": "^8.4.0",
41+
"lru-cache": "^5.1.1",
4142
"node-fetch": "^2.6.0",
4243
"node-machine-id": "^1.1.10",
4344
"prettier": "^1.16.4",

packages/cubejs-server-core/yarn.lock

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -833,10 +833,10 @@
833833
moment-timezone "^0.5.27"
834834
ramda "^0.25.0"
835835

836-
"@cubejs-backend/query-orchestrator@^0.12.1":
837-
version "0.12.1"
838-
resolved "https://registry.yarnpkg.com/@cubejs-backend/query-orchestrator/-/query-orchestrator-0.12.1.tgz#e8b7ea46f9a7e695b6d7a299616b68cb0ae42818"
839-
integrity sha512-yINa38E+BWTrKS42ByqL0qpTDZAuWVNMWj1Ea+ajFtyInu2MDD7sUCF5HkIMD0H8m4nMtsXl/D4X0rZb0EthCw==
836+
"@cubejs-backend/query-orchestrator@^0.12.2":
837+
version "0.12.2"
838+
resolved "https://registry.yarnpkg.com/@cubejs-backend/query-orchestrator/-/query-orchestrator-0.12.2.tgz#13c3be7c3fa150a7a8047810143f0eb4bc5854bb"
839+
integrity sha512-fy+8Mfhez1MF4eVpGFpBjYHxqf4AX9nRBqXKg+8BBrc6Rz7xLOoh3xvI7IGMrtahCo+HyuCgHpBqejryB24TEQ==
840840
dependencies:
841841
ramda "^0.24.1"
842842
redis "^2.8.0"
@@ -3620,6 +3620,13 @@ loose-envify@^1.0.0:
36203620
dependencies:
36213621
js-tokens "^3.0.0 || ^4.0.0"
36223622

3623+
lru-cache@^5.1.1:
3624+
version "5.1.1"
3625+
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
3626+
integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
3627+
dependencies:
3628+
yallist "^3.0.2"
3629+
36233630
lz-string@^1.4.4:
36243631
version "1.4.4"
36253632
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
@@ -5566,7 +5573,7 @@ y18n@^4.0.0:
55665573
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
55675574
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
55685575

5569-
yallist@^3.0.0, yallist@^3.0.3:
5576+
yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3:
55705577
version "3.1.1"
55715578
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
55725579
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==

0 commit comments

Comments
 (0)