Adds redis cache for distributed environments (#2691)
* Makes schemaCache clearning promise-based * Adds redis cache adapter for distributed systems * Adds redis service to travis * allow pg to fail
This commit is contained in:
@@ -4,6 +4,7 @@ node_js:
|
|||||||
- '6.1'
|
- '6.1'
|
||||||
services:
|
services:
|
||||||
- postgresql
|
- postgresql
|
||||||
|
- redis-server
|
||||||
addons:
|
addons:
|
||||||
postgresql: '9.4'
|
postgresql: '9.4'
|
||||||
before_script:
|
before_script:
|
||||||
@@ -18,8 +19,11 @@ env:
|
|||||||
- MONGODB_VERSION=3.0.8
|
- MONGODB_VERSION=3.0.8
|
||||||
- MONGODB_VERSION=3.2.6
|
- MONGODB_VERSION=3.2.6
|
||||||
- PARSE_SERVER_TEST_DB=postgres
|
- PARSE_SERVER_TEST_DB=postgres
|
||||||
|
- PARSE_SERVER_TEST_CACHE=redis
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
|
allow_failures:
|
||||||
|
- env: PARSE_SERVER_TEST_DB=postgres
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ var MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAda
|
|||||||
const GridStoreAdapter = require('../src/Adapters/Files/GridStoreAdapter').GridStoreAdapter;
|
const GridStoreAdapter = require('../src/Adapters/Files/GridStoreAdapter').GridStoreAdapter;
|
||||||
const FSAdapter = require('parse-server-fs-adapter');
|
const FSAdapter = require('parse-server-fs-adapter');
|
||||||
const PostgresStorageAdapter = require('../src/Adapters/Storage/Postgres/PostgresStorageAdapter');
|
const PostgresStorageAdapter = require('../src/Adapters/Storage/Postgres/PostgresStorageAdapter');
|
||||||
|
const RedisCacheAdapter = require('../src/Adapters/Cache/RedisCacheAdapter').default;
|
||||||
|
|
||||||
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||||
const postgresURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
|
const postgresURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
|
||||||
@@ -101,6 +102,10 @@ var defaultConfiguration = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (process.env.PARSE_SERVER_TEST_CACHE === 'redis') {
|
||||||
|
defaultConfiguration.cacheAdapter = new RedisCacheAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
let openConnections = {};
|
let openConnections = {};
|
||||||
|
|
||||||
// Set up a default API server for testing with default configuration.
|
// Set up a default API server for testing with default configuration.
|
||||||
|
|||||||
69
src/Adapters/Cache/RedisCacheAdapter.js
Normal file
69
src/Adapters/Cache/RedisCacheAdapter.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import redis from 'redis';
|
||||||
|
import logger from '../../logger';
|
||||||
|
|
||||||
|
function debug() {
|
||||||
|
logger.debug.apply(logger, ['RedisCacheAdapter', ...arguments]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RedisCacheAdapter {
|
||||||
|
|
||||||
|
constructor(ctx) {
|
||||||
|
this.client = redis.createClient(ctx);
|
||||||
|
this.p = Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
debug('get', key);
|
||||||
|
this.p = this.p.then(() => {
|
||||||
|
return new Promise((resolve, _) => {
|
||||||
|
this.client.get(key, function(err, res) {
|
||||||
|
debug('-> get', key, res);
|
||||||
|
if(!res) {
|
||||||
|
return resolve(null);
|
||||||
|
}
|
||||||
|
resolve(JSON.parse(res));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return this.p;
|
||||||
|
}
|
||||||
|
|
||||||
|
put(key, value, ttl) {
|
||||||
|
value = JSON.stringify(value);
|
||||||
|
debug('put', key, value, ttl);
|
||||||
|
this.p = this.p.then(() => {
|
||||||
|
return new Promise((resolve, _) => {
|
||||||
|
this.client.set(key, value, function(err, res) {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return this.p;
|
||||||
|
}
|
||||||
|
|
||||||
|
del(key) {
|
||||||
|
debug('del', key);
|
||||||
|
this.p = this.p.then(() => {
|
||||||
|
return new Promise((resolve, _) => {
|
||||||
|
this.client.del(key, function(err, res) {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return this.p;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
debug('clear');
|
||||||
|
this.p = this.p.then(() => {
|
||||||
|
return new Promise((resolve, _) => {
|
||||||
|
this.client.flushall(function(err, res) {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return this.p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RedisCacheAdapter;
|
||||||
@@ -323,15 +323,20 @@ export default class SchemaController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reloadData(options = {clearCache: false}) {
|
reloadData(options = {clearCache: false}) {
|
||||||
|
let promise = Promise.resolve();
|
||||||
if (options.clearCache) {
|
if (options.clearCache) {
|
||||||
this._cache.clear();
|
promise = promise.then(() => {
|
||||||
|
return this._cache.clear();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (this.reloadDataPromise && !options.clearCache) {
|
if (this.reloadDataPromise && !options.clearCache) {
|
||||||
return this.reloadDataPromise;
|
return this.reloadDataPromise;
|
||||||
}
|
}
|
||||||
this.data = {};
|
this.data = {};
|
||||||
this.perms = {};
|
this.perms = {};
|
||||||
this.reloadDataPromise = this.getAllClasses(options)
|
this.reloadDataPromise = promise.then(() => {
|
||||||
|
return this.getAllClasses(options);
|
||||||
|
})
|
||||||
.then(allSchemas => {
|
.then(allSchemas => {
|
||||||
allSchemas.forEach(schema => {
|
allSchemas.forEach(schema => {
|
||||||
this.data[schema.className] = injectDefaultSchema(schema).fields;
|
this.data[schema.className] = injectDefaultSchema(schema).fields;
|
||||||
@@ -355,10 +360,13 @@ export default class SchemaController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAllClasses(options = {clearCache: false}) {
|
getAllClasses(options = {clearCache: false}) {
|
||||||
|
let promise = Promise.resolve();
|
||||||
if (options.clearCache) {
|
if (options.clearCache) {
|
||||||
this._cache.clear();
|
promise = this._cache.clear();
|
||||||
}
|
}
|
||||||
return this._cache.getAllClasses().then((allClasses) => {
|
return promise.then(() => {
|
||||||
|
return this._cache.getAllClasses()
|
||||||
|
}).then((allClasses) => {
|
||||||
if (allClasses && allClasses.length && !options.clearCache) {
|
if (allClasses && allClasses.length && !options.clearCache) {
|
||||||
return Promise.resolve(allClasses);
|
return Promise.resolve(allClasses);
|
||||||
}
|
}
|
||||||
@@ -373,22 +381,25 @@ export default class SchemaController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getOneSchema(className, allowVolatileClasses = false, options = {clearCache: false}) {
|
getOneSchema(className, allowVolatileClasses = false, options = {clearCache: false}) {
|
||||||
|
let promise = Promise.resolve();
|
||||||
if (options.clearCache) {
|
if (options.clearCache) {
|
||||||
this._cache.clear();
|
promise = this._cache.clear();
|
||||||
}
|
}
|
||||||
if (allowVolatileClasses && volatileClasses.indexOf(className) > -1) {
|
return promise.then(() => {
|
||||||
return Promise.resolve(this.data[className]);
|
if (allowVolatileClasses && volatileClasses.indexOf(className) > -1) {
|
||||||
}
|
return Promise.resolve(this.data[className]);
|
||||||
return this._cache.getOneSchema(className).then((cached) => {
|
|
||||||
if (cached && !options.clearCache) {
|
|
||||||
return Promise.resolve(cached);
|
|
||||||
}
|
}
|
||||||
return this._dbAdapter.getClass(className)
|
return this._cache.getOneSchema(className).then((cached) => {
|
||||||
.then(injectDefaultSchema)
|
if (cached && !options.clearCache) {
|
||||||
.then((result) => {
|
return Promise.resolve(cached);
|
||||||
return this._cache.setOneSchema(className, result).then(() => {
|
}
|
||||||
return result;
|
return this._dbAdapter.getClass(className)
|
||||||
})
|
.then(injectDefaultSchema)
|
||||||
|
.then((result) => {
|
||||||
|
return this._cache.setOneSchema(className, result).then(() => {
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -409,8 +420,9 @@ export default class SchemaController {
|
|||||||
return this._dbAdapter.createClass(className, convertSchemaToAdapterSchema({ fields, classLevelPermissions, className }))
|
return this._dbAdapter.createClass(className, convertSchemaToAdapterSchema({ fields, classLevelPermissions, className }))
|
||||||
.then(convertAdapterSchemaToParseSchema)
|
.then(convertAdapterSchemaToParseSchema)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this._cache.clear();
|
return this._cache.clear().then(() => {
|
||||||
return res;
|
return Promise.resolve(res);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error && error.code === Parse.Error.DUPLICATE_VALUE) {
|
if (error && error.code === Parse.Error.DUPLICATE_VALUE) {
|
||||||
@@ -508,6 +520,7 @@ export default class SchemaController {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
// The schema still doesn't validate. Give up
|
// The schema still doesn't validate. Give up
|
||||||
throw new Parse.Error(Parse.Error.INVALID_JSON, 'schema class name does not revalidate');
|
throw new Parse.Error(Parse.Error.INVALID_JSON, 'schema class name does not revalidate');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import S3Adapter from 'parse-server-s3-adapter'
|
|||||||
import FileSystemAdapter from 'parse-server-fs-adapter'
|
import FileSystemAdapter from 'parse-server-fs-adapter'
|
||||||
import InMemoryCacheAdapter from './Adapters/Cache/InMemoryCacheAdapter'
|
import InMemoryCacheAdapter from './Adapters/Cache/InMemoryCacheAdapter'
|
||||||
import NullCacheAdapter from './Adapters/Cache/NullCacheAdapter'
|
import NullCacheAdapter from './Adapters/Cache/NullCacheAdapter'
|
||||||
|
import RedisCacheAdapter from './Adapters/Cache/RedisCacheAdapter'
|
||||||
import TestUtils from './TestUtils';
|
import TestUtils from './TestUtils';
|
||||||
import { useExternal } from './deprecated';
|
import { useExternal } from './deprecated';
|
||||||
import { getLogger } from './logger';
|
import { getLogger } from './logger';
|
||||||
@@ -22,4 +23,4 @@ Object.defineProperty(module.exports, 'logger', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default ParseServer;
|
export default ParseServer;
|
||||||
export { S3Adapter, GCSAdapter, FileSystemAdapter, InMemoryCacheAdapter, NullCacheAdapter, TestUtils, _ParseServer as ParseServer };
|
export { S3Adapter, GCSAdapter, FileSystemAdapter, InMemoryCacheAdapter, NullCacheAdapter, RedisCacheAdapter, TestUtils, _ParseServer as ParseServer };
|
||||||
|
|||||||
Reference in New Issue
Block a user