Skip to content

Commit b347bff

Browse files
steven-supersolidflovilmart
authored andcommitted
Add option to re-use schema cache between requests (#2979)
* Add option to reuse database controller between requests. Clear schema cache when deleting everything * Add test * Rename setting to persistSchemaCache to more accurately reflect effect * Repurpose option to determine whether to randomize cache prefix. Restore Config.js controller creation. Add tests * Fix bug with missing parameter passed to to SchemaCache * Renaming and formatting * Fix property name typo * Rename option to avoid double negative and still be falsey by default. Style fix
1 parent 801308d commit b347bff

File tree

7 files changed

+128
-34
lines changed

7 files changed

+128
-34
lines changed

spec/EnableSingleSchemaCache.spec.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
const auth = require('../src/Auth');
2+
const Config = require('../src/Config');
3+
const rest = require('../src/rest');
4+
5+
describe('Enable single schema cache', () => {
6+
beforeEach((done) => {
7+
reconfigureServer({
8+
enableSingleSchemaCache: true,
9+
schemaCacheTTL: 30000
10+
}).then(() => {
11+
done();
12+
});
13+
});
14+
15+
it('can perform multiple create and query operations', (done) => {
16+
let config = fakeRequestForConfig();
17+
let nobody = auth.nobody(config);
18+
rest.create(config, nobody, 'Foo', {type: 1}).then(() => {
19+
config = fakeRequestForConfig();
20+
nobody = auth.nobody(config);
21+
return rest.create(config, nobody, 'Foo', {type: 2});
22+
}).then(() => {
23+
config = fakeRequestForConfig();
24+
nobody = auth.nobody(config);
25+
return rest.create(config, nobody, 'Bar');
26+
}).then(() => {
27+
config = fakeRequestForConfig();
28+
nobody = auth.nobody(config);
29+
return rest.find(config, nobody, 'Bar', {type: 1});
30+
}).then((response) => {
31+
fail('Should throw error');
32+
done();
33+
}, (error) => {
34+
config = fakeRequestForConfig();
35+
nobody = auth.nobody(config);
36+
expect(error).toBeDefined();
37+
return rest.find(config, nobody, 'Foo', {type: 1});
38+
}).then((response) => {
39+
config = fakeRequestForConfig();
40+
nobody = auth.nobody(config);
41+
expect(response.results.length).toEqual(1);
42+
done();
43+
});
44+
});
45+
});
46+
47+
const fakeRequestForConfig = function() {
48+
return new Config('test');
49+
};

spec/SchemaCache.spec.js

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,66 @@ var InMemoryCacheAdapter = require('../src/Adapters/Cache/InMemoryCacheAdapter')
33
var SchemaCache = require('../src/Controllers/SchemaCache').default;
44

55
describe('SchemaCache', () => {
6-
var schemaCache;
6+
let cacheController;
77

8-
beforeEach(() => {
9-
var cacheAdapter = new InMemoryCacheAdapter({});
10-
var cacheController = new CacheController(cacheAdapter, 'appId');
11-
schemaCache = new SchemaCache(cacheController);
12-
});
8+
beforeEach(() => {
9+
const cacheAdapter = new InMemoryCacheAdapter({});
10+
cacheController = new CacheController(cacheAdapter, 'appId');
11+
});
1312

14-
it('can retrieve a single schema after all schemas stored', (done) => {
15-
var allSchemas = [{
16-
className: 'Class1'
17-
}, {
18-
className: 'Class2'
19-
}];
20-
schemaCache.setAllClasses(allSchemas);
21-
schemaCache.getOneSchema('Class2').then((schema) => {
22-
expect(schema).not.toBeNull();
23-
done();
24-
});
25-
});
13+
it('can retrieve a single schema after all schemas stored', (done) => {
14+
const schemaCache = new SchemaCache(cacheController);
15+
const allSchemas = [{
16+
className: 'Class1'
17+
}, {
18+
className: 'Class2'
19+
}];
20+
schemaCache.setAllClasses(allSchemas).then(() => {
21+
return schemaCache.getOneSchema('Class2');
22+
}).then((schema) => {
23+
expect(schema).not.toBeNull();
24+
done();
25+
});
26+
});
2627

27-
it('does not return all schemas after a single schema is stored', (done) => {
28-
var schema = {
29-
className: 'Class1'
30-
};
31-
schemaCache.setOneSchema('Class1', schema);
32-
schemaCache.getAllClasses().then((allSchemas) => {
33-
expect(allSchemas).toBeNull();
34-
done();
35-
});
36-
});
28+
it('does not return all schemas after a single schema is stored', (done) => {
29+
const schemaCache = new SchemaCache(cacheController);
30+
const schema = {
31+
className: 'Class1'
32+
};
33+
schemaCache.setOneSchema(schema.className, schema).then(() => {
34+
return schemaCache.getAllClasses();
35+
}).then((allSchemas) => {
36+
expect(allSchemas).toBeNull();
37+
done();
38+
});
39+
});
40+
41+
it('doesn\'t persist cached data by default', (done) => {
42+
const schemaCache = new SchemaCache(cacheController);
43+
const schema = {
44+
className: 'Class1'
45+
};
46+
schemaCache.setOneSchema(schema.className, schema).then(() => {
47+
const anotherSchemaCache = new SchemaCache(cacheController);
48+
return anotherSchemaCache.getOneSchema(schema.className).then((schema) => {
49+
expect(schema).toBeNull();
50+
done();
51+
});
52+
});
53+
});
54+
55+
it('can persist cached data', (done) => {
56+
const schemaCache = new SchemaCache(cacheController, 5000, true);
57+
const schema = {
58+
className: 'Class1'
59+
};
60+
schemaCache.setOneSchema(schema.className, schema).then(() => {
61+
const anotherSchemaCache = new SchemaCache(cacheController, 5000, true);
62+
return anotherSchemaCache.getOneSchema(schema.className).then((schema) => {
63+
expect(schema).not.toBeNull();
64+
done();
65+
});
66+
});
67+
});
3768
});

src/Config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ export class Config {
3737

3838
// Create a new DatabaseController per request
3939
if (cacheInfo.databaseController) {
40-
const schemaCache = new SchemaCache(cacheInfo.cacheController, cacheInfo.schemaCacheTTL);
40+
const schemaCache = new SchemaCache(cacheInfo.cacheController, cacheInfo.schemaCacheTTL, cacheInfo.enableSingleSchemaCache);
4141
this.database = new DatabaseController(cacheInfo.databaseController.adapter, schemaCache);
4242
}
4343

4444
this.schemaCacheTTL = cacheInfo.schemaCacheTTL;
45+
this.enableSingleSchemaCache = cacheInfo.enableSingleSchemaCache;
4546

4647
this.serverURL = cacheInfo.serverURL;
4748
this.publicServerURL = removeTrailingSlash(cacheInfo.publicServerURL);

src/Controllers/DatabaseController.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,10 @@ DatabaseController.prototype.canAddField = function(schema, className, object, a
515515
// Returns a promise.
516516
DatabaseController.prototype.deleteEverything = function() {
517517
this.schemaPromise = null;
518-
return this.adapter.deleteAllClasses();
518+
return Promise.all([
519+
this.adapter.deleteAllClasses(),
520+
this.schemaCache.clear()
521+
]);
519522
};
520523

521524
// Finds the keys in a query. Returns a Set. REST format only

src/Controllers/SchemaCache.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ import defaults from '../defaults';
88
export default class SchemaCache {
99
cache: Object;
1010

11-
constructor(cacheController, ttl = defaults.schemaCacheTTL) {
11+
constructor(cacheController, ttl = defaults.schemaCacheTTL, randomizePrefix = false) {
1212
this.ttl = ttl;
1313
if (typeof ttl == 'string') {
1414
this.ttl = parseInt(ttl);
1515
}
1616
this.cache = cacheController;
17-
this.prefix = SCHEMA_CACHE_PREFIX+randomString(20);
17+
this.prefix = SCHEMA_CACHE_PREFIX;
18+
if (!randomizePrefix) {
19+
this.prefix += randomString(20);
20+
}
1821
}
1922

2023
put(key, value) {

src/ParseServer.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ class ParseServer {
139139
expireInactiveSessions = defaults.expireInactiveSessions,
140140
revokeSessionOnPasswordReset = defaults.revokeSessionOnPasswordReset,
141141
schemaCacheTTL = defaults.schemaCacheTTL, // cache for 5s
142+
enableSingleSchemaCache = false,
142143
__indexBuildCompletionCallbackForTests = () => {},
143144
}) {
144145
// Initialize the node client SDK automatically
@@ -181,7 +182,7 @@ class ParseServer {
181182
const analyticsController = new AnalyticsController(analyticsControllerAdapter);
182183

183184
const liveQueryController = new LiveQueryController(liveQuery);
184-
const databaseController = new DatabaseController(databaseAdapter, new SchemaCache(cacheController, schemaCacheTTL));
185+
const databaseController = new DatabaseController(databaseAdapter, new SchemaCache(cacheController, schemaCacheTTL, enableSingleSchemaCache));
185186
const hooksController = new HooksController(appId, databaseController, webhookKey);
186187

187188
const dbInitPromise = databaseController.performInitizalization();
@@ -221,7 +222,8 @@ class ParseServer {
221222
jsonLogs,
222223
revokeSessionOnPasswordReset,
223224
databaseController,
224-
schemaCacheTTL
225+
schemaCacheTTL,
226+
enableSingleSchemaCache
225227
});
226228

227229
// To maintain compatibility. TODO: Remove in some version that breaks backwards compatability

src/cli/definitions/parse-server.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,11 @@ export default {
195195
help: "The TTL for caching the schema for optimizing read/write operations. You should put a long TTL when your DB is in production. default to 0; disabled.",
196196
action: numberParser("schemaCacheTTL"),
197197
},
198+
"enableSingleSchemaCache": {
199+
env: "PARSE_SERVER_ENABLE_SINGLE_SCHEMA_CACHE",
200+
help: "Use a single schema cache shared across requests. Reduces number of queries made to _SCHEMA. Defaults to false, i.e. unique schema cache per request.",
201+
action: booleanParser
202+
},
198203
"cluster": {
199204
help: "Run with cluster, optionally set the number of processes default to os.cpus().length",
200205
action: numberOrBoolParser("cluster")

0 commit comments

Comments
 (0)