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
This commit is contained in:
committed by
Florent Vilmart
parent
801308d9b7
commit
b347bff641
49
spec/EnableSingleSchemaCache.spec.js
Normal file
49
spec/EnableSingleSchemaCache.spec.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
const auth = require('../src/Auth');
|
||||||
|
const Config = require('../src/Config');
|
||||||
|
const rest = require('../src/rest');
|
||||||
|
|
||||||
|
describe('Enable single schema cache', () => {
|
||||||
|
beforeEach((done) => {
|
||||||
|
reconfigureServer({
|
||||||
|
enableSingleSchemaCache: true,
|
||||||
|
schemaCacheTTL: 30000
|
||||||
|
}).then(() => {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can perform multiple create and query operations', (done) => {
|
||||||
|
let config = fakeRequestForConfig();
|
||||||
|
let nobody = auth.nobody(config);
|
||||||
|
rest.create(config, nobody, 'Foo', {type: 1}).then(() => {
|
||||||
|
config = fakeRequestForConfig();
|
||||||
|
nobody = auth.nobody(config);
|
||||||
|
return rest.create(config, nobody, 'Foo', {type: 2});
|
||||||
|
}).then(() => {
|
||||||
|
config = fakeRequestForConfig();
|
||||||
|
nobody = auth.nobody(config);
|
||||||
|
return rest.create(config, nobody, 'Bar');
|
||||||
|
}).then(() => {
|
||||||
|
config = fakeRequestForConfig();
|
||||||
|
nobody = auth.nobody(config);
|
||||||
|
return rest.find(config, nobody, 'Bar', {type: 1});
|
||||||
|
}).then((response) => {
|
||||||
|
fail('Should throw error');
|
||||||
|
done();
|
||||||
|
}, (error) => {
|
||||||
|
config = fakeRequestForConfig();
|
||||||
|
nobody = auth.nobody(config);
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
return rest.find(config, nobody, 'Foo', {type: 1});
|
||||||
|
}).then((response) => {
|
||||||
|
config = fakeRequestForConfig();
|
||||||
|
nobody = auth.nobody(config);
|
||||||
|
expect(response.results.length).toEqual(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const fakeRequestForConfig = function() {
|
||||||
|
return new Config('test');
|
||||||
|
};
|
||||||
@@ -3,35 +3,66 @@ var InMemoryCacheAdapter = require('../src/Adapters/Cache/InMemoryCacheAdapter')
|
|||||||
var SchemaCache = require('../src/Controllers/SchemaCache').default;
|
var SchemaCache = require('../src/Controllers/SchemaCache').default;
|
||||||
|
|
||||||
describe('SchemaCache', () => {
|
describe('SchemaCache', () => {
|
||||||
var schemaCache;
|
let cacheController;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
var cacheAdapter = new InMemoryCacheAdapter({});
|
const cacheAdapter = new InMemoryCacheAdapter({});
|
||||||
var cacheController = new CacheController(cacheAdapter, 'appId');
|
cacheController = new CacheController(cacheAdapter, 'appId');
|
||||||
schemaCache = new SchemaCache(cacheController);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can retrieve a single schema after all schemas stored', (done) => {
|
it('can retrieve a single schema after all schemas stored', (done) => {
|
||||||
var allSchemas = [{
|
const schemaCache = new SchemaCache(cacheController);
|
||||||
|
const allSchemas = [{
|
||||||
className: 'Class1'
|
className: 'Class1'
|
||||||
}, {
|
}, {
|
||||||
className: 'Class2'
|
className: 'Class2'
|
||||||
}];
|
}];
|
||||||
schemaCache.setAllClasses(allSchemas);
|
schemaCache.setAllClasses(allSchemas).then(() => {
|
||||||
schemaCache.getOneSchema('Class2').then((schema) => {
|
return schemaCache.getOneSchema('Class2');
|
||||||
|
}).then((schema) => {
|
||||||
expect(schema).not.toBeNull();
|
expect(schema).not.toBeNull();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not return all schemas after a single schema is stored', (done) => {
|
it('does not return all schemas after a single schema is stored', (done) => {
|
||||||
var schema = {
|
const schemaCache = new SchemaCache(cacheController);
|
||||||
|
const schema = {
|
||||||
className: 'Class1'
|
className: 'Class1'
|
||||||
};
|
};
|
||||||
schemaCache.setOneSchema('Class1', schema);
|
schemaCache.setOneSchema(schema.className, schema).then(() => {
|
||||||
schemaCache.getAllClasses().then((allSchemas) => {
|
return schemaCache.getAllClasses();
|
||||||
|
}).then((allSchemas) => {
|
||||||
expect(allSchemas).toBeNull();
|
expect(allSchemas).toBeNull();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('doesn\'t persist cached data by default', (done) => {
|
||||||
|
const schemaCache = new SchemaCache(cacheController);
|
||||||
|
const schema = {
|
||||||
|
className: 'Class1'
|
||||||
|
};
|
||||||
|
schemaCache.setOneSchema(schema.className, schema).then(() => {
|
||||||
|
const anotherSchemaCache = new SchemaCache(cacheController);
|
||||||
|
return anotherSchemaCache.getOneSchema(schema.className).then((schema) => {
|
||||||
|
expect(schema).toBeNull();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can persist cached data', (done) => {
|
||||||
|
const schemaCache = new SchemaCache(cacheController, 5000, true);
|
||||||
|
const schema = {
|
||||||
|
className: 'Class1'
|
||||||
|
};
|
||||||
|
schemaCache.setOneSchema(schema.className, schema).then(() => {
|
||||||
|
const anotherSchemaCache = new SchemaCache(cacheController, 5000, true);
|
||||||
|
return anotherSchemaCache.getOneSchema(schema.className).then((schema) => {
|
||||||
|
expect(schema).not.toBeNull();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,11 +37,12 @@ export class Config {
|
|||||||
|
|
||||||
// Create a new DatabaseController per request
|
// Create a new DatabaseController per request
|
||||||
if (cacheInfo.databaseController) {
|
if (cacheInfo.databaseController) {
|
||||||
const schemaCache = new SchemaCache(cacheInfo.cacheController, cacheInfo.schemaCacheTTL);
|
const schemaCache = new SchemaCache(cacheInfo.cacheController, cacheInfo.schemaCacheTTL, cacheInfo.enableSingleSchemaCache);
|
||||||
this.database = new DatabaseController(cacheInfo.databaseController.adapter, schemaCache);
|
this.database = new DatabaseController(cacheInfo.databaseController.adapter, schemaCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.schemaCacheTTL = cacheInfo.schemaCacheTTL;
|
this.schemaCacheTTL = cacheInfo.schemaCacheTTL;
|
||||||
|
this.enableSingleSchemaCache = cacheInfo.enableSingleSchemaCache;
|
||||||
|
|
||||||
this.serverURL = cacheInfo.serverURL;
|
this.serverURL = cacheInfo.serverURL;
|
||||||
this.publicServerURL = removeTrailingSlash(cacheInfo.publicServerURL);
|
this.publicServerURL = removeTrailingSlash(cacheInfo.publicServerURL);
|
||||||
|
|||||||
@@ -515,7 +515,10 @@ DatabaseController.prototype.canAddField = function(schema, className, object, a
|
|||||||
// Returns a promise.
|
// Returns a promise.
|
||||||
DatabaseController.prototype.deleteEverything = function() {
|
DatabaseController.prototype.deleteEverything = function() {
|
||||||
this.schemaPromise = null;
|
this.schemaPromise = null;
|
||||||
return this.adapter.deleteAllClasses();
|
return Promise.all([
|
||||||
|
this.adapter.deleteAllClasses(),
|
||||||
|
this.schemaCache.clear()
|
||||||
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Finds the keys in a query. Returns a Set. REST format only
|
// Finds the keys in a query. Returns a Set. REST format only
|
||||||
|
|||||||
@@ -8,13 +8,16 @@ import defaults from '../defaults';
|
|||||||
export default class SchemaCache {
|
export default class SchemaCache {
|
||||||
cache: Object;
|
cache: Object;
|
||||||
|
|
||||||
constructor(cacheController, ttl = defaults.schemaCacheTTL) {
|
constructor(cacheController, ttl = defaults.schemaCacheTTL, randomizePrefix = false) {
|
||||||
this.ttl = ttl;
|
this.ttl = ttl;
|
||||||
if (typeof ttl == 'string') {
|
if (typeof ttl == 'string') {
|
||||||
this.ttl = parseInt(ttl);
|
this.ttl = parseInt(ttl);
|
||||||
}
|
}
|
||||||
this.cache = cacheController;
|
this.cache = cacheController;
|
||||||
this.prefix = SCHEMA_CACHE_PREFIX+randomString(20);
|
this.prefix = SCHEMA_CACHE_PREFIX;
|
||||||
|
if (!randomizePrefix) {
|
||||||
|
this.prefix += randomString(20);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
put(key, value) {
|
put(key, value) {
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ class ParseServer {
|
|||||||
expireInactiveSessions = defaults.expireInactiveSessions,
|
expireInactiveSessions = defaults.expireInactiveSessions,
|
||||||
revokeSessionOnPasswordReset = defaults.revokeSessionOnPasswordReset,
|
revokeSessionOnPasswordReset = defaults.revokeSessionOnPasswordReset,
|
||||||
schemaCacheTTL = defaults.schemaCacheTTL, // cache for 5s
|
schemaCacheTTL = defaults.schemaCacheTTL, // cache for 5s
|
||||||
|
enableSingleSchemaCache = false,
|
||||||
__indexBuildCompletionCallbackForTests = () => {},
|
__indexBuildCompletionCallbackForTests = () => {},
|
||||||
}) {
|
}) {
|
||||||
// Initialize the node client SDK automatically
|
// Initialize the node client SDK automatically
|
||||||
@@ -181,7 +182,7 @@ class ParseServer {
|
|||||||
const analyticsController = new AnalyticsController(analyticsControllerAdapter);
|
const analyticsController = new AnalyticsController(analyticsControllerAdapter);
|
||||||
|
|
||||||
const liveQueryController = new LiveQueryController(liveQuery);
|
const liveQueryController = new LiveQueryController(liveQuery);
|
||||||
const databaseController = new DatabaseController(databaseAdapter, new SchemaCache(cacheController, schemaCacheTTL));
|
const databaseController = new DatabaseController(databaseAdapter, new SchemaCache(cacheController, schemaCacheTTL, enableSingleSchemaCache));
|
||||||
const hooksController = new HooksController(appId, databaseController, webhookKey);
|
const hooksController = new HooksController(appId, databaseController, webhookKey);
|
||||||
|
|
||||||
const dbInitPromise = databaseController.performInitizalization();
|
const dbInitPromise = databaseController.performInitizalization();
|
||||||
@@ -221,7 +222,8 @@ class ParseServer {
|
|||||||
jsonLogs,
|
jsonLogs,
|
||||||
revokeSessionOnPasswordReset,
|
revokeSessionOnPasswordReset,
|
||||||
databaseController,
|
databaseController,
|
||||||
schemaCacheTTL
|
schemaCacheTTL,
|
||||||
|
enableSingleSchemaCache
|
||||||
});
|
});
|
||||||
|
|
||||||
// To maintain compatibility. TODO: Remove in some version that breaks backwards compatability
|
// To maintain compatibility. TODO: Remove in some version that breaks backwards compatability
|
||||||
|
|||||||
@@ -195,6 +195,11 @@ export default {
|
|||||||
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.",
|
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.",
|
||||||
action: numberParser("schemaCacheTTL"),
|
action: numberParser("schemaCacheTTL"),
|
||||||
},
|
},
|
||||||
|
"enableSingleSchemaCache": {
|
||||||
|
env: "PARSE_SERVER_ENABLE_SINGLE_SCHEMA_CACHE",
|
||||||
|
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.",
|
||||||
|
action: booleanParser
|
||||||
|
},
|
||||||
"cluster": {
|
"cluster": {
|
||||||
help: "Run with cluster, optionally set the number of processes default to os.cpus().length",
|
help: "Run with cluster, optionally set the number of processes default to os.cpus().length",
|
||||||
action: numberOrBoolParser("cluster")
|
action: numberOrBoolParser("cluster")
|
||||||
|
|||||||
Reference in New Issue
Block a user