Skip to content

Commit 1a8700c

Browse files
committed
feat: add dropBufferSupport option to improve the performance (#293)
1 parent bffd4b9 commit 1a8700c

File tree

7 files changed

+181
-177
lines changed

7 files changed

+181
-177
lines changed

API.md

+2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ Creates a Redis instance
5959
| [options.connectionName] | <code>string</code> | <code>null</code> | Connection name. |
6060
| [options.db] | <code>number</code> | <code>0</code> | Database index to use. |
6161
| [options.password] | <code>string</code> | <code>null</code> | If set, client will send AUTH command with the value of this option when connected. |
62+
| [options.parser] | <code>string</code> | <code>null</code> | Either "hiredis" or "javascript". If not set, "hiredis" parser will be used if it's installed (`npm install hiredis`), otherwise "javascript" parser will be used. |
63+
| [options.dropBufferSupport] | <code>boolean</code> | <code>false</code> | Drop the buffer support for better performance. This option is recommanded to be enabled when "hiredis" parser is used. Refer to https://github.com/luin/ioredis/wiki/Improve-Performance for more details. |
6264
| [options.enableReadyCheck] | <code>boolean</code> | <code>true</code> | When a connection is established to the Redis server, the server might still be loading the database from disk. While loading, the server not respond to any commands. To work around this, when this option is `true`, ioredis will check the status of the Redis server, and when the Redis server is able to process commands, a `ready` event will be emitted. |
6365
| [options.enableOfflineQueue] | <code>boolean</code> | <code>true</code> | By default, if there is no active connection to the Redis server, commands are added to a queue and are executed once the connection is "ready" (when `enableReadyCheck` is `true`, "ready" means the Redis server has loaded the database from disk, otherwise means the connection to the Redis server has been established). If this option is false, when execute the command when the connection isn't ready, an error will be returned. |
6466
| [options.connectTimeout] | <code>number</code> | <code>10000</code> | The milliseconds before a timeout occurs during the initial connection to the Redis server. |

README.md

+3-4
Original file line numberDiff line numberDiff line change
@@ -807,10 +807,9 @@ var cluster = new Redis.Cluster([
807807
});
808808
```
809809

810-
## Native Parser
811-
If [hiredis](https://github.com/redis/hiredis-node) is installed (by `npm install hiredis`),
812-
ioredis will use it by default. Otherwise, a pure JavaScript parser will be used.
813-
Typically, there's not much difference between them in terms of performance.
810+
## Improve Performance
811+
ioredis supports two parsers, "hiredis" and "javascript". Refer to https://github.com/luin/ioredis/wiki/Improve-Performance
812+
for details about the differences between them in terms of performance.
814813

815814
<hr>
816815

benchmarks/single_node.js

+47-170
Original file line numberDiff line numberDiff line change
@@ -1,221 +1,98 @@
11
'use strict';
22

33
var childProcess = require('child_process');
4-
var nodeRedis = require('redis');
5-
var IORedis = require('../');
6-
var ndredis, ioredis;
4+
var Redis = require('../');
75

86
console.log('==========================');
9-
console.log('ioredis: ' + require('../package.json').version);
10-
console.log('node_redis: ' + require('redis/package.json').version);
7+
console.log('redis: ' + require('../package.json').version);
118
var os = require('os');
129
console.log('CPU: ' + os.cpus().length);
1310
console.log('OS: ' + os.platform() + ' ' + os.arch());
1411
console.log('node version: ' + process.version);
1512
console.log('current commit: ' + childProcess.execSync('git rev-parse --short HEAD'));
1613
console.log('==========================');
1714

15+
var redisJD, redisJ, redisBD, redisB;
1816
var waitReady = function (next) {
19-
var pending = 2;
20-
ndredis.on('ready', function () {
17+
var pending = 4;
18+
function check() {
2119
if (!--pending) {
2220
next();
2321
}
24-
});
22+
}
23+
redisJD = new Redis({ parser: 'javascript', dropBufferSupport: true });
24+
redisJ = new Redis({ parser: 'javascript', dropBufferSupport: false });
25+
redisBD = new Redis({ parser: 'hiredis', dropBufferSupport: true });
26+
redisB = new Redis({ parser: 'hiredis', dropBufferSupport: false });
27+
redisJD.on('ready', check);
28+
redisJ.on('ready', check);
29+
redisBD.on('ready', check);
30+
redisB.on('ready', check);
31+
};
2532

26-
ioredis.on('ready', function () {
27-
if (!--pending) {
28-
next();
29-
}
30-
});
33+
var quit = function () {
34+
redisJD.quit();
35+
redisJ.quit();
36+
redisBD.quit();
37+
redisB.quit();
3138
};
3239

33-
suite('simple set', function () {
40+
suite('SET foo bar', function () {
3441
set('mintime', 5000);
3542
set('concurrency', 300);
3643
before(function (start) {
37-
ndredis = nodeRedis.createClient();
38-
ioredis = new IORedis();
3944
waitReady(start);
4045
});
4146

42-
bench('ioredis', function (next) {
43-
ioredis.set('foo', 'bar', next);
44-
});
45-
46-
bench('node_redis', function (next) {
47-
ndredis.set('foo', 'bar', next);
47+
bench('javascript parser + dropBufferSupport: true', function (next) {
48+
redisJD.set('foo', 'bar', next);
4849
});
4950

50-
after(function () {
51-
ndredis.quit();
52-
ioredis.quit();
53-
});
54-
});
55-
56-
suite('simple get', function () {
57-
set('mintime', 5000);
58-
set('concurrency', 300);
59-
before(function (start) {
60-
ndredis = nodeRedis.createClient();
61-
ioredis = new IORedis();
62-
waitReady(function () {
63-
ndredis.set('foo', 'bar', start);
64-
});
51+
bench('javascript parser', function (next) {
52+
redisJ.setBuffer('foo', 'bar', next);
6553
});
6654

67-
bench('ioredis', function (next) {
68-
ioredis.get('foo', next);
55+
bench('hiredis parser + dropBufferSupport: true', function (next) {
56+
redisBD.set('foo', 'bar', next);
6957
});
7058

71-
bench('node_redis', function (next) {
72-
ndredis.get('foo', next);
59+
bench('hiredis parser', function (next) {
60+
redisB.setBuffer('foo', 'bar', next);
7361
});
7462

75-
after(function () {
76-
ndredis.quit();
77-
ioredis.quit();
78-
});
63+
after(quit);
7964
});
8065

81-
suite('simple get with pipeline', function () {
66+
suite('LRANGE foo 0 99', function () {
8267
set('mintime', 5000);
8368
set('concurrency', 300);
8469
before(function (start) {
85-
ndredis = nodeRedis.createClient();
86-
ioredis = new IORedis();
87-
waitReady(function () {
88-
ndredis.set('foo', 'bar', start);
89-
});
90-
});
91-
92-
bench('ioredis', function (next) {
93-
var pipeline = ioredis.pipeline();
94-
for (var i = 0; i < 10; ++i) {
95-
pipeline.get('foo');
96-
}
97-
pipeline.exec(next);
98-
});
99-
100-
bench('node_redis', function (next) {
101-
var pending = 0;
102-
for (var i = 0; i < 10; ++i) {
103-
pending += 1;
104-
ndredis.get('foo', check);
70+
var redis = new Redis();
71+
var item = [];
72+
for (var i = 0; i < 100; ++i) {
73+
item.push((Math.random() * 100000 | 0) + 'str');
10574
}
106-
function check() {
107-
if (!--pending) {
108-
next();
109-
}
110-
}
111-
});
112-
113-
after(function () {
114-
ndredis.quit();
115-
ioredis.quit();
116-
});
117-
});
118-
119-
suite('lrange 100', function () {
120-
set('mintime', 5000);
121-
set('concurrency', 300);
122-
before(function (start) {
123-
ndredis = nodeRedis.createClient();
124-
ioredis = new IORedis();
125-
waitReady(function () {
126-
var item = [];
127-
for (var i = 0; i < 100; ++i) {
128-
item.push((Math.random() * 100000 | 0) + 'str');
129-
}
130-
ndredis.del('foo');
131-
ndredis.lpush('foo', item, start);
132-
});
133-
});
134-
135-
bench('ioredis', function (next) {
136-
ioredis.lrange('foo', 0, 99, next);
137-
});
138-
139-
bench('node_redis', function (next) {
140-
ndredis.lrange('foo', 0, 99, next);
141-
});
142-
143-
after(function () {
144-
ndredis.quit();
145-
ioredis.quit();
146-
});
147-
});
148-
149-
suite('publish', function () {
150-
set('mintime', 5000);
151-
set('concurrency', 300);
152-
153-
before(function (start) {
154-
ndredis = nodeRedis.createClient();
155-
ioredis = new IORedis();
156-
waitReady(function () {
157-
start();
75+
redis.del('foo');
76+
redis.lpush('foo', item, function () {
77+
waitReady(start);
15878
});
15979
});
16080

161-
bench('ioredis', function (next) {
162-
ioredis.publish('foo', 'bar', next);
163-
});
164-
165-
bench('node_redis', function (next) {
166-
ndredis.publish('foo', 'bar', next);
81+
bench('javascript parser + dropBufferSupport: true', function (next) {
82+
redisJD.lrange('foo', 0, 99, next);
16783
});
16884

169-
after(function () {
170-
ndredis.quit();
171-
ioredis.quit();
172-
});
173-
});
174-
175-
suite('subscribe', function () {
176-
set('mintime', 5000);
177-
set('concurrency', 300);
178-
179-
var ndpublisher = null;
180-
var iopublisher = null;
181-
var ndsubscriber = null;
182-
var iosubscriber = null;
183-
184-
before(function (start) {
185-
ndredis = nodeRedis.createClient();
186-
ioredis = new IORedis();
187-
waitReady(function () {
188-
ndsubscriber = ndredis;
189-
ndsubscriber.subscribe('foo');
190-
iosubscriber = ioredis;
191-
iosubscriber.subscribe('foo');
192-
193-
ndredis = nodeRedis.createClient();
194-
ioredis = new IORedis();
195-
waitReady(function () {
196-
ndpublisher = ndredis;
197-
iopublisher = ioredis;
198-
start();
199-
});
200-
});
85+
bench('javascript parser', function (next) {
86+
redisJ.lrangeBuffer('foo', 0, 99, next);
20187
});
20288

203-
bench('ioredis', function (next) {
204-
iosubscriber.removeAllListeners('message');
205-
ndsubscriber.removeAllListeners('message');
206-
iosubscriber.on('message', next);
207-
iopublisher.publish('foo', 'bar');
89+
bench('hiredis parser + dropBufferSupport: true', function (next) {
90+
redisBD.lrange('foo', 0, 99, next);
20891
});
20992

210-
bench('node_redis', function (next) {
211-
iosubscriber.removeAllListeners('message');
212-
ndsubscriber.removeAllListeners('message');
213-
ndsubscriber.on('message', next);
214-
ndpublisher.publish('foo', 'bar');
93+
bench('hiredis parser', function (next) {
94+
redisB.lrangeBuffer('foo', 0, 99, next);
21595
});
21696

217-
after(function () {
218-
ndredis.quit();
219-
ioredis.quit();
220-
});
97+
after(quit);
22198
});

lib/commander.js

+25-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
var _ = require('lodash');
44
var Command = require('./command');
55
var Script = require('./script');
6+
var Promise = require('bluebird');
7+
8+
var DROP_BUFFER_SUPPORT_ERROR = '*Buffer methods are not available ' +
9+
'because "dropBufferSupport" option is enabled.' +
10+
'Refer to https://github.com/luin/ioredis/wiki/Improve-Performance for more details.';
611

712
/**
813
* Commander
@@ -106,7 +111,16 @@ function generateFunction(_commandName, _encoding) {
106111
args[i - firstArgIndex] = arguments[i];
107112
}
108113

109-
var options = { replyEncoding: _encoding };
114+
var options;
115+
if (this.options.dropBufferSupport) {
116+
if (!_encoding) {
117+
return Promise.reject(new Error(DROP_BUFFER_SUPPORT_ERROR)).nodeify(callback);
118+
}
119+
options = { replyEncoding: null };
120+
} else {
121+
options = { replyEncoding: _encoding };
122+
}
123+
110124
if (this.options.showFriendlyErrorStack) {
111125
options.errorStack = new Error().stack;
112126
}
@@ -133,7 +147,16 @@ function generateScriptingFunction(_script, _encoding) {
133147
args[i] = arguments[i];
134148
}
135149

136-
var options = { replyEncoding: _encoding };
150+
var options;
151+
if (this.options.dropBufferSupport) {
152+
if (!_encoding) {
153+
return Promise.reject(new Error(DROP_BUFFER_SUPPORT_ERROR)).nodeify(callback);
154+
}
155+
options = { replyEncoding: null };
156+
} else {
157+
options = { replyEncoding: _encoding };
158+
}
159+
137160
if (this.options.showFriendlyErrorStack) {
138161
options.errorStack = new Error().stack;
139162
}

lib/redis.js

+6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ var ScanStream = require('./scan_stream');
3636
* @param {number} [options.db=0] - Database index to use.
3737
* @param {string} [options.password=null] - If set, client will send AUTH command
3838
* with the value of this option when connected.
39+
* @param {string} [options.parser=null] - Either "hiredis" or "javascript". If not set, "hiredis" parser
40+
* will be used if it's installed (`npm install hiredis`), otherwise "javascript" parser will be used.
41+
* @param {boolean} [options.dropBufferSupport=false] - Drop the buffer support for better performance.
42+
* This option is recommanded to be enabled when "hiredis" parser is used.
43+
* Refer to https://github.com/luin/ioredis/wiki/Improve-Performance for more details.
3944
* @param {boolean} [options.enableReadyCheck=true] - When a connection is established to
4045
* the Redis server, the server might still be loading the database from disk.
4146
* While loading, the server not respond to any commands.
@@ -166,6 +171,7 @@ Redis.defaultOptions = {
166171
db: 0,
167172
// Others
168173
parser: null,
174+
dropBufferSupport: false,
169175
enableOfflineQueue: true,
170176
enableReadyCheck: true,
171177
autoResubscribe: true,

lib/redis/parser.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ exports.initParser = function () {
2020
this.replyParser = new Parser({
2121
name: this.options.parser,
2222
stringNumbers: this.options.stringNumbers,
23-
returnBuffers: true,
23+
returnBuffers: !this.options.dropBufferSupport,
2424
returnError: function (err) {
2525
_this.returnError(new ReplyError(err.message));
2626
},
@@ -33,6 +33,12 @@ exports.initParser = function () {
3333
_this.disconnect(true);
3434
}
3535
});
36+
37+
if (this.replyParser.name === 'hiredis' && !this.options.dropBufferSupport) {
38+
console.warn('[WARN] ioredis is using hiredis parser, however "dropBufferSupport" is disabled. ' +
39+
'It\'s highly recommanded to enable this option. ' +
40+
'Refer to https://github.com/luin/ioredis/wiki/Improve-Performance for more details.');
41+
}
3642
};
3743

3844
exports.returnError = function (err) {

0 commit comments

Comments
 (0)