Skip to content

Commit 59a37c9

Browse files
flovilmartRafael Santos
authored and
Rafael Santos
committed
Adds schema caching capabilities (5s by default) (parse-community#2286)
* Adds schema caching capabilities (off by default) * Use InMemoryCacheAdapter * Uses proper adapter to generate a cache * Fix bugs when running disabled cache * nits * nits * Use options object instead of boolean * Imrpove concurrency of loadSchema * Adds testing with SCHEMA_CACHE_ON * Use CacheController instead of generator - Makes caching SchemaCache use a generated prefix - Makes clearing the SchemaCache clear only the cached schema keys - Enable cache by default (ttl 5s)
1 parent f73e72c commit 59a37c9

14 files changed

+202
-44
lines changed

spec/ParseInstallation.spec.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@ let Parse = require('parse/node').Parse;
99
let rest = require('../src/rest');
1010
let request = require("request");
1111

12-
let config = new Config('test');
13-
let database = config.database;
12+
let config;
13+
let database;
1414
let defaultColumns = require('../src/Controllers/SchemaController').defaultColumns;
1515

1616
const installationSchema = { fields: Object.assign({}, defaultColumns._Default, defaultColumns._Installation) };
1717

1818
describe('Installations', () => {
19+
20+
beforeEach(() => {
21+
config = new Config('test');
22+
database = config.database;
23+
});
24+
1925
it_exclude_dbs(['postgres'])('creates an android installation with ids', (done) => {
2026
var installId = '12345678-abcd-abcd-abcd-123456789abc';
2127
var device = 'android';

spec/PointerPermissions.spec.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ var Schema = require('../src/Controllers/SchemaController');
44
var Config = require('../src/Config');
55

66
describe('Pointer Permissions', () => {
7+
8+
beforeEach(() => {
9+
new Config(Parse.applicationId).database.schemaCache.clear();
10+
});
11+
712
it_exclude_dbs(['postgres'])('should work with find', (done) => {
813
let config = new Config(Parse.applicationId);
914
let user = new Parse.User();

spec/RestQuery.spec.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@ var querystring = require('querystring');
99
var request = require('request');
1010
var rp = require('request-promise');
1111

12-
var config = new Config('test');
13-
let database = config.database;
12+
var config;
13+
let database;
1414
var nobody = auth.nobody(config);
1515

1616
describe('rest query', () => {
17+
18+
beforeEach(() => {
19+
config = new Config('test');
20+
database = config.database;
21+
});
22+
1723
it('basic query', (done) => {
1824
rest.create(config, nobody, 'TestObject', {}).then(() => {
1925
return rest.find(config, nobody, 'TestObject', {});

spec/Schema.spec.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ var Config = require('../src/Config');
44
var SchemaController = require('../src/Controllers/SchemaController');
55
var dd = require('deep-diff');
66

7-
var config = new Config('test');
7+
var config;
88

99
var hasAllPODobject = () => {
1010
var obj = new Parse.Object('HasAllPOD');
@@ -20,6 +20,10 @@ var hasAllPODobject = () => {
2020
};
2121

2222
describe('SchemaController', () => {
23+
beforeEach(() => {
24+
config = new Config('test');
25+
});
26+
2327
it('can validate one object', (done) => {
2428
config.database.loadSchema().then((schema) => {
2529
return schema.validateObject('TestObject', {a: 1, b: 'yo', c: false});

spec/helper.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ var defaultConfiguration = {
5959
myoauth: {
6060
module: path.resolve(__dirname, "myoauth") // relative path as it's run from src
6161
}
62-
},
62+
}
6363
};
6464

6565
let openConnections = {};

spec/schemas.spec.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ var masterKeyHeaders = {
116116
};
117117

118118
describe('schemas', () => {
119+
120+
beforeEach(() => {
121+
config.database.schemaCache.clear();
122+
});
123+
119124
it('requires the master key to get all schemas', (done) => {
120125
request.get({
121126
url: 'http://localhost:8378/1/schemas',

src/Config.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// mount is the URL for the root of the API; includes http, domain, etc.
44

55
import AppCache from './cache';
6+
import SchemaCache from './Controllers/SchemaCache';
7+
import DatabaseController from './Controllers/DatabaseController';
68

79
function removeTrailingSlash(str) {
810
if (!str) {
@@ -32,7 +34,14 @@ export class Config {
3234
this.fileKey = cacheInfo.fileKey;
3335
this.facebookAppIds = cacheInfo.facebookAppIds;
3436
this.allowClientClassCreation = cacheInfo.allowClientClassCreation;
35-
this.database = cacheInfo.databaseController;
37+
38+
// Create a new DatabaseController per request
39+
if (cacheInfo.databaseController) {
40+
const schemaCache = new SchemaCache(cacheInfo.cacheController, cacheInfo.schemaCacheTTL);
41+
this.database = new DatabaseController(cacheInfo.databaseController.adapter, schemaCache);
42+
}
43+
44+
this.schemaCacheTTL = cacheInfo.schemaCacheTTL;
3645

3746
this.serverURL = cacheInfo.serverURL;
3847
this.publicServerURL = removeTrailingSlash(cacheInfo.publicServerURL);

src/Controllers/CacheController.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ function joinKeys(...keys) {
1313
* eg "Role" or "Session"
1414
*/
1515
export class SubCache {
16-
constructor(prefix, cacheController) {
16+
constructor(prefix, cacheController, ttl) {
1717
this.prefix = prefix;
1818
this.cache = cacheController;
19+
this.ttl = ttl;
1920
}
2021

2122
get(key) {

src/Controllers/DatabaseController.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import _ from 'lodash';
77
var mongodb = require('mongodb');
88
var Parse = require('parse/node').Parse;
99

10-
var SchemaController = require('../Controllers/SchemaController');
10+
var SchemaController = require('./SchemaController');
11+
1112
const deepcopy = require('deepcopy');
1213

1314
function addWriteACL(query, acl) {
@@ -80,9 +81,9 @@ const validateQuery = query => {
8081
});
8182
}
8283

83-
function DatabaseController(adapter) {
84+
function DatabaseController(adapter, schemaCache) {
8485
this.adapter = adapter;
85-
86+
this.schemaCache = schemaCache;
8687
// We don't want a mutable this.schema, because then you could have
8788
// one request that uses different schemas for different parts of
8889
// it. Instead, use loadSchema to get a schema.
@@ -107,9 +108,9 @@ DatabaseController.prototype.validateClassName = function(className) {
107108
};
108109

109110
// Returns a promise for a schemaController.
110-
DatabaseController.prototype.loadSchema = function() {
111+
DatabaseController.prototype.loadSchema = function(options = {clearCache: false}) {
111112
if (!this.schemaPromise) {
112-
this.schemaPromise = SchemaController.load(this.adapter);
113+
this.schemaPromise = SchemaController.load(this.adapter, this.schemaCache, options);
113114
this.schemaPromise.then(() => delete this.schemaPromise,
114115
() => delete this.schemaPromise);
115116
}
@@ -805,8 +806,8 @@ const untransformObjectACL = ({_rperm, _wperm, ...output}) => {
805806
}
806807

807808
DatabaseController.prototype.deleteSchema = function(className) {
808-
return this.loadSchema()
809-
.then(schemaController => schemaController.getOneSchema(className))
809+
return this.loadSchema(true)
810+
.then(schemaController => schemaController.getOneSchema(className, true))
810811
.catch(error => {
811812
if (error === undefined) {
812813
return { fields: {} };

src/Controllers/SchemaCache.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
const MAIN_SCHEMA = "__MAIN_SCHEMA";
2+
const SCHEMA_CACHE_PREFIX = "__SCHEMA";
3+
const ALL_KEYS = "__ALL_KEYS";
4+
5+
import { randomString } from '../cryptoUtils';
6+
7+
export default class SchemaCache {
8+
cache: Object;
9+
10+
constructor(cacheController, ttl = 30) {
11+
this.ttl = ttl;
12+
if (typeof ttl == 'string') {
13+
this.ttl = parseInt(ttl);
14+
}
15+
this.cache = cacheController;
16+
this.prefix = SCHEMA_CACHE_PREFIX+randomString(20);
17+
}
18+
19+
put(key, value) {
20+
return this.cache.get(this.prefix+ALL_KEYS).then((allKeys) => {
21+
allKeys = allKeys || {};
22+
allKeys[key] = true;
23+
return Promise.all([this.cache.put(this.prefix+ALL_KEYS, allKeys, this.ttl), this.cache.put(key, value, this.ttl)]);
24+
});
25+
}
26+
27+
getAllClasses() {
28+
if (!this.ttl) {
29+
return Promise.resolve(null);
30+
}
31+
return this.cache.get(this.prefix+MAIN_SCHEMA);
32+
}
33+
34+
setAllClasses(schema) {
35+
if (!this.ttl) {
36+
return Promise.resolve(null);
37+
}
38+
return this.put(this.prefix+MAIN_SCHEMA, schema);
39+
}
40+
41+
setOneSchema(className, schema) {
42+
if (!this.ttl) {
43+
return Promise.resolve(null);
44+
}
45+
return this.put(this.prefix+className, schema);
46+
}
47+
48+
getOneSchema(className) {
49+
if (!this.ttl) {
50+
return Promise.resolve(null);
51+
}
52+
return this.cache.get(this.prefix+className);
53+
}
54+
55+
clear() {
56+
// That clears all caches...
57+
let promise = Promise.resolve();
58+
return this.cache.get(this.prefix+ALL_KEYS).then((allKeys) => {
59+
if (!allKeys) {
60+
return;
61+
}
62+
let promises = Object.keys(allKeys).map((key) => {
63+
return this.cache.del(key);
64+
});
65+
return Promise.all(promises);
66+
});
67+
}
68+
}

0 commit comments

Comments
 (0)