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 05aba62f1cbbca7d5d3e80b9444529f59407cb56.

* 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
This commit is contained in:
Diamond Lewis
2021-03-16 16:05:36 -05:00
committed by GitHub
parent 32fc45d2d2
commit a02014f557
38 changed files with 673 additions and 937 deletions

View File

@@ -17,6 +17,7 @@
// @flow-disable-next
const Parse = require('parse/node').Parse;
import { StorageAdapter } from '../Adapters/Storage/StorageAdapter';
import SchemaCache from '../Adapters/Cache/SchemaCache';
import DatabaseController from './DatabaseController';
import Config from '../Config';
// @flow-disable-next
@@ -682,15 +683,13 @@ const typeToString = (type: SchemaField | string): string => {
export default class SchemaController {
_dbAdapter: StorageAdapter;
schemaData: { [string]: Schema };
_cache: any;
reloadDataPromise: ?Promise<any>;
protectedFields: any;
userIdRegEx: RegExp;
constructor(databaseAdapter: StorageAdapter, schemaCache: any) {
constructor(databaseAdapter: StorageAdapter) {
this._dbAdapter = databaseAdapter;
this._cache = schemaCache;
this.schemaData = new SchemaData();
this.schemaData = new SchemaData(SchemaCache.all(), this.protectedFields);
this.protectedFields = Config.get(Parse.applicationId).protectedFields;
const customIds = Config.get(Parse.applicationId).allowCustomObjectId;
@@ -699,6 +698,10 @@ export default class SchemaController {
const autoIdRegEx = /^[a-zA-Z0-9]{1,}$/;
this.userIdRegEx = customIds ? customIdRegEx : autoIdRegEx;
this._dbAdapter.watch(() => {
this.reloadData({ clearCache: true });
});
}
reloadData(options: LoadSchemaOptions = { clearCache: false }): Promise<any> {
@@ -725,12 +728,11 @@ export default class SchemaController {
if (options.clearCache) {
return this.setAllClasses();
}
return this._cache.getAllClasses().then(allClasses => {
if (allClasses && allClasses.length) {
return Promise.resolve(allClasses);
}
return this.setAllClasses();
});
const cached = SchemaCache.all();
if (cached && cached.length) {
return Promise.resolve(cached);
}
return this.setAllClasses();
}
setAllClasses(): Promise<Array<Schema>> {
@@ -738,11 +740,7 @@ export default class SchemaController {
.getAllClasses()
.then(allSchemas => allSchemas.map(injectDefaultSchema))
.then(allSchemas => {
/* eslint-disable no-console */
this._cache
.setAllClasses(allSchemas)
.catch(error => console.error('Error saving schema to cache:', error));
/* eslint-enable no-console */
SchemaCache.put(allSchemas);
return allSchemas;
});
}
@@ -752,32 +750,28 @@ export default class SchemaController {
allowVolatileClasses: boolean = false,
options: LoadSchemaOptions = { clearCache: false }
): Promise<Schema> {
let promise = Promise.resolve();
if (options.clearCache) {
promise = this._cache.clear();
SchemaCache.clear();
}
return promise.then(() => {
if (allowVolatileClasses && volatileClasses.indexOf(className) > -1) {
const data = this.schemaData[className];
return Promise.resolve({
className,
fields: data.fields,
classLevelPermissions: data.classLevelPermissions,
indexes: data.indexes,
});
}
return this._cache.getOneSchema(className).then(cached => {
if (cached && !options.clearCache) {
return Promise.resolve(cached);
}
return this.setAllClasses().then(allSchemas => {
const oneSchema = allSchemas.find(schema => schema.className === className);
if (!oneSchema) {
return Promise.reject(undefined);
}
return oneSchema;
});
if (allowVolatileClasses && volatileClasses.indexOf(className) > -1) {
const data = this.schemaData[className];
return Promise.resolve({
className,
fields: data.fields,
classLevelPermissions: data.classLevelPermissions,
indexes: data.indexes,
});
}
const cached = SchemaCache.get(className);
if (cached && !options.clearCache) {
return Promise.resolve(cached);
}
return this.setAllClasses().then(allSchemas => {
const oneSchema = allSchemas.find(schema => schema.className === className);
if (!oneSchema) {
return Promise.reject(undefined);
}
return oneSchema;
});
}
@@ -788,7 +782,7 @@ export default class SchemaController {
// on success, and rejects with an error on fail. Ensure you
// have authorization (master key, or client class creation
// enabled) before calling this function.
addClassIfNotExists(
async addClassIfNotExists(
className: string,
fields: SchemaFields = {},
classLevelPermissions: any,
@@ -803,9 +797,8 @@ export default class SchemaController {
}
return Promise.reject(validationError);
}
return this._dbAdapter
.createClass(
try {
const adapterSchema = await this._dbAdapter.createClass(
className,
convertSchemaToAdapterSchema({
fields,
@@ -813,18 +806,18 @@ export default class SchemaController {
indexes,
className,
})
)
.then(convertAdapterSchemaToParseSchema)
.catch(error => {
if (error && error.code === Parse.Error.DUPLICATE_VALUE) {
throw new Parse.Error(
Parse.Error.INVALID_CLASS_NAME,
`Class ${className} already exists.`
);
} else {
throw error;
}
});
);
// TODO: Remove by updating schema cache directly
await this.reloadData({ clearCache: true });
const parseSchema = convertAdapterSchemaToParseSchema(adapterSchema);
return parseSchema;
} catch (error) {
if (error && error.code === Parse.Error.DUPLICATE_VALUE) {
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} already exists.`);
} else {
throw error;
}
}
}
updateClass(
@@ -938,9 +931,8 @@ export default class SchemaController {
}
// We don't have this class. Update the schema
return (
// The schema update succeeded. Reload the schema
this.addClassIfNotExists(className)
// The schema update succeeded. Reload the schema
.then(() => this.reloadData({ clearCache: true }))
.catch(() => {
// The schema update failed. This can be okay - it might
// have failed because there's a race condition and a different
@@ -1050,12 +1042,16 @@ export default class SchemaController {
}
// Sets the Class-level permissions for a given className, which must exist.
setPermissions(className: string, perms: any, newSchema: SchemaFields) {
async setPermissions(className: string, perms: any, newSchema: SchemaFields) {
if (typeof perms === 'undefined') {
return Promise.resolve();
}
validateCLP(perms, newSchema, this.userIdRegEx);
return this._dbAdapter.setClassLevelPermissions(className, perms);
await this._dbAdapter.setClassLevelPermissions(className, perms);
const cached = SchemaCache.get(className);
if (cached) {
cached.classLevelPermissions = perms;
}
}
// Returns a promise that resolves successfully to the new schema
@@ -1203,7 +1199,9 @@ export default class SchemaController {
);
});
})
.then(() => this._cache.clear());
.then(() => {
SchemaCache.clear();
});
}
// Validates an object provided in REST format.
@@ -1245,6 +1243,7 @@ export default class SchemaController {
const enforceFields = results.filter(result => !!result);
if (enforceFields.length !== 0) {
// TODO: Remove by updating schema cache directly
await this.reloadData({ clearCache: true });
}
this.ensureFields(enforceFields);
@@ -1413,12 +1412,8 @@ export default class SchemaController {
}
// Returns a promise for a new Schema.
const load = (
dbAdapter: StorageAdapter,
schemaCache: any,
options: any
): Promise<SchemaController> => {
const schema = new SchemaController(dbAdapter, schemaCache);
const load = (dbAdapter: StorageAdapter, options: any): Promise<SchemaController> => {
const schema = new SchemaController(dbAdapter);
return schema.reloadData(options).then(() => schema);
};