Riak 2.x client with weighted load balancing and easy to use wrappers over CRDT data types
npm install no-riak[![Build Status][badge-travis]][travis]
[![Test Coverage][badge-coverage]][coverage]
[![david Dependencies][badge-david-deps]][david-deps]
[![david Dev Dependencies][badge-david-dev-deps]][david-dev-deps]
[![license][badge-license]][license]
__no-riak__ is a Basho Riak KV client for Node.js with easy to use wrappers over CRDT data types.
Supports Riak authentication, conection pooling and balancing across multiple servers according to their weight.
All methods will return a promise
* Installation
* Usage
* Quick example
* Key/Value operations
* Secondary indexes
* Map/Reduce
* Operations on Buckets and Bucket Types
* Other operations
* Authentication
* CRDT Data Types
* Counter
* Set
* Map
* Connection pooling and load balancing
* Handling connection errors
```
npm install no-riak
`javascript
var Riak = require('no-riak');
var client = new Riak.Client();
return client.put({
bucket: 'test-bucket',
key: 'key1',
content: {
value: 'hello'
}
})
.then(function () {
return client.get({
bucket: 'test-bucket',
key: 'key1'
});
})
.then(function (result) {
// result => { content:
// [ { value:
// vtag: '7V2EHl2Wh06SCAIl4y7M2Y',
// last_mod: 1454584844,
// last_mod_usecs: 893098 } ],
// vclock: 'a85hYGBgzGDKBVI8ypz/fn5Ie3OPQeizegZTIlMeKwPPBvULfFkA' }
console.log(result.content[0].value.toString()); // => 'hello'
});
`
- get(params)put(params)
- del(params)
- update(params)
- listKeys(params)
- updateCounter(params)
- getCounter(params)
-
- index(params)
Example:
`javascript
var bucket = 'no-riak-test-kv';
return Promise.all([0, 1, 2].map(function (i) {
return client.put({
bucket: bucket,
key: 'key' + i,
content: {
value: 'i' + i,
indexes: [{
key: 'no-riak-test_bin',
value: 'indexValue'
}]
}
});
}))
.then(function () {
return client.index({
bucket: bucket,
index: 'no-riak-test_bin',
qtype: 0,
max_results: 2,
key: 'indexValue'
});
})
.then(function (result) {
// result => { results: [ 'key0', 'key1' ], continuation: 'g20AAAAEa2V5MQ==' }
// now get rest of search results:
return client.index({
bucket: bucket,
index: 'no-riak-test_bin',
qtype: 0,
continuation: result.continuation,
key: 'indexValue'
});
})
.then(function (result){
// result => { results: [ 'key2' ] }
});
`
- mapReduce()
Example:
`javascriptnum
var bucket = 'no-riak-test-kv';
var keys = [];
return Promise.all([0, 1, 2].map(function (i) {
keys[i] = 'mr_key' + i;
return client.put({
bucket: bucket,
key: keys[i],
content: {
value: {
num: i + 10
}
}
});
}))
.then(function () {
return client.mapReduce({
request: {
inputs: keys.map(function (k) { return [bucket, k]; }),
query: [{
map: { // this phase will return JSON decoded values for each input
source: 'function(v) { var d = Riak.mapValuesJson(v)[0]; return [d]; }',
language: 'javascript',
keep: true
}
}, { // this phase will return the property of each value
reduce: {
source: 'function(values) { return values.map(function(v){ return v.num; }); }',
language: 'javascript',
keep: true
}
}, { // this phase will return a sum of all values
reduce: {
module: 'riak_kv_mapreduce',
function: 'reduce_sum',
language: 'erlang',
keep: true
}
}]
}
});
})
.then(function (results) {
// [ [ { num: 10 }, { num: 12 }, { num: 11 } ], // thats phase 1 results
// [ 10, 12, 11 ], // phase 2 results
// [ 33 ] ] // phase 3 results
// each index in results array is an array of results for each map/reduce phase
// even if phase results were stripped with keep: false
});
`
- listBuckets()getBucket()
- setBucket()
- resetBucket()
- getBucketType()
- setBucketType()
-
Example:
`javascript`
return client.setBucket({
bucket: 'some-bucket',
props: {
allow_mult: true,
r: 'all' // possible string values are: one, quorum, all, default
}
});
- ping()getServerInfo()
-
Enable authentication in Riak, create user, add corresponding grants, example:
`bash`
riak-admin security enable
riak-admin security add-user test password=secret
riak-admin security grant riak_kv.put,riak_kv.get on any to test
riak-admin security add-source test 127.0.0.1/32 password
And then simply provide auth option when creating Client:
`javascript`
var client = new Riak.Client({
auth: {
user: 'test',
password: 'secret'
}
});
All communication will be encrypted over TLS. You can override TLS options:
`javascript`
var client = new Riak.Client({
auth: {
user: 'test',
password: 'secret'
},
tls: {
secureProtocol: 'SSLv23_method',
rejectUnauthorized: false,
ciphers: 'DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:AES128-SHA256:AES128-SHA:AES256-SHA256:AES256-SHA:RC4-SHA'
}
});
You can operate on a lower level with Riak CRDT Data Types with the following methods:
- dtFetch()dtUpdate()
-
__no-riak__ also provides easy to use wrappers over Map, Set and Counter.
Represents signed 64 bit integer (via long.js)
- increment(value) [sync] increment counter value with positive or negative value, returns thiskey()
- [sync] get counter keyvalue()
- [sync] return counter valueload()
- [async] load counter value from Riak and return this in a Promisesave()
- [async] save counter to Riak and return this in a Promise
`javascript
var Riak = require('no-riak');
var client = new Riak.Client();
var bucket = 'no_riak_test_crdt_counter_bucket';
var bucketType = 'no_riak_test_crdt_counter';
var counter = new Riak.CRDT.Counter(client, {
bucket: bucket,
type: bucketType
});
return counter.increment(1).increment(-5).save().call('value')
.then(function (v) {
// v.toNumber() => -4
});
`
Represents an array of uniqe opaque Buffer values.
- key() [sync] get set keyvalue()
- [sync] return set valueload()
- [async] load set value from Riak and return this in a Promisesave()
- [async] save set to Riak and return this in a Promiseadd(value)
- [sync] add new value to set, returns thisremove(value)
- [sync] removes value from the set, returns this
Example:
`javascript
var bucket = 'no_riak_test_crdt_set_bucket';
var bucketType = 'no_riak_test_crdt_set';
var set = new Riak.CRDT.Set(client, {
bucket: bucket,
type: bucketType
});
return set
.add('a1', 'a2', 'a3', 'a2', 'a2', 'a3')
.remove('a1')
.save()
.call('value')
.then(function (v) {
// v => ['a2', 'a3']
});
`
By default __no-riak__ will convert set values to strings, if you want to stick with buffers, pass strings: false option to Set constructor:
`javascript`
var set = new Riak.CRDT.Set(client, {
bucket: bucket,
type: bucketType,
strings: false
});
Represents a list of name/value pairs. Values can be Counters, Sets, Maps, Registers and Flags.
- key() [sync] get map keyvalue()
- [sync] return map valueload()
- [async] load map value from Riak and return this in a Promisesave()
- [async] save map to Riak and return this in a Promiseupdate(name, value)
- [sync] update existing field or add new field to map, returns thisremove(name, type)
- [sync] remove existing field from the map. constructor is one of the following: Riak.CRDT.Counter, Riak.CRDT.Set, Riak.CRDT.Map, Riak.CRDT.Map.Register, Riak.CRDT.Map.Flagget(name)
- [sync] get Riak.CRDT.* instance for corresponding field name. This instance can be used to update the map.
Example:
`javascript
var bucket = 'no_riak_test_crdt_map_bucket';
var bucketType = 'no_riak_test_crdt_map';
var map = new Riak.CRDT.Map(client, {
bucket: bucket,
type: bucketType
});
return map
.update('key1', new Riak.CRDT.Counter().increment(-5))
.update('key2', new Riak.CRDT.Set().add('a1', 'a2', 'a3').remove('a2'))
.save()
.call('value')
.then(function (v) {
console.log(v); // => { key1: { low: -5, high: -1, unsigned: false }, key2: [ 'a1', 'a3' ] }
});
`
Using get(name) to operate on map fields
`javascript
var set;
var map = new Riak.CRDT.Map(client, {
bucket: bucket,
type: bucketType
});
map
.update('key1', new Riak.CRDT.Counter().increment(-5))
.update('key2', new Riak.CRDT.Set().add('a1', 'a2', 'a3'));
set = map.get('key2');
set.remove('a2');
map.save().call('value').then(function (v){
console.log(v); // => { key1: { low: -5, high: -1, unsigned: false }, key2: [ 'a1', 'a3' ] }
});
`
Using Map.register and Map.Flag:
`javascript
var map = new Riak.CRDT.Map(client, {
bucket: bucket,
type: bucketType
});
map
.update('key1', new Riak.CRDT.Map.Register().set('a1'))
.update('key2', new Riak.CRDT.Map.Flag().enable())
.save()
.call('value')
.then(function (v){
console.log(v); // { key1: 'a1', key2: true }
});
`
Set and Register values in a Map will be by default converted to strings, pass strings: false to Map constructor to receive buffers instead:
`javascript`
var map = new Riak.CRDT.Map(client, {
bucket: bucket,
type: bucketType,
strings: false
});
__no-riak__ can balance requests across a pool of connections. It can also fill the connections pool according to the list of servers and their corresponding weight:
`javascript`
var client = new Riak.Client({
connectionString: '10.0.1.1:8087:4,10.0.1.2:8087:3,10.0.1.3:8087:2',
pool: {
min: 9
}
});
Here, 9 connections will be created when client starts, 4 of which will connect to '10.0.1.1', 3 to '10.0.1.2' and 2 to '10.0.1.3'. When there is a demand for more connections, no-riak will create up to pool.max connections and will also split them across servers considering their weight.
__no-riak__ will retry any request that failed due to network (connection) error, the corresponding option is
* retries - number of retries for each failed request, defaults to 3
__no-riak__ can temporary remove the server whose connections are failing with some kind of network error (socket timeout, connection refused, etc).
Such server will be assigned effective weight=0 and and so its connections will be replaced by connections to other servers in the cluster.
__no-riak__ will then periodically check disabled servers and restore them with their original weight once they are back online.
Two options help control this behaviour:
* maxConnectionErrors - maximum number of connections errors for the server, defaults to 3maxConnectionErrorsPeriod
* - period in ms which is considered when counting number of errors, defaults to 60000 (1 min)
Default options mean that if any server had 3 or more errors within last minute then this server is marked as down.
__no-riak__ will emit two events which can be useful to track disabled servers:
* net:hostdown - emitted when host has reached configured error rate and is now temporary disablednet:hostup
* - emitted when host is ready to accept new connections and will now take part in load balancing
You can also query current connections pool state like this:
`javascript``
var stats = client.pool.count();
// => { free: { '10.0.1.5:8087': 5, '10.0.1.3:8087': 5, '10.0.1.4:8087': 5, '10.0.1.1:8087': 1, '10.0.1.2:8087': 1 }, busy: {} }
[badge-license]: https://img.shields.io/badge/License-MIT-green.svg
[license]: https://github.com/oleksiyk/no-riak/blob/master/LICENSE
[badge-travis]: https://api.travis-ci.org/oleksiyk/no-riak.svg?branch=master
[travis]: https://travis-ci.org/oleksiyk/no-riak
[badge-coverage]: https://codeclimate.com/github/oleksiyk/no-riak/badges/coverage.svg
[coverage]: https://codeclimate.com/github/oleksiyk/no-riak/coverage
[badge-david-deps]: https://david-dm.org/oleksiyk/no-riak.svg
[david-deps]: https://david-dm.org/oleksiyk/no-riak
[badge-david-dev-deps]: https://david-dm.org/oleksiyk/no-riak/dev-status.svg
[david-dev-deps]: https://david-dm.org/oleksiyk/no-riak#info=devDependencies
[badge-bithound-code]: https://www.bithound.io/github/oleksiyk/no-riak/badges/code.svg
[bithound-code]: https://www.bithound.io/github/oleksiyk/no-riak
[badge-bithound-overall]: https://www.bithound.io/github/oleksiyk/no-riak/badges/score.svg
[bithound-overall]: https://www.bithound.io/github/oleksiyk/no-riak
[badge-bithound-deps]: https://www.bithound.io/github/oleksiyk/no-riak/badges/dependencies.svg
[bithound-deps]: https://www.bithound.io/github/oleksiyk/no-riak/master/dependencies/npm
[badge-bithound-dev-deps]: https://www.bithound.io/github/oleksiyk/no-riak/badges/devDependencies.svg
[bithound-dev-deps]: https://www.bithound.io/github/oleksiyk/no-riak/master/dependencies/npm