Skip to content

Commit

Permalink
feat: revisit of .to(nodeGroup) command
Browse files Browse the repository at this point in the history
Provides ability to send arbitrary commands too all nodes in a requested group or
just a handy helper to select nodes from such a group

Supports 'all', 'masters' and 'slaves' groups exposing `call` and `callBuffer` command
semantics similar to Commander
  • Loading branch information
AVVS committed Jan 22, 2016
1 parent 1e26631 commit ba12e47
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 0 deletions.
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,43 @@ but a few so that if one is unreachable the client will try the next one, and th
* `retryDelayOnClusterDown`: When a cluster is down, all commands will be rejected with the error of `CLUSTERDOWN`. If this option is a number (by default, it is 1000), the client
will resend the commands after the specified time (in ms).

### Running same operation on multiple nodes

Some operations, such as `flushdb` and `flushall`, can only be performed on a single node.
There is a helper function for this case:

```javascript
var redis = new redis.Cluster();

// groups are: all, masters and slaves
// returns object or throws an error on invalid group
// {
// nodes: Array,
// call: Function,
// callBuffer: function,
// }
redis.to('group')

// will be performed on all master nodes
Promise.map(redis.to('masters').nodes, function(node) {
return node.flushdb();
});

// exactly the same as above
redis.to('masters').call('flushdb').then(function (results) {
//
})

redis.to('slaves').call('flushdb', function (err, results) {

});

// in case of buffer
redis.to('slaves').callBuffer('get', 'key').catch(function (err) {
// likely rejected, because operations would only succeed partially due to slot | moved error
});
```

### Transaction and pipeline in Cluster mode
Almost all features that are supported by `Redis` are also supported by `Redis.Cluster`, e.g. custom commands, transaction and pipeline.
However there are some differences when using transaction and pipeline in Cluster mode:
Expand Down
53 changes: 53 additions & 0 deletions lib/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,59 @@ Cluster.prototype.executeClusterDownCommands = function () {
}
};

Cluster.prototype.to = function (name) {
var fnName = '_select' + name[0].toUpperCase() + name.slice(1);
var fn = typeof this[fnName];
if (typeof fn !== 'function') {
// programmatic error, can't happen in prod, so throw
throw new Error('to ' + name + ' is not a valid group of nodes');
}

// could be 0 nodes just as well
var nodes = fn();
return {
nodes: nodes,
call: this._generateCallNodes(nodes, 'call'),
callBuffer: this._generateCallNodes(nodes, 'callBuffer')
};
};

Cluster.prototype._generateCallNodes = function (nodes, op, _opts) {
var opts = _opts || {};

return function callNode() {
var argLength = arguments.length;
var hasCb = typeof arguments[argLength - 1] === 'function';
var args = new Array(argLength);
for (var i = 0; i < argLength; ++i) {
args[i] = arguments[i];
}

var callback = hasCb ? args.pop() : null;
var promise = Promise.map(function (node) {
return node[op].apply(node, args);
}, opts);

if (callback) {
return promise.nodeify(callback);
}

return promise;
};
};

Cluster.prototype._selectAll = function () {
return _.values(this.nodes);
};

Cluster.prototype._selectMasters = function () {
return _.values(this.masterNodes);
};

Cluster.prototype._selectSlaves = function () {
return _.difference(this._selectAll(), this._selectMasters());
};

Cluster.prototype.sendCommand = function (command, stream, node) {
if (this.status === 'end') {
command.reject(new Error('Connection is closed.'));
Expand Down

0 comments on commit ba12e47

Please sign in to comment.