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:
Florent Vilmart
2016-09-17 16:52:02 -04:00
committed by Drew
parent f9dca6072a
commit ddb0fb8a27
5 changed files with 112 additions and 20 deletions

View File

@@ -4,6 +4,7 @@ node_js:
- '6.1'
services:
- postgresql
- redis-server
addons:
postgresql: '9.4'
before_script:
@@ -18,8 +19,11 @@ env:
- MONGODB_VERSION=3.0.8
- MONGODB_VERSION=3.2.6
- PARSE_SERVER_TEST_DB=postgres
- PARSE_SERVER_TEST_CACHE=redis
matrix:
fast_finish: true
allow_failures:
- env: PARSE_SERVER_TEST_DB=postgres
branches:
only:
- master

View File

@@ -24,6 +24,7 @@ var MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAda
const GridStoreAdapter = require('../src/Adapters/Files/GridStoreAdapter').GridStoreAdapter;
const FSAdapter = require('parse-server-fs-adapter');
const PostgresStorageAdapter = require('../src/Adapters/Storage/Postgres/PostgresStorageAdapter');
const RedisCacheAdapter = require('../src/Adapters/Cache/RedisCacheAdapter').default;
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
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 = {};
// Set up a default API server for testing with default configuration.

View 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;

View File

@@ -323,15 +323,20 @@ export default class SchemaController {
}
reloadData(options = {clearCache: false}) {
let promise = Promise.resolve();
if (options.clearCache) {
this._cache.clear();
promise = promise.then(() => {
return this._cache.clear();
});
}
if (this.reloadDataPromise && !options.clearCache) {
return this.reloadDataPromise;
}
this.data = {};
this.perms = {};
this.reloadDataPromise = this.getAllClasses(options)
this.reloadDataPromise = promise.then(() => {
return this.getAllClasses(options);
})
.then(allSchemas => {
allSchemas.forEach(schema => {
this.data[schema.className] = injectDefaultSchema(schema).fields;
@@ -355,10 +360,13 @@ export default class SchemaController {
}
getAllClasses(options = {clearCache: false}) {
let promise = Promise.resolve();
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) {
return Promise.resolve(allClasses);
}
@@ -373,22 +381,25 @@ export default class SchemaController {
}
getOneSchema(className, allowVolatileClasses = false, options = {clearCache: false}) {
let promise = Promise.resolve();
if (options.clearCache) {
this._cache.clear();
promise = this._cache.clear();
}
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 promise.then(() => {
if (allowVolatileClasses && volatileClasses.indexOf(className) > -1) {
return Promise.resolve(this.data[className]);
}
return this._dbAdapter.getClass(className)
.then(injectDefaultSchema)
.then((result) => {
return this._cache.setOneSchema(className, result).then(() => {
return result;
})
return this._cache.getOneSchema(className).then((cached) => {
if (cached && !options.clearCache) {
return Promise.resolve(cached);
}
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 }))
.then(convertAdapterSchemaToParseSchema)
.then((res) => {
this._cache.clear();
return res;
return this._cache.clear().then(() => {
return Promise.resolve(res);
});
})
.catch(error => {
if (error && error.code === Parse.Error.DUPLICATE_VALUE) {
@@ -508,6 +520,7 @@ export default class SchemaController {
}
})
.catch(error => {
console.error(error);
// The schema still doesn't validate. Give up
throw new Parse.Error(Parse.Error.INVALID_JSON, 'schema class name does not revalidate');
});

View File

@@ -3,6 +3,7 @@ import S3Adapter from 'parse-server-s3-adapter'
import FileSystemAdapter from 'parse-server-fs-adapter'
import InMemoryCacheAdapter from './Adapters/Cache/InMemoryCacheAdapter'
import NullCacheAdapter from './Adapters/Cache/NullCacheAdapter'
import RedisCacheAdapter from './Adapters/Cache/RedisCacheAdapter'
import TestUtils from './TestUtils';
import { useExternal } from './deprecated';
import { getLogger } from './logger';
@@ -22,4 +23,4 @@ Object.defineProperty(module.exports, 'logger', {
});
export default ParseServer;
export { S3Adapter, GCSAdapter, FileSystemAdapter, InMemoryCacheAdapter, NullCacheAdapter, TestUtils, _ParseServer as ParseServer };
export { S3Adapter, GCSAdapter, FileSystemAdapter, InMemoryCacheAdapter, NullCacheAdapter, RedisCacheAdapter, TestUtils, _ParseServer as ParseServer };