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:
Steven Shipton
2016-11-02 23:05:23 +00:00
committed by Florent Vilmart
parent 801308d9b7
commit b347bff641
7 changed files with 128 additions and 34 deletions

View 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');
};

View File

@@ -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);
className: 'Class1' const allSchemas = [{
}, { className: 'Class1'
className: 'Class2' }, {
}]; className: 'Class2'
schemaCache.setAllClasses(allSchemas); }];
schemaCache.getOneSchema('Class2').then((schema) => { schemaCache.setAllClasses(allSchemas).then(() => {
expect(schema).not.toBeNull(); return schemaCache.getOneSchema('Class2');
done(); }).then((schema) => {
}); expect(schema).not.toBeNull();
}); 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);
className: 'Class1' const schema = {
}; className: 'Class1'
schemaCache.setOneSchema('Class1', schema); };
schemaCache.getAllClasses().then((allSchemas) => { schemaCache.setOneSchema(schema.className, schema).then(() => {
expect(allSchemas).toBeNull(); return schemaCache.getAllClasses();
done(); }).then((allSchemas) => {
}); expect(allSchemas).toBeNull();
}); 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();
});
});
});
}); });

View File

@@ -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);

View File

@@ -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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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")