Skip to content

Commit 22b6253

Browse files
dplewisArul-
authored andcommitted
Improve single schema cache (parse-community#7214)
* Initial Commit * fix flaky test * temporary set ci timeout * turn off ci check * fix postgres tests * fix tests * node flaky test * remove improvements * Update SchemaPerformance.spec.js * fix tests * revert ci * Create Singleton Object * properly clear cache testing * Cleanup * remove fit * try PushController.spec * try push test rewrite * try push enqueue time * Increase test timeout * remove pg server creation test * xit push tests * more xit * remove skipped tests * Fix conflicts * reduce ci timeout * fix push tests * Revert "fix push tests" This reverts commit 05aba62. * improve initialization * fix flaky tests * xit flaky test * Update CHANGELOG.md * enable debug logs * Update LogsRouter.spec.js * create initial indexes in series * lint * horizontal scaling documentation * Update Changelog * change horizontalScaling db option * Add enableSchemaHooks option * move enableSchemaHooks to databaseOptions
1 parent a2256c3 commit 22b6253

39 files changed

+1182
-1137
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ ___
8989
## Unreleased (Master Branch)
9090
[Full Changelog](https://github.com/parse-community/parse-server/compare/4.5.0...master)
9191
### Breaking Changes
92+
Leveraging database real-time hooks, schema caching has been drastically improved. These improvements allows for reduced calls to the DB, faster queries and prevention of memory leaks. A breaking change can occur if you are horizontally scaling Parse Server (multiple Parse Server instances connecting to the same DB). Set `databaseOptions: { enableSchemaHooks: true }` parameter in [Parse Server Options](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) (`enableSingleSchemaCache` and `schemaCacheTTL` have been removed). If you are horizontal scaling instances connected to MongoDB, you must use replica set clusters with WiredTiger, see [ChangeStream](https://docs.mongodb.com/manual/changeStreams/#availability)
93+
94+
The new schema cache uses a singleton object that is stored in-memory. In a horizontally scaled environment, if you update the schema in one instance the DB hooks will update the schema in all other instances. `databaseOptions: { enableSchemaHooks: true }` enables the DB hooks. If you have multiple server instances but `databaseOptions: { enableSchemaHooks: false }`, your schema maybe out of sync in your instances (resyncing will happen if an instance restarts). (Diamond Lewis, SebC) [#7214](https://github.com/parse-community/parse-server/issues/7214)
9295
- Added file upload restriction. File upload is now only allowed for authenticated users by default for improved security. To allow file upload also for Anonymous Users or Public, set the `fileUpload` parameter in the [Parse Server Options](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) (dblythy, Manuel Trezza) [#7071](https://github.com/parse-community/parse-server/pull/7071)
9396
### Notable Changes
9497
- Added Parse Server Security Check to report weak security settings (Manuel Trezza, dblythy) [#7247](https://github.com/parse-community/parse-server/issues/7247)

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"@parse/push-adapter": "3.4.0",
2828
"@parse/s3-files-adapter": "1.6.0",
2929
"@parse/simple-mailgun-adapter": "1.2.0",
30-
"apollo-server-express": "2.20.0",
30+
"apollo-server-express": "2.21.0",
3131
"bcryptjs": "2.4.3",
3232
"body-parser": "1.19.0",
3333
"commander": "5.1.0",
@@ -88,6 +88,7 @@
8888
"form-data": "3.0.0",
8989
"husky": "4.2.5",
9090
"jasmine": "3.5.0",
91+
"jasmine-spec-reporter": "6.0.0",
9192
"jsdoc": "3.6.3",
9293
"jsdoc-babel": "0.5.0",
9394
"lint-staged": "10.2.3",

resources/buildConfigDefinitions.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ function getENVPrefix(iface) {
5353
'PasswordPolicyOptions' : 'PARSE_SERVER_PASSWORD_POLICY_',
5454
'FileUploadOptions' : 'PARSE_SERVER_FILE_UPLOAD_',
5555
'SecurityOptions': 'PARSE_SERVER_SECURITY_',
56+
'DatabaseOptions': 'PARSE_SERVER_DATABASE_'
5657
}
5758
if (options[iface.id.name]) {
5859
return options[iface.id.name]
@@ -168,7 +169,7 @@ function parseDefaultValue(elt, value, t) {
168169
if (type == 'NumberOrBoolean') {
169170
literalValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
170171
}
171-
const literalTypes = ['Object', 'SecurityOptions', 'PagesRoute', 'IdempotencyOptions','FileUploadOptions','CustomPagesOptions', 'PagesCustomUrlsOptions', 'PagesOptions'];
172+
const literalTypes = ['Object', 'SecurityOptions', 'PagesRoute', 'IdempotencyOptions','FileUploadOptions','CustomPagesOptions', 'PagesCustomUrlsOptions', 'PagesOptions', 'DatabaseOptions'];
172173
if (literalTypes.includes(type)) {
173174
const object = parsers.objectParser(value);
174175
const props = Object.keys(object).map((key) => {

spec/EnableSingleSchemaCache.spec.js

Lines changed: 0 additions & 58 deletions
This file was deleted.

spec/LogsRouter.spec.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ const WinstonLoggerAdapter = require('../lib/Adapters/Logger/WinstonLoggerAdapte
88

99
const loggerController = new LoggerController(new WinstonLoggerAdapter());
1010

11-
describe('LogsRouter', () => {
11+
describe_only(() => {
12+
return process.env.PARSE_SERVER_LOG_LEVEL !== 'debug';
13+
})('LogsRouter', () => {
1214
it('can check valid master key of request', done => {
1315
// Make mock request
1416
const request = {

spec/MongoStorageAdapter.spec.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const fakeClient = {
1818
describe_only_db('mongo')('MongoStorageAdapter', () => {
1919
beforeEach(done => {
2020
new MongoStorageAdapter({ uri: databaseURI }).deleteAllClasses().then(done, fail);
21+
Config.get(Parse.applicationId).schemaCache.clear();
2122
});
2223

2324
it('auto-escapes symbols in auth information', () => {
@@ -314,6 +315,8 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
314315
await user.signUp();
315316

316317
const database = Config.get(Parse.applicationId).database;
318+
await database.adapter.dropAllIndexes('_User');
319+
317320
const preIndexPlan = await database.find(
318321
'_User',
319322
{ username: 'bugs' },
@@ -549,5 +552,33 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
549552
});
550553
});
551554
});
555+
556+
describe('watch _SCHEMA', () => {
557+
it('should change', async done => {
558+
const adapter = new MongoStorageAdapter({
559+
uri: databaseURI,
560+
collectionPrefix: '',
561+
mongoOptions: { enableSchemaHooks: true },
562+
});
563+
await reconfigureServer({ databaseAdapter: adapter });
564+
expect(adapter.enableSchemaHooks).toBe(true);
565+
spyOn(adapter, '_onchange');
566+
const schema = {
567+
fields: {
568+
array: { type: 'Array' },
569+
object: { type: 'Object' },
570+
date: { type: 'Date' },
571+
},
572+
};
573+
574+
await adapter.createClass('Stuff', schema);
575+
const myClassSchema = await adapter.getClass('Stuff');
576+
expect(myClassSchema).toBeDefined();
577+
setTimeout(() => {
578+
expect(adapter._onchange).toHaveBeenCalled();
579+
done();
580+
}, 5000);
581+
});
582+
});
552583
}
553584
});

spec/ParseGraphQLController.spec.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,20 @@ describe('ParseGraphQLController', () => {
2828
return graphQLConfigRecord;
2929
};
3030

31+
<<<<<<< HEAD
3132
beforeAll(async () => {
3233
parseServer = await global.reconfigureServer({
3334
schemaCacheTTL: 100,
3435
});
3536
databaseController = parseServer.config.databaseController;
3637
cacheController = parseServer.config.cacheController;
38+
=======
39+
beforeEach(async () => {
40+
if (!parseServer) {
41+
parseServer = await global.reconfigureServer();
42+
databaseController = parseServer.config.databaseController;
43+
cacheController = parseServer.config.cacheController;
44+
>>>>>>> a02014f... Improve single schema cache (#7214)
3745

3846
const defaultFind = databaseController.find.bind(databaseController);
3947
databaseController.find = async (className, query, ...args) => {

spec/ParseGraphQLSchema.spec.js

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@ describe('ParseGraphQLSchema', () => {
99
let parseGraphQLSchema;
1010
const appId = 'test';
1111

12-
beforeAll(async () => {
13-
parseServer = await global.reconfigureServer({
14-
schemaCacheTTL: 100,
15-
});
12+
beforeEach(async () => {
13+
parseServer = await global.reconfigureServer();
1614
databaseController = parseServer.config.databaseController;
1715
parseGraphQLController = parseServer.config.parseGraphQLController;
1816
parseGraphQLSchema = new ParseGraphQLSchema({
@@ -68,7 +66,7 @@ describe('ParseGraphQLSchema', () => {
6866
const graphQLSubscriptions = parseGraphQLSchema.graphQLSubscriptions;
6967
const newClassObject = new Parse.Object('NewClass');
7068
await newClassObject.save();
71-
await databaseController.schemaCache.clear();
69+
await parseServer.config.schemaCache.clear();
7270
await new Promise(resolve => setTimeout(resolve, 200));
7371
await parseGraphQLSchema.load();
7472
expect(parseClasses).not.toBe(parseGraphQLSchema.parseClasses);
@@ -426,14 +424,14 @@ describe('ParseGraphQLSchema', () => {
426424
log: defaultLogger,
427425
appId,
428426
});
429-
await parseGraphQLSchema.databaseController.schemaCache.clear();
427+
await parseGraphQLSchema.schemaCache.clear();
430428
const schema1 = await parseGraphQLSchema.load();
431429
const types1 = parseGraphQLSchema.graphQLTypes;
432430
const queries1 = parseGraphQLSchema.graphQLQueries;
433431
const mutations1 = parseGraphQLSchema.graphQLMutations;
434432
const user = new Parse.Object('User');
435433
await user.save();
436-
await parseGraphQLSchema.databaseController.schemaCache.clear();
434+
await parseGraphQLSchema.schemaCache.clear();
437435
const schema2 = await parseGraphQLSchema.load();
438436
const types2 = parseGraphQLSchema.graphQLTypes;
439437
const queries2 = parseGraphQLSchema.graphQLQueries;
@@ -456,14 +454,14 @@ describe('ParseGraphQLSchema', () => {
456454
});
457455
const car1 = new Parse.Object('Car');
458456
await car1.save();
459-
await parseGraphQLSchema.databaseController.schemaCache.clear();
457+
await parseGraphQLSchema.schemaCache.clear();
460458
const schema1 = await parseGraphQLSchema.load();
461459
const types1 = parseGraphQLSchema.graphQLTypes;
462460
const queries1 = parseGraphQLSchema.graphQLQueries;
463461
const mutations1 = parseGraphQLSchema.graphQLMutations;
464462
const car2 = new Parse.Object('car');
465463
await car2.save();
466-
await parseGraphQLSchema.databaseController.schemaCache.clear();
464+
await parseGraphQLSchema.schemaCache.clear();
467465
const schema2 = await parseGraphQLSchema.load();
468466
const types2 = parseGraphQLSchema.graphQLTypes;
469467
const queries2 = parseGraphQLSchema.graphQLQueries;
@@ -486,13 +484,13 @@ describe('ParseGraphQLSchema', () => {
486484
});
487485
const car = new Parse.Object('Car');
488486
await car.save();
489-
await parseGraphQLSchema.databaseController.schemaCache.clear();
487+
await parseGraphQLSchema.schemaCache.clear();
490488
const schema1 = await parseGraphQLSchema.load();
491489
const queries1 = parseGraphQLSchema.graphQLQueries;
492490
const mutations1 = parseGraphQLSchema.graphQLMutations;
493491
const cars = new Parse.Object('cars');
494492
await cars.save();
495-
await parseGraphQLSchema.databaseController.schemaCache.clear();
493+
await parseGraphQLSchema.schemaCache.clear();
496494
const schema2 = await parseGraphQLSchema.load();
497495
const queries2 = parseGraphQLSchema.graphQLQueries;
498496
const mutations2 = parseGraphQLSchema.graphQLMutations;
@@ -532,7 +530,7 @@ describe('ParseGraphQLSchema', () => {
532530

533531
await data.save();
534532

535-
await parseGraphQLSchema.databaseController.schemaCache.clear();
533+
await parseGraphQLSchema.schemaCache.clear();
536534
await parseGraphQLSchema.load();
537535

538536
const queries1 = parseGraphQLSchema.graphQLQueries;
@@ -569,7 +567,7 @@ describe('ParseGraphQLSchema', () => {
569567

570568
await data.save();
571569

572-
await parseGraphQLSchema.databaseController.schemaCache.clear();
570+
await parseGraphQLSchema.schemaCache.clear();
573571
await parseGraphQLSchema.load();
574572

575573
const mutations = parseGraphQLSchema.graphQLMutations;

0 commit comments

Comments
 (0)