Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add dropBufferSupport option to improve the performance #293

Merged
merged 6 commits into from
May 7, 2016
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ Creates a Redis instance
| [options.connectionName] | <code>string</code> | <code>null</code> | Connection name. |
| [options.db] | <code>number</code> | <code>0</code> | Database index to use. |
| [options.password] | <code>string</code> | <code>null</code> | If set, client will send AUTH command with the value of this option when connected. |
| [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. |
| [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. |
| [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. |
| [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. |
| [options.connectTimeout] | <code>number</code> | <code>10000</code> | The milliseconds before a timeout occurs during the initial connection to the Redis server. |
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -807,10 +807,9 @@ var cluster = new Redis.Cluster([
});
```

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

<hr>

Expand Down
217 changes: 47 additions & 170 deletions benchmarks/single_node.js
Original file line number Diff line number Diff line change
@@ -1,221 +1,98 @@
'use strict';

var childProcess = require('child_process');
var nodeRedis = require('redis');
var IORedis = require('../');
var ndredis, ioredis;
var Redis = require('../');

console.log('==========================');
console.log('ioredis: ' + require('../package.json').version);
console.log('node_redis: ' + require('redis/package.json').version);
console.log('redis: ' + require('../package.json').version);
var os = require('os');
console.log('CPU: ' + os.cpus().length);
console.log('OS: ' + os.platform() + ' ' + os.arch());
console.log('node version: ' + process.version);
console.log('current commit: ' + childProcess.execSync('git rev-parse --short HEAD'));
console.log('==========================');

var redisJD, redisJ, redisBD, redisB;
var waitReady = function (next) {
var pending = 2;
ndredis.on('ready', function () {
var pending = 4;
function check() {
if (!--pending) {
next();
}
});
}
redisJD = new Redis({ parser: 'javascript', dropBufferSupport: true });
redisJ = new Redis({ parser: 'javascript', dropBufferSupport: false });
redisBD = new Redis({ parser: 'hiredis', dropBufferSupport: true });
redisB = new Redis({ parser: 'hiredis', dropBufferSupport: false });
redisJD.on('ready', check);
redisJ.on('ready', check);
redisBD.on('ready', check);
redisB.on('ready', check);
};

ioredis.on('ready', function () {
if (!--pending) {
next();
}
});
var quit = function () {
redisJD.quit();
redisJ.quit();
redisBD.quit();
redisB.quit();
};

suite('simple set', function () {
suite('SET foo bar', function () {
set('mintime', 5000);
set('concurrency', 300);
before(function (start) {
ndredis = nodeRedis.createClient();
ioredis = new IORedis();
waitReady(start);
});

bench('ioredis', function (next) {
ioredis.set('foo', 'bar', next);
});

bench('node_redis', function (next) {
ndredis.set('foo', 'bar', next);
bench('javascript parser + dropBufferSupport: true', function (next) {
redisJD.set('foo', 'bar', next);
});

after(function () {
ndredis.quit();
ioredis.quit();
});
});

suite('simple get', function () {
set('mintime', 5000);
set('concurrency', 300);
before(function (start) {
ndredis = nodeRedis.createClient();
ioredis = new IORedis();
waitReady(function () {
ndredis.set('foo', 'bar', start);
});
bench('javascript parser', function (next) {
redisJ.setBuffer('foo', 'bar', next);
});

bench('ioredis', function (next) {
ioredis.get('foo', next);
bench('hiredis parser + dropBufferSupport: true', function (next) {
redisBD.set('foo', 'bar', next);
});

bench('node_redis', function (next) {
ndredis.get('foo', next);
bench('hiredis parser', function (next) {
redisB.setBuffer('foo', 'bar', next);
});

after(function () {
ndredis.quit();
ioredis.quit();
});
after(quit);
});

suite('simple get with pipeline', function () {
suite('LRANGE foo 0 99', function () {
set('mintime', 5000);
set('concurrency', 300);
before(function (start) {
ndredis = nodeRedis.createClient();
ioredis = new IORedis();
waitReady(function () {
ndredis.set('foo', 'bar', start);
});
});

bench('ioredis', function (next) {
var pipeline = ioredis.pipeline();
for (var i = 0; i < 10; ++i) {
pipeline.get('foo');
}
pipeline.exec(next);
});

bench('node_redis', function (next) {
var pending = 0;
for (var i = 0; i < 10; ++i) {
pending += 1;
ndredis.get('foo', check);
var redis = new Redis();
var item = [];
for (var i = 0; i < 100; ++i) {
item.push((Math.random() * 100000 | 0) + 'str');
}
function check() {
if (!--pending) {
next();
}
}
});

after(function () {
ndredis.quit();
ioredis.quit();
});
});

suite('lrange 100', function () {
set('mintime', 5000);
set('concurrency', 300);
before(function (start) {
ndredis = nodeRedis.createClient();
ioredis = new IORedis();
waitReady(function () {
var item = [];
for (var i = 0; i < 100; ++i) {
item.push((Math.random() * 100000 | 0) + 'str');
}
ndredis.del('foo');
ndredis.lpush('foo', item, start);
});
});

bench('ioredis', function (next) {
ioredis.lrange('foo', 0, 99, next);
});

bench('node_redis', function (next) {
ndredis.lrange('foo', 0, 99, next);
});

after(function () {
ndredis.quit();
ioredis.quit();
});
});

suite('publish', function () {
set('mintime', 5000);
set('concurrency', 300);

before(function (start) {
ndredis = nodeRedis.createClient();
ioredis = new IORedis();
waitReady(function () {
start();
redis.del('foo');
redis.lpush('foo', item, function () {
waitReady(start);
});
});

bench('ioredis', function (next) {
ioredis.publish('foo', 'bar', next);
});

bench('node_redis', function (next) {
ndredis.publish('foo', 'bar', next);
bench('javascript parser + dropBufferSupport: true', function (next) {
redisJD.lrange('foo', 0, 99, next);
});

after(function () {
ndredis.quit();
ioredis.quit();
});
});

suite('subscribe', function () {
set('mintime', 5000);
set('concurrency', 300);

var ndpublisher = null;
var iopublisher = null;
var ndsubscriber = null;
var iosubscriber = null;

before(function (start) {
ndredis = nodeRedis.createClient();
ioredis = new IORedis();
waitReady(function () {
ndsubscriber = ndredis;
ndsubscriber.subscribe('foo');
iosubscriber = ioredis;
iosubscriber.subscribe('foo');

ndredis = nodeRedis.createClient();
ioredis = new IORedis();
waitReady(function () {
ndpublisher = ndredis;
iopublisher = ioredis;
start();
});
});
bench('javascript parser', function (next) {
redisJ.lrangeBuffer('foo', 0, 99, next);
});

bench('ioredis', function (next) {
iosubscriber.removeAllListeners('message');
ndsubscriber.removeAllListeners('message');
iosubscriber.on('message', next);
iopublisher.publish('foo', 'bar');
bench('hiredis parser + dropBufferSupport: true', function (next) {
redisBD.lrange('foo', 0, 99, next);
});

bench('node_redis', function (next) {
iosubscriber.removeAllListeners('message');
ndsubscriber.removeAllListeners('message');
ndsubscriber.on('message', next);
ndpublisher.publish('foo', 'bar');
bench('hiredis parser', function (next) {
redisB.lrangeBuffer('foo', 0, 99, next);
});

after(function () {
ndredis.quit();
ioredis.quit();
});
after(quit);
});
27 changes: 25 additions & 2 deletions lib/commander.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
var _ = require('lodash');
var Command = require('./command');
var Script = require('./script');
var Promise = require('bluebird');

var DROP_BUFFER_SUPPORT_ERROR = '*Buffer methods are not available ' +
'because "dropBufferSupport" option is enabled.' +
'Refer to /~https://github.com/luin/ioredis/wiki/Improve-Performance for more details.';

/**
* Commander
Expand Down Expand Up @@ -106,7 +111,16 @@ function generateFunction(_commandName, _encoding) {
args[i - firstArgIndex] = arguments[i];
}

var options = { replyEncoding: _encoding };
var options;
if (this.options.dropBufferSupport) {
if (!_encoding) {
return Promise.reject(new Error(DROP_BUFFER_SUPPORT_ERROR)).nodeify(callback);
}
options = { replyEncoding: null };
} else {
options = { replyEncoding: _encoding };
}

if (this.options.showFriendlyErrorStack) {
options.errorStack = new Error().stack;
}
Expand All @@ -133,7 +147,16 @@ function generateScriptingFunction(_script, _encoding) {
args[i] = arguments[i];
}

var options = { replyEncoding: _encoding };
var options;
if (this.options.dropBufferSupport) {
if (!_encoding) {
return Promise.reject(new Error(DROP_BUFFER_SUPPORT_ERROR)).nodeify(callback);
}
options = { replyEncoding: null };
} else {
options = { replyEncoding: _encoding };
}

if (this.options.showFriendlyErrorStack) {
options.errorStack = new Error().stack;
}
Expand Down
6 changes: 6 additions & 0 deletions lib/redis.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ var ScanStream = require('./scan_stream');
* @param {number} [options.db=0] - Database index to use.
* @param {string} [options.password=null] - If set, client will send AUTH command
* with the value of this option when connected.
* @param {string} [options.parser=null] - 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.
* @param {boolean} [options.dropBufferSupport=false] - 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.
* @param {boolean} [options.enableReadyCheck=true] - 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.
Expand Down Expand Up @@ -166,6 +171,7 @@ Redis.defaultOptions = {
db: 0,
// Others
parser: null,
dropBufferSupport: false,
enableOfflineQueue: true,
enableReadyCheck: true,
autoResubscribe: true,
Expand Down
8 changes: 7 additions & 1 deletion lib/redis/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ exports.initParser = function () {
this.replyParser = new Parser({
name: this.options.parser,
stringNumbers: this.options.stringNumbers,
returnBuffers: true,
returnBuffers: !this.options.dropBufferSupport,
returnError: function (err) {
_this.returnError(new ReplyError(err.message));
},
Expand All @@ -33,6 +33,12 @@ exports.initParser = function () {
_this.disconnect(true);
}
});

if (this.replyParser.name === 'hiredis' && !this.options.dropBufferSupport) {
console.warn('[WARN] ioredis is using hiredis parser, however "dropBufferSupport" is disabled. ' +
'It\'s highly recommanded to enable this option. ' +
'Refer to /~https://github.com/luin/ioredis/wiki/Improve-Performance for more details.');
}
};

exports.returnError = function (err) {
Expand Down
Loading