Developing with Riak Redis Add-on
This page will walk you through setting up your environment for development with Riak Redis Add-on (RRA), as well as present examples and configuration parameters for basic development operations.
Overview
Riak Redis Add-on (RRA) packages a cache proxy service. The cache proxy service provides accessibility to Riak TS, as a persistent data store, with Redis, as a cache through the various Redis client libraries and command-line interface tool redis-cli
.
As with Riak TS, the cache proxy service almost always performs best and most predictably when you use the basic CRUD operations – Create, Read, Update, Delete – that you’d find in any key/value store. Learning these operations is a great place to start when beginning to develop applications that use RRA.
The set of clients (including recommendations) for Redis are listed at Redis clients. For brevity sake, examples provided here are in:
- Erlang (Eredis)
- Javascript (node_redis)
- Python (redis-py)
- Ruby (redis-rb)
- Scala (lettuce)
- Java, see the Scala examples. The code intentionally uses as few Scala tricks as possible to focus on the use of the Redis client.
Riak TS Setup
While you can use Riak Redis Add-on with Riak TS configured so either last_write_wins
is set to ‘true’ or allow_mult
is set to ‘true’, we recommend using the allow_mult
setting in order to provide client sibling resolution in the event of a network partition. The examples and instructions on this page will assume allow_mult
is set to ‘true’.
The cache proxy service is tested under both configurations. However, due to lack of support via the Redis protocol for returning multiple values for a single GET
, effectively last_write_wins
semantics apply.
For a deeper explanation of Riak TS’s configurable behaviors, see John Daily’s blog series part 4 .
Bucket Type Setup
Create a Bucket Type
If your application organizes data in a way that does not include bucket-type
and instead only uses bucket to organize its keyspace, the default
bucket-type
can be used by omitting the bucket-type portion of the colon-delimited
hierarchical namespaced key. Otherwise said, test:food
is equivalent to
default:test:food
where the bucket-type is default
, the bucket is test
,
and the key is food
. For examples here, we will use rra:test:food
to clearly
use a bucket-type.
If your application organizes data including a bucket-type, ensure that that
bucket-type is created in Riak without specifying the data type, so effectively
an opaque value, ie a string
. The following command provides an example of
creating the bucket-type rra
:
if ! riak-admin bucket-type status rra >/dev/null 2>&1; then
riak-admin bucket-type create rra '{"props":{}}'
riak-admin bucket-type activate rra
fi
Set Bucket Props
The following is an example, using Riak TS’s default HTTP port, of setting allow_mult
to ‘true’ and last_write_wins
to ‘false’:
curl -XPUT -H 'Content-Type: application/json' /
-d '{"props": {"allow_mult": true, "last_write_wins": false}}' /
'http://127.0.0.1:8098/types/rra/buckets/test/props'
For additional configuration options see bucket properties.
Object/Key Operations
Riak TS organizes data into buckets, keys, and values, with bucket types acting as an additional namespace in Riak TS versions 1.0 and greater. Values, which we’ll refer to as objects, are identifiable by a unique key, and each key/value pair is stored in a bucket.
Objects accessed via the cache proxy service in Riak Redis Add-on are restricted to plaintext format. This plaintext format may be a simple string, JSON, XML, or other plaintext representations that can be parsed in the client application (e.g. YAML).
While buckets are a flat namespace in Riak TS and you can name them
whatever you’d like (bucket
or a90bf521c
or ___
), within the cache proxy
service, Redis bucket_type:bucket:key is mapped to Riak TS
bucket_type/bucket/key, so bucket type and bucket names should not contain
colon (:
). When not specified, bucket type defaults to “default”.
Outside of the above restriction, bucket names have no intrinsic significance beyond allowing you to store objects with the same key in different buckets.
The same goes for naming keys: many objects can have the same key as long as they’re in different buckets. There is no restriction on key containing colon (:
), and this practice of representing a nested namespace is common in applications using Redis.
Riak TS bucket types enable you to provide common configurations for buckets (as many buckets as you wish). This means you can easily enable buckets to share common configurations, i.e. identical replication properties or commit hooks.
Reading Objects
Reads via the cache proxy service are analogous to a Redis GET
, with the added benefit of reading-through to Riak TS which results in greater resilience through node outages and network partitions.
To request a value at a bucket/key in Riak TS, issue the following:
{ok, RedisClientPid} = eredis:start_link("127.0.0.1", 22122).
{ok, Value} = eredis:q(RedisClientPid, ["GET", "rra:test:food"]).
var redis = require("redis"),
client = redis.createClient(22122, "127.0.0.1");
client.get("rra:test:food", redis.print);
import redis
r = redis.StrictRedis(host="127.0.0.1", port=22122)
r.get("rra:test:food")
require "redis"
redis = Redis.new(host: "127.0.0.1", port: 22122)
redis.get("rra:test:food")
import com.lambdaworks.redis._
var client = RedisClient.create("redis://127.0.0.1:22122")
var connection = client.connect()
var value = connection.get("rra:test:food")
Get Configuration Parameters
Note: The cache proxy service read option (related to replication factor and consistency concern) may optionally be set within the nutcracker.conf. This will result in an override of the setting value at the bucket-level in Riak TS.
The following configuration parameters apply to GET
and may be set within the
RRA configuration file /etc/cache_proxy/cache_proxy_22122.yml
:
Parameter | Description | Default |
---|---|---|
n_val |
The number of replicas for objects in a bucket. The n_val should be an integer greater than 0 and less than or equal to the number of nodes in the cluster.NOTE: If you change the n_val after keys have been added to the bucket it may result in failed reads, as the new value may not be replicated to all of the appropriate partitions. |
3 |
pr |
How many vnodes must respond for a read to be deemed successful. | 0 |
r |
How many replicas need to agree when retrieving an existing object before responding. | 2 |
basic_quorum |
Whether to return early in some failure cases, e.g. when r =1 and you get 2 errors and a success. |
0 (false) |
sloppy_quorum |
Whether to treat vnodes holding values for another vnode as acceptable within the quorum determination. | 0 (false) |
notfound_ok |
Whether to treat notfounds as successful reads for the purpose of r . |
1 (true) |
timeout |
The number of milliseconds to await a response. | 0 (server specified) |
Sibling Resolution
As the Redis protocol does not provide a means to return multiple siblings, the cache proxy service must provide server-side sibling resolution. At present, only last-write-wins sibling resolution is available. The result is an effective last-write-wins configuration for access through the cache proxy service.
Writing Objects
Writes via the cache proxy service are analogous to a Redis SET
, with the added
benefit of writing to Riak TS followed by a PEXPIRE
to Redis, invalidating
cache. As with HTTP PUT, SET
semantically covers both create and update
operations.
To set a value at a bucket/key in Riak TS, issue the following:
{ok, RedisClientPid} = eredis:start_link("127.0.0.1", 22122).
{ok, KeysAffected} = eredis:q(RedisClientPid, ["SET", "rra:test:food", "apple"]).
var redis = require("redis"),
client = redis.createClient(22122, "127.0.0.1");
client.set("rra:test:food", "apple", redis.print);
import redis
r = redis.StrictRedis(host="127.0.0.1", port=22122)
r.set("rra:test:food", "apple")
require "redis"
redis = Redis.new(host: "127.0.0.1", port: 22122)
redis.set("rra:test:food', 'apple")
import com.lambdaworks.redis._
var client = RedisClient.create("redis://127.0.0.1:22122")
var connection = client.connect()
connection.set("rra:test:food", "apple")
Set Configuration Parameters
Note: The cache proxy service write option (related to replication factor and consistency concern) may optionally be set within the nutcracker.conf, resulting in an override of the setting value at the bucket-level in Riak TS.
The following configuration parameters apply to SET
and may be set within the
RRA configuration file /etc/cache_proxy/cache_proxy_22122.yml
:
Parameter | Description | Default |
---|---|---|
n_val |
The number of replicas for objects in a bucket. The n_val should be an integer greater than 0 and less than or equal to the number of nodes in the cluster.NOTE: If you change the n_val after keys have been added to the bucket it may result in failed reads, as the new value may not be replicated to all of the appropriate partitions. |
3 |
pw |
How many vnodes must respond for a write to be deemed successful. | 0 |
w |
How many replicas need to acknowledge the write before responding. | 2 |
sloppy_quorum |
Whether to treat vnodes holding values for another vnode as acceptable within the quorum determination. | 0 (false) |
Sibling Explosion
As noted in the section “Sibling Resolution” above, Riak TS provides for a line of descendency (known as the [causal context][concept causal context]) for a value stored at a key. Clients performing write operations provide this causal context by setting the vector clock (VClock) that they last read.
If a client does not provide the causal context, Riak TS makes no assumptions and treats the write as a new causal context, semantically equivalent to a create. In the case that a value is already stored at the key, this would lead to a sibling.
Since the Redis protocol does not provide a means to pass a VClock, the cache proxy service needs to perform a read-before-write to obtain the current VClock so the write can continue the causal context previously established and avoid “sibling explosion”.
Despite these efforts, in the event of a network partition, siblings will still be created as clients writing to nodes on either side of the network partition can create divergent lines of descendency. Sibling resolution remains the means to merge these lines of descent into a coherent causal context.
Deleting Objects
Deletes via the cache proxy service are analogous to a Redis DEL
, with the added
benefit of writing to Riak TS followed by a PEXPIRE
to Redis, invalidating
cache.
To delete a value at a bucket/key in Riak TS, issue the following:
{ok, RedisClientPid} = eredis:start_link("127.0.0.1", 22122).
{ok, KeysAffected} = eredis:q(RedisClientPid, ["DEL", "rra:test:food"]).
var redis = require("redis"),
client = redis.createClient(22122, "127.0.0.1");
client.del("rra:test:food", redis.print);
import redis
r = redis.StrictRedis(host="127.0.0.1", port=22122)
r.del("rra:test:food")
require "redis"
redis = Redis.new(host: "127.0.0.1", port: 22122)
redis.del("rra:test:food")
import com.lambdaworks.redis._
var client = RedisClient.create("redis://127.0.0.1:22122")
var connection = client.connect()
connection.del("rra:test:food")
Delete Configuration Parameters
The following configuration parameters apply to DEL
and may be set within the
RRA configuration file /etc/cache_proxy/cache_proxy_22122.yml
:
Parameter | Description | Default |
---|---|---|
n_val |
The number of replicas for objects in a bucket. The n_val should be an integer greater than 0 and less than or equal to the number of nodes in the cluster.NOTE: If you change the n_val after keys have been added to the bucket it may result in failed reads, as the new value may not be replicated to all of the appropriate partitions. |
3 |
pw |
How many vnodes must respond for a write to be deemed successful. | 0 |
w |
How many replicas need to acknowledge the write before responding. | 2 |
sloppy_quorum |
Whether to treat vnodes holding values for another vnode as acceptable within the quorum determination. | 0 (false) |