Skip to content

Commit a02014f

Browse files
authored
Improve single schema cache (#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 32fc45d commit a02014f

38 files changed

+673
-937
lines changed

CHANGELOG.md

+3
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)

resources/buildConfigDefinitions.js

+2-1
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

-58
This file was deleted.

spec/LogsRouter.spec.js

+3-1
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

+31
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' },
@@ -546,5 +549,33 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
546549
});
547550
});
548551
});
552+
553+
describe('watch _SCHEMA', () => {
554+
it('should change', async done => {
555+
const adapter = new MongoStorageAdapter({
556+
uri: databaseURI,
557+
collectionPrefix: '',
558+
mongoOptions: { enableSchemaHooks: true },
559+
});
560+
await reconfigureServer({ databaseAdapter: adapter });
561+
expect(adapter.enableSchemaHooks).toBe(true);
562+
spyOn(adapter, '_onchange');
563+
const schema = {
564+
fields: {
565+
array: { type: 'Array' },
566+
object: { type: 'Object' },
567+
date: { type: 'Date' },
568+
},
569+
};
570+
571+
await adapter.createClass('Stuff', schema);
572+
const myClassSchema = await adapter.getClass('Stuff');
573+
expect(myClassSchema).toBeDefined();
574+
setTimeout(() => {
575+
expect(adapter._onchange).toHaveBeenCalled();
576+
done();
577+
}, 5000);
578+
});
579+
});
549580
}
550581
});

spec/ParseGraphQLController.spec.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ describe('ParseGraphQLController', () => {
3030

3131
beforeEach(async () => {
3232
if (!parseServer) {
33-
parseServer = await global.reconfigureServer({
34-
schemaCacheTTL: 100,
35-
});
33+
parseServer = await global.reconfigureServer();
3634
databaseController = parseServer.config.databaseController;
3735
cacheController = parseServer.config.cacheController;
3836

spec/ParseGraphQLSchema.spec.js

+10-12
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ describe('ParseGraphQLSchema', () => {
1010
const appId = 'test';
1111

1212
beforeEach(async () => {
13-
parseServer = await global.reconfigureServer({
14-
schemaCacheTTL: 100,
15-
});
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)