Skip to content

Commit ddb0fb8

Browse files
flovilmartdrew-gross
authored andcommitted
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
1 parent f9dca60 commit ddb0fb8

File tree

5 files changed

+112
-20
lines changed

5 files changed

+112
-20
lines changed

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ node_js:
44
- '6.1'
55
services:
66
- postgresql
7+
- redis-server
78
addons:
89
postgresql: '9.4'
910
before_script:
@@ -18,8 +19,11 @@ env:
1819
- MONGODB_VERSION=3.0.8
1920
- MONGODB_VERSION=3.2.6
2021
- PARSE_SERVER_TEST_DB=postgres
22+
- PARSE_SERVER_TEST_CACHE=redis
2123
matrix:
2224
fast_finish: true
25+
allow_failures:
26+
- env: PARSE_SERVER_TEST_DB=postgres
2327
branches:
2428
only:
2529
- master

spec/helper.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ var MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAda
2424
const GridStoreAdapter = require('../src/Adapters/Files/GridStoreAdapter').GridStoreAdapter;
2525
const FSAdapter = require('parse-server-fs-adapter');
2626
const PostgresStorageAdapter = require('../src/Adapters/Storage/Postgres/PostgresStorageAdapter');
27+
const RedisCacheAdapter = require('../src/Adapters/Cache/RedisCacheAdapter').default;
2728

2829
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
2930
const postgresURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
@@ -101,6 +102,10 @@ var defaultConfiguration = {
101102
}
102103
};
103104

105+
if (process.env.PARSE_SERVER_TEST_CACHE === 'redis') {
106+
defaultConfiguration.cacheAdapter = new RedisCacheAdapter();
107+
}
108+
104109
let openConnections = {};
105110

106111
// Set up a default API server for testing with default configuration.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import redis from 'redis';
2+
import logger from '../../logger';
3+
4+
function debug() {
5+
logger.debug.apply(logger, ['RedisCacheAdapter', ...arguments]);
6+
}
7+
8+
export class RedisCacheAdapter {
9+
10+
constructor(ctx) {
11+
this.client = redis.createClient(ctx);
12+
this.p = Promise.resolve();
13+
}
14+
15+
get(key) {
16+
debug('get', key);
17+
this.p = this.p.then(() => {
18+
return new Promise((resolve, _) => {
19+
this.client.get(key, function(err, res) {
20+
debug('-> get', key, res);
21+
if(!res) {
22+
return resolve(null);
23+
}
24+
resolve(JSON.parse(res));
25+
});
26+
});
27+
});
28+
return this.p;
29+
}
30+
31+
put(key, value, ttl) {
32+
value = JSON.stringify(value);
33+
debug('put', key, value, ttl);
34+
this.p = this.p.then(() => {
35+
return new Promise((resolve, _) => {
36+
this.client.set(key, value, function(err, res) {
37+
resolve();
38+
});
39+
});
40+
});
41+
return this.p;
42+
}
43+
44+
del(key) {
45+
debug('del', key);
46+
this.p = this.p.then(() => {
47+
return new Promise((resolve, _) => {
48+
this.client.del(key, function(err, res) {
49+
resolve();
50+
});
51+
});
52+
});
53+
return this.p;
54+
}
55+
56+
clear() {
57+
debug('clear');
58+
this.p = this.p.then(() => {
59+
return new Promise((resolve, _) => {
60+
this.client.flushall(function(err, res) {
61+
resolve();
62+
});
63+
});
64+
});
65+
return this.p;
66+
}
67+
}
68+
69+
export default RedisCacheAdapter;

src/Controllers/SchemaController.js

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -323,15 +323,20 @@ export default class SchemaController {
323323
}
324324

325325
reloadData(options = {clearCache: false}) {
326+
let promise = Promise.resolve();
326327
if (options.clearCache) {
327-
this._cache.clear();
328+
promise = promise.then(() => {
329+
return this._cache.clear();
330+
});
328331
}
329332
if (this.reloadDataPromise && !options.clearCache) {
330333
return this.reloadDataPromise;
331334
}
332335
this.data = {};
333336
this.perms = {};
334-
this.reloadDataPromise = this.getAllClasses(options)
337+
this.reloadDataPromise = promise.then(() => {
338+
return this.getAllClasses(options);
339+
})
335340
.then(allSchemas => {
336341
allSchemas.forEach(schema => {
337342
this.data[schema.className] = injectDefaultSchema(schema).fields;
@@ -355,10 +360,13 @@ export default class SchemaController {
355360
}
356361

357362
getAllClasses(options = {clearCache: false}) {
363+
let promise = Promise.resolve();
358364
if (options.clearCache) {
359-
this._cache.clear();
365+
promise = this._cache.clear();
360366
}
361-
return this._cache.getAllClasses().then((allClasses) => {
367+
return promise.then(() => {
368+
return this._cache.getAllClasses()
369+
}).then((allClasses) => {
362370
if (allClasses && allClasses.length && !options.clearCache) {
363371
return Promise.resolve(allClasses);
364372
}
@@ -373,22 +381,25 @@ export default class SchemaController {
373381
}
374382

375383
getOneSchema(className, allowVolatileClasses = false, options = {clearCache: false}) {
384+
let promise = Promise.resolve();
376385
if (options.clearCache) {
377-
this._cache.clear();
378-
}
379-
if (allowVolatileClasses && volatileClasses.indexOf(className) > -1) {
380-
return Promise.resolve(this.data[className]);
386+
promise = this._cache.clear();
381387
}
382-
return this._cache.getOneSchema(className).then((cached) => {
383-
if (cached && !options.clearCache) {
384-
return Promise.resolve(cached);
388+
return promise.then(() => {
389+
if (allowVolatileClasses && volatileClasses.indexOf(className) > -1) {
390+
return Promise.resolve(this.data[className]);
385391
}
386-
return this._dbAdapter.getClass(className)
387-
.then(injectDefaultSchema)
388-
.then((result) => {
389-
return this._cache.setOneSchema(className, result).then(() => {
390-
return result;
391-
})
392+
return this._cache.getOneSchema(className).then((cached) => {
393+
if (cached && !options.clearCache) {
394+
return Promise.resolve(cached);
395+
}
396+
return this._dbAdapter.getClass(className)
397+
.then(injectDefaultSchema)
398+
.then((result) => {
399+
return this._cache.setOneSchema(className, result).then(() => {
400+
return result;
401+
})
402+
});
392403
});
393404
});
394405
}
@@ -409,8 +420,9 @@ export default class SchemaController {
409420
return this._dbAdapter.createClass(className, convertSchemaToAdapterSchema({ fields, classLevelPermissions, className }))
410421
.then(convertAdapterSchemaToParseSchema)
411422
.then((res) => {
412-
this._cache.clear();
413-
return res;
423+
return this._cache.clear().then(() => {
424+
return Promise.resolve(res);
425+
});
414426
})
415427
.catch(error => {
416428
if (error && error.code === Parse.Error.DUPLICATE_VALUE) {
@@ -508,6 +520,7 @@ export default class SchemaController {
508520
}
509521
})
510522
.catch(error => {
523+
console.error(error);
511524
// The schema still doesn't validate. Give up
512525
throw new Parse.Error(Parse.Error.INVALID_JSON, 'schema class name does not revalidate');
513526
});

src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import S3Adapter from 'parse-server-s3-adapter'
33
import FileSystemAdapter from 'parse-server-fs-adapter'
44
import InMemoryCacheAdapter from './Adapters/Cache/InMemoryCacheAdapter'
55
import NullCacheAdapter from './Adapters/Cache/NullCacheAdapter'
6+
import RedisCacheAdapter from './Adapters/Cache/RedisCacheAdapter'
67
import TestUtils from './TestUtils';
78
import { useExternal } from './deprecated';
89
import { getLogger } from './logger';
@@ -22,4 +23,4 @@ Object.defineProperty(module.exports, 'logger', {
2223
});
2324

2425
export default ParseServer;
25-
export { S3Adapter, GCSAdapter, FileSystemAdapter, InMemoryCacheAdapter, NullCacheAdapter, TestUtils, _ParseServer as ParseServer };
26+
export { S3Adapter, GCSAdapter, FileSystemAdapter, InMemoryCacheAdapter, NullCacheAdapter, RedisCacheAdapter, TestUtils, _ParseServer as ParseServer };

0 commit comments

Comments
 (0)