Skip to content

Commit 7d622f0

Browse files
authored
feat: Upgrade Redis 3 to 4 (#8293)
BREAKING CHANGE: This release upgrades to Redis 4; if you are using the Redis cache adapter with Parse Server then this is a breaking change as the Redis client options have changed; see the [Redis migration guide](https://github.com/redis/node-redis/blob/redis%404.0.0/docs/v3-to-v4.md) for more details (#8293)
1 parent 9af9115 commit 7d622f0

File tree

7 files changed

+444
-399
lines changed

7 files changed

+444
-399
lines changed

package-lock.json

+346-302
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"pg-monitor": "1.5.0",
5050
"pg-promise": "10.12.1",
5151
"pluralize": "8.0.0",
52-
"redis": "3.1.2",
52+
"redis": "4.0.6",
5353
"semver": "7.3.8",
5454
"subscriptions-transport-ws": "0.11.0",
5555
"tv4": "1.3.0",

spec/DefinedSchemas.spec.js

+29
Original file line numberDiff line numberDiff line change
@@ -677,4 +677,33 @@ describe('DefinedSchemas', () => {
677677
expect(testSchema.classLevelPermissions.create).toEqual({ requiresAuthentication: true });
678678
expect(logger.error).toHaveBeenCalledTimes(0);
679679
});
680+
681+
it('should not affect cacheAdapter', async () => {
682+
const server = await reconfigureServer();
683+
const logger = require('../lib/logger').logger;
684+
spyOn(logger, 'error').and.callThrough();
685+
const migrationOptions = {
686+
definitions: [
687+
{
688+
className: 'Test',
689+
fields: { aField: { type: 'String' } },
690+
indexes: { aField: { aField: 1 } },
691+
classLevelPermissions: {
692+
create: { requiresAuthentication: true },
693+
},
694+
},
695+
],
696+
};
697+
698+
const cacheAdapter = {
699+
get: () => Promise.resolve(null),
700+
put: () => {},
701+
del: () => {},
702+
clear: () => {},
703+
connect: jasmine.createSpy('clear'),
704+
};
705+
server.config.cacheAdapter = cacheAdapter;
706+
await new DefinedSchemas(migrationOptions, server.config).execute();
707+
expect(cacheAdapter.connect).not.toHaveBeenCalled();
708+
});
680709
});

spec/RedisCacheAdapter.spec.js

+15-14
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,22 @@ describe_only(() => {
1919

2020
beforeEach(async () => {
2121
cache = new RedisCacheAdapter(null, 100);
22+
await cache.connect();
2223
await cache.clear();
2324
});
2425

25-
it('should get/set/clear', done => {
26+
it('should get/set/clear', async () => {
2627
const cacheNaN = new RedisCacheAdapter({
2728
ttl: NaN,
2829
});
29-
30-
cacheNaN
31-
.put(KEY, VALUE)
32-
.then(() => cacheNaN.get(KEY))
33-
.then(value => expect(value).toEqual(VALUE))
34-
.then(() => cacheNaN.clear())
35-
.then(() => cacheNaN.get(KEY))
36-
.then(value => expect(value).toEqual(null))
37-
.then(() => cacheNaN.clear())
38-
.then(done);
30+
await cacheNaN.connect();
31+
await cacheNaN.put(KEY, VALUE);
32+
let value = await cacheNaN.get(KEY);
33+
expect(value).toEqual(VALUE);
34+
await cacheNaN.clear();
35+
value = await cacheNaN.get(KEY);
36+
expect(value).toEqual(null);
37+
await cacheNaN.clear();
3938
});
4039

4140
it('should expire after ttl', done => {
@@ -100,7 +99,7 @@ describe_only(() => {
10099
it('handleShutdown, close connection', async () => {
101100
await cache.handleShutdown();
102101
setTimeout(() => {
103-
expect(cache.client.connected).toBe(false);
102+
expect(cache.client.isOpen).toBe(false);
104103
}, 0);
105104
});
106105
});
@@ -122,8 +121,9 @@ describe_only(() => {
122121
return Object.keys(cache.queue.queue).length;
123122
}
124123

125-
it('it should clear completed operations from queue', done => {
124+
it('it should clear completed operations from queue', async done => {
126125
const cache = new RedisCacheAdapter({ ttl: NaN });
126+
await cache.connect();
127127

128128
// execute a bunch of operations in sequence
129129
let promise = Promise.resolve();
@@ -144,8 +144,9 @@ describe_only(() => {
144144
promise.then(() => expect(getQueueCount(cache)).toEqual(0)).then(done);
145145
});
146146

147-
it('it should count per key chained operations correctly', done => {
147+
it('it should count per key chained operations correctly', async done => {
148148
const cache = new RedisCacheAdapter({ ttl: NaN });
149+
await cache.connect();
149150

150151
let key1Promise = Promise.resolve();
151152
let key2Promise = Promise.resolve();

src/Adapters/Cache/RedisCacheAdapter.js

+40-78
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import redis from 'redis';
1+
import { createClient } from 'redis';
22
import logger from '../../logger';
33
import { KeyPromiseQueue } from '../../KeyPromiseQueue';
44

@@ -15,114 +15,76 @@ const isValidTTL = ttl => typeof ttl === 'number' && ttl > 0;
1515
export class RedisCacheAdapter {
1616
constructor(redisCtx, ttl = DEFAULT_REDIS_TTL) {
1717
this.ttl = isValidTTL(ttl) ? ttl : DEFAULT_REDIS_TTL;
18-
this.client = redis.createClient(redisCtx);
18+
this.client = createClient(redisCtx);
1919
this.queue = new KeyPromiseQueue();
2020
}
2121

22-
handleShutdown() {
22+
async connect() {
23+
if (this.client.isOpen) {
24+
return;
25+
}
26+
return this.client.connect();
27+
}
28+
29+
async handleShutdown() {
2330
if (!this.client) {
24-
return Promise.resolve();
31+
return;
32+
}
33+
try {
34+
await this.client.quit();
35+
} catch (err) {
36+
logger.error('RedisCacheAdapter error on shutdown', { error: err });
2537
}
26-
return new Promise(resolve => {
27-
this.client.quit(err => {
28-
if (err) {
29-
logger.error('RedisCacheAdapter error on shutdown', { error: err });
30-
}
31-
resolve();
32-
});
33-
});
3438
}
3539

36-
get(key) {
40+
async get(key) {
3741
debug('get', { key });
38-
return this.queue.enqueue(
39-
key,
40-
() =>
41-
new Promise(resolve => {
42-
this.client.get(key, function (err, res) {
43-
debug('-> get', { key, res });
44-
if (!res) {
45-
return resolve(null);
46-
}
47-
resolve(JSON.parse(res));
48-
});
49-
})
50-
);
42+
try {
43+
await this.queue.enqueue(key);
44+
const res = await this.client.get(key);
45+
if (!res) {
46+
return null;
47+
}
48+
return JSON.parse(res);
49+
} catch (err) {
50+
logger.error('RedisCacheAdapter error on get', { error: err });
51+
}
5152
}
5253

53-
put(key, value, ttl = this.ttl) {
54+
async put(key, value, ttl = this.ttl) {
5455
value = JSON.stringify(value);
5556
debug('put', { key, value, ttl });
56-
57+
await this.queue.enqueue(key);
5758
if (ttl === 0) {
5859
// ttl of zero is a logical no-op, but redis cannot set expire time of zero
59-
return this.queue.enqueue(key, () => Promise.resolve());
60+
return;
6061
}
6162

6263
if (ttl === Infinity) {
63-
return this.queue.enqueue(
64-
key,
65-
() =>
66-
new Promise(resolve => {
67-
this.client.set(key, value, function () {
68-
resolve();
69-
});
70-
})
71-
);
64+
return this.client.set(key, value);
7265
}
7366

7467
if (!isValidTTL(ttl)) {
7568
ttl = this.ttl;
7669
}
77-
78-
return this.queue.enqueue(
79-
key,
80-
() =>
81-
new Promise(resolve => {
82-
this.client.psetex(key, ttl, value, function () {
83-
resolve();
84-
});
85-
})
86-
);
70+
return this.client.set(key, value, { PX: ttl });
8771
}
8872

89-
del(key) {
73+
async del(key) {
9074
debug('del', { key });
91-
return this.queue.enqueue(
92-
key,
93-
() =>
94-
new Promise(resolve => {
95-
this.client.del(key, function () {
96-
resolve();
97-
});
98-
})
99-
);
75+
await this.queue.enqueue(key);
76+
return this.client.del(key);
10077
}
10178

102-
clear() {
79+
async clear() {
10380
debug('clear');
104-
return this.queue.enqueue(
105-
FLUSH_DB_KEY,
106-
() =>
107-
new Promise(resolve => {
108-
this.client.flushdb(function () {
109-
resolve();
110-
});
111-
})
112-
);
81+
await this.queue.enqueue(FLUSH_DB_KEY);
82+
return this.client.sendCommand(['FLUSHDB']);
11383
}
11484

11585
// Used for testing
116-
async getAllKeys() {
117-
return new Promise((resolve, reject) => {
118-
this.client.keys('*', (err, keys) => {
119-
if (err) {
120-
reject(err);
121-
} else {
122-
resolve(keys);
123-
}
124-
});
125-
});
86+
getAllKeys() {
87+
return this.client.keys('*');
12688
}
12789
}
12890

src/Adapters/PubSub/RedisPubSub.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import redis from 'redis';
1+
import { createClient } from 'redis';
22

33
function createPublisher({ redisURL, redisOptions = {} }): any {
44
redisOptions.no_ready_check = true;
5-
return redis.createClient(redisURL, redisOptions);
5+
return createClient(redisURL, redisOptions);
66
}
77

88
function createSubscriber({ redisURL, redisOptions = {} }): any {
99
redisOptions.no_ready_check = true;
10-
return redis.createClient(redisURL, redisOptions);
10+
return createClient(redisURL, redisOptions);
1111
}
1212

1313
const RedisPubSub = {

src/ParseServer.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,18 @@ class ParseServer {
8787
.performInitialization()
8888
.then(() => hooksController.load())
8989
.then(async () => {
90+
const startupPromises = [];
9091
if (schema) {
91-
await new DefinedSchemas(schema, this.config).execute();
92+
startupPromises.push(new DefinedSchemas(schema, this.config).execute());
9293
}
94+
if (
95+
options.cacheAdapter &&
96+
options.cacheAdapter.connect &&
97+
typeof options.cacheAdapter.connect === 'function'
98+
) {
99+
startupPromises.push(options.cacheAdapter.connect());
100+
}
101+
await Promise.all(startupPromises);
93102
if (serverStartComplete) {
94103
serverStartComplete();
95104
}

0 commit comments

Comments
 (0)