NAME
ShardedKV - An interface to sharded key-value stores
VERSION
version 0.09
SYNOPSIS
use ShardedKV;
use ShardedKV::Continuum::Ketama;
use ShardedKV::Storage::Redis;
my $continuum_spec = [
["shard1", 100], # shard name, weight
["shard2", 150],
];
my $continuum = ShardedKV::Continuum::Ketama->new(from => $continuum_spec);
# Redis storage chosen here, but can also be "Memory" or "MySQL".
# "Memory" is for testing. Mixing storages likely has weird side effects.
my %storages = (
shard1 => ShardedKV::Storage::Redis->new(
redis_master_str => 'redisserver:6379',
redis_slave_strs => ['redisbackup:6379', 'redisbackup2:6379'],
),
shard2 => ShardedKV::Storage::Redis->new(
redis_master_str => 'redisserver:6380',
redis_slave_strs => ['redisbackup:6380', 'redisbackup2:6380'],
),
);
my $skv = ShardedKV->new(
storages => \%storages,
continuum => $continuum,
);
my $value = $skv->get($key);
$skv->set($key, $value);
$skv->delete($key);
DESCRIPTION
This is experimental software. Interfaces and implementation are subject to change. If you are interested in using this in production, please get in touch to gauge the current state of stability.
This module implements an abstract interface to a sharded key-value store. The storage backends as well as the "continuum" are pluggable. "Continuum" is to mean "the logic that decides in which shard a particular key lives". Typically, people use consistent hashing for this purpose and very commonly the choice is to use ketama specifically. See below for references.
Beside the abstract querying interface, this module also implements logic to add one or more servers to the continuum and use passive key migration to extend capacity without downtime. Do make it a point to understand the logic before using it. More on that below.
PUBLIC ATTRIBUTES
continuum
The continuum object decides on which shard a given key lives. This is required for a ShardedKV
object and must be an object that implements the ShardedKV::Continuum
role.
migration_continuum
This is a second continuum object that has additional shards configured. If this is set, a passive key migration is in effect. See begin_migration
below!
storages
A hashref of storage objects, each of which represents one shard. Keys in the hash must be the same labels/shard names that are used in the continuum. Each storage object must implement the ShardedKV::Storage
role.
PUBLIC METHODS
get
Given a key, fetches the value for that key from the correct shard and returns that value or undef on failure.
Different storage backends may return a reference to the value instead. For example, the Redis and Memory backends return scalar references, whereas the mysql backend returns an array reference. This might still change, likely, all backends may be required to return scalar references in the future.
set
Given a key and a value, saves the value into the key within the correct shard.
The value needs to be a reference of the same type that would be returned by the storage backend when calling get()
. See the discussion above.
delete
Given a key, deletes the key's entry from the correct shard.
In a migration situation, this might attempt to delete the key from multiple shards, see below.
begin_migration
Given a ShardedKV::Continuum
object, this sets the migration_continuum
property of the ShardedKV
, thus beginning a passive key migration. Right now, the only kind of migration that is supported is adding shards! Only one migration may be in effect at a time. The passive qualification there is very significant. If you are, for example, using the Redis storage backend with a key expiration of one hour, then you know, that after letting the passive migration run for one hour, all keys that are still relevant will have been migrated (or expired if they were not relevant).
Full migration example:
use ShardedKV;
use ShardedKV::Continuum::Ketama;
use ShardedKV::Storage::Redis;
my $continuum_spec = [
["shard1", 100], # shard name, weight
["shard2", 150],
];
my $continuum = ShardedKV::Continuum::Ketama->new(from => $continuum_spec);
# Redis storage chosen here, but can also be "Memory" or "MySQL".
# "Memory" is for testing. Mixing storages likely has weird side effects.
my %storages = (
shard1 => ShardedKV::Storage::Redis->new(
redis_master_str => 'redisserver:6379',
expiration_time => 60*60,
),
shard2 => ShardedKV::Storage::Redis->new(
redis_master_str => 'redisserver:6380',
expiration_time => 60*60,
),
);
my $skv = ShardedKV->new(
storages => \%storages,
continuum => $continuum,
);
# ... use the skv ...
# Oh, we need to extend it!
# Add storages:
$skv->storages->{shard3} = ShardedKV::Storage::Redis->new(
redis_master_str => 'NEWredisserver:6379',
expiration_time => 60*60,
);
# ... could add more at the same time...
my $old_continuum = $skv->continuum;
my $extended_continuum = $old_continuum->clone;
$extended_continuum->extend([shard3 => 120]);
$skv->begin_migration($extended_continuum);
# ... use the skv normally...
# ... after one hour (60*60 seconds), we can stop the migration:
$skv->end_migration();
The logic for the migration is fairly simple:
If there is a migration continuum, then for get requests, that continuum is used to find the right shard for the given key. If that shard does not have the key, we check the original continuum and if that points the key at a different shard, we query that.
For delete requests, we also attempt to delete from the shard pointed to by the migration continuum AND the shard pointed to by the main continuum.
For set requests, we always only use the shard deduced from the migration continuum
end_migration()
will promote the migration continuum to the regular continuum and set the migration_continuum
property to undef.
end_migration
See the begin_migration
docs above.
SEE ALSO
ACKNLOWLEDGMENT
This module was originally developed for booking.com. With approval from booking.com, this module was generalized and put on CPAN, for which the authors would like to express their gratitude.
AUTHORS
Steffen Mueller <smueller@cpan.org>
Nick Perez <nperez@cpan.org>
COPYRIGHT AND LICENSE
This software is copyright (c) 2012 by Steffen Mueller.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.