add support for replication

This commit is contained in:
Justin Balthrop 2009-11-23 23:29:26 -08:00
parent e0a617bde7
commit bef69b90eb
5 changed files with 288 additions and 25 deletions

213
ext/bdb.c
View file

@ -1637,6 +1637,8 @@ VALUE env_open(VALUE obj, VALUE vhome, VALUE vflags, VALUE vmode)
raise_error(0,"env handle already used and closed"); raise_error(0,"env handle already used and closed");
rv = eh->env->open(eh->env,StringValueCStr(vhome),flags,mode); rv = eh->env->open(eh->env,StringValueCStr(vhome),flags,mode);
eh->env->app_private=eh;
if (rv != 0) { if (rv != 0) {
raise_error(rv, "env_open failure: %s",db_strerror(rv)); raise_error(rv, "env_open failure: %s",db_strerror(rv));
} }
@ -2749,6 +2751,205 @@ VALUE env_get_home(VALUE obj)
return rb_str_new2(home); return rb_str_new2(home);
} }
/*
* call-seq:
* env.set_verbose(which, onoff)
*
* set verbose messages on or off
*/
VALUE env_set_verbose(VALUE obj, VALUE which, VALUE onoff)
{
t_envh *eh;
int rv;
Data_Get_Struct(obj,t_envh,eh);
rv = eh->env->set_verbose(eh->env, NUM2UINT(which), RTEST(onoff));
if ( rv != 0 ) raise_error(rv, "env_set_verbose: %s",db_strerror(rv));
return Qtrue;
}
/*
* call-seq:
* env.rep_priority = int
*
* specify how the replication manager will handle acknowledgement of replication messages
*/
VALUE env_rep_set_priority(VALUE obj, VALUE priority)
{
t_envh *eh;
int rv;
Data_Get_Struct(obj,t_envh,eh);
rv = eh->env->rep_set_priority(eh->env, NUM2UINT(priority));
if ( rv != 0 ) raise_error(rv, "env_rep_set_priority: %s", db_strerror(rv));
return priority;
}
/*
* call-seq:
* env.rep_priority -> int
*
* returns the replication manager's acknowledgement policy
*/
VALUE env_rep_get_priority(VALUE obj)
{
t_envh *eh;
u_int32_t priority;
Data_Get_Struct(obj,t_envh,eh);
eh->env->rep_get_priority(eh->env, &priority);
return INT2NUM(priority);
}
/*
* call-seq:
* env.rep_nsites = int
*
* specify how the replication manager will handle acknowledgement of replication messages
*/
VALUE env_rep_set_nsites(VALUE obj, VALUE nsites)
{
t_envh *eh;
int rv;
Data_Get_Struct(obj,t_envh,eh);
rv = eh->env->rep_set_nsites(eh->env, NUM2UINT(nsites));
if ( rv != 0 ) raise_error(rv, "env_rep_set_nsites: %s", db_strerror(rv));
return nsites;
}
/*
* call-seq:
* env.rep_nsites -> int
*
* returns the replication manager's acknowledgement policy
*/
VALUE env_rep_get_nsites(VALUE obj)
{
t_envh *eh;
u_int32_t nsites;
Data_Get_Struct(obj,t_envh,eh);
eh->env->rep_get_nsites(eh->env, &nsites);
return INT2NUM(nsites);
}
/*
* call-seq:
* env.repmgr_set_local_site(host, port)
*
* specify the local site for the replication manager
*/
VALUE env_repmgr_set_local_site(VALUE obj, VALUE host, VALUE port)
{
t_envh *eh;
int rv;
Data_Get_Struct(obj,t_envh,eh);
rv = eh->env->repmgr_set_local_site(eh->env, StringValuePtr(host), NUM2UINT(port), 0);
if ( rv != 0 ) raise_error(rv, "env_repmgr_set_local_site: %s", db_strerror(rv));
return Qtrue;
}
/*
* call-seq:
* env.repmgr_add_remote_site(host, port)
*
* add a remote for the replication manager
*/
VALUE env_repmgr_add_remote_site(VALUE obj, VALUE host, VALUE port)
{
t_envh *eh;
int rv;
int eidp;
Data_Get_Struct(obj,t_envh,eh);
rv = eh->env->repmgr_add_remote_site(eh->env, StringValuePtr(host), NUM2UINT(port), &eidp, 0);
if ( rv != 0 ) raise_error(rv, "env_repmgr_add_remote_site: %s", db_strerror(rv));
return INT2NUM(eidp);
}
/*
* call-seq:
* env.repmgr_ack_policy = int
*
* specify how the replication manager will handle acknowledgement of replication messages
*/
VALUE env_repmgr_set_ack_policy(VALUE obj, VALUE policy)
{
t_envh *eh;
int rv;
Data_Get_Struct(obj,t_envh,eh);
rv = eh->env->repmgr_set_ack_policy(eh->env, NUM2INT(policy));
if ( rv != 0 ) raise_error(rv, "env_repmgr_set_ack_policy: %s", db_strerror(rv));
return policy;
}
/*
* call-seq:
* env.repmgr_ack_policy -> int
*
* returns the replication manager's acknowledgement policy
*/
VALUE env_repmgr_get_ack_policy(VALUE obj)
{
t_envh *eh;
int policy;
Data_Get_Struct(obj,t_envh,eh);
eh->env->repmgr_get_ack_policy(eh->env, &policy);
return INT2NUM(policy);
}
/*
* call-seq:
* env.repmgr_start(num_threads, flags)
*
* start the replication manager
*/
VALUE env_repmgr_start(VALUE obj, VALUE num_threads, VALUE flags)
{
t_envh *eh;
int rv;
Data_Get_Struct(obj,t_envh,eh);
rv = eh->env->repmgr_start(eh->env, NUM2INT(num_threads), NUM2UINT(flags));
if ( rv != 0 ) raise_error(rv, "env_repmgr_start: %s", db_strerror(rv));
return Qtrue;
}
/*
* call-seq:
* env.repmgr_stat_print
*
* prints replication manager stats
*/
VALUE env_repmgr_stat_print(VALUE obj, VALUE flags)
{
t_envh *eh;
int rv;
Data_Get_Struct(obj,t_envh,eh);
rv = eh->env->repmgr_stat_print(eh->env, NUM2UINT(flags));
if ( rv != 0 ) raise_error(rv, "env_repmgr_stat_print: %s", db_strerror(rv));
return Qtrue;
}
static void txn_finish(t_txnh *txn) static void txn_finish(t_txnh *txn)
{ {
if ( RTEST(ruby_debug) ) if ( RTEST(ruby_debug) )
@ -3008,6 +3209,18 @@ void Init_bdb() {
rb_define_method(cEnv,"set_tmp_dir",env_set_tmp_dir,1); rb_define_method(cEnv,"set_tmp_dir",env_set_tmp_dir,1);
rb_define_method(cEnv,"get_tmp_dir",env_get_tmp_dir,0); rb_define_method(cEnv,"get_tmp_dir",env_get_tmp_dir,0);
rb_define_method(cEnv,"get_home",env_get_home,0); rb_define_method(cEnv,"get_home",env_get_home,0);
rb_define_method(cEnv,"set_verbose",env_set_verbose,2);
rb_define_method(cEnv,"rep_priority=", env_rep_set_priority, 1);
rb_define_method(cEnv,"rep_priority", env_rep_get_priority, 0);
rb_define_method(cEnv,"rep_nsites=", env_rep_set_nsites, 1);
rb_define_method(cEnv,"rep_nsites", env_rep_get_nsites, 0);
rb_define_method(cEnv,"repmgr_set_local_site", env_repmgr_set_local_site, 2);
rb_define_method(cEnv,"repmgr_add_remote_site", env_repmgr_add_remote_site, 2);
rb_define_method(cEnv,"repmgr_ack_policy=", env_repmgr_set_ack_policy, 1);
rb_define_method(cEnv,"repmgr_ack_policy", env_repmgr_get_ack_policy, 0);
rb_define_method(cEnv,"repmgr_start", env_repmgr_start, 2);
rb_define_method(cEnv,"repmgr_stat_print", env_repmgr_stat_print, 1);
cTxnStat = rb_define_class_under(mBdb,"TxnStat",rb_cObject); cTxnStat = rb_define_class_under(mBdb,"TxnStat",rb_cObject);
rb_define_method(cTxnStat,"[]",stat_aref,1); rb_define_method(cTxnStat,"[]",stat_aref,1);

View file

@ -19,8 +19,12 @@ class Bdb::Base
indexes[field] = opts indexes[field] = opts
end end
def path
config[:path] || Dir.pwd
end
def environment def environment
@environment ||= Bdb::Environment.new(config[:path], self) @environment ||= Bdb::Environment.new(path, self)
end end
def transaction(nested = true, &block) def transaction(nested = true, &block)
@ -32,7 +36,11 @@ class Bdb::Base
end end
def checkpoint(opts = {}) def checkpoint(opts = {})
environment.synchronize(opts) environment.checkpoint(opts)
end
def master?
environment.master?
end end
private private

View file

@ -10,10 +10,11 @@ class Bdb::Database < Bdb::Base
def db(index = nil) def db(index = nil)
if @db.nil? if @db.nil?
@db = {} @db = {}
open_flags = master? ? Bdb::DB_CREATE : Bdb::DB_RDONLY
transaction(false) do transaction(false) do
primary_db = environment.env.db primary_db = environment.env.db
primary_db.pagesize = config[:page_size] if config[:page_size] primary_db.pagesize = config[:page_size] if config[:page_size]
primary_db.open(transaction, name, nil, Bdb::Db::BTREE, Bdb::DB_CREATE, 0) primary_db.open(transaction, name, nil, Bdb::Db::BTREE, open_flags, 0)
@db[:primary_key] = primary_db @db[:primary_key] = primary_db
indexes.each do |field, opts| indexes.each do |field, opts|
@ -31,13 +32,22 @@ class Bdb::Database < Bdb::Base
index_db = environment.env.db index_db = environment.env.db
index_db.flags = Bdb::DB_DUPSORT unless opts[:unique] index_db.flags = Bdb::DB_DUPSORT unless opts[:unique]
index_db.pagesize = config[:page_size] if config[:page_size] index_db.pagesize = config[:page_size] if config[:page_size]
index_db.open(transaction, "#{name}_by_#{field}", nil, Bdb::Db::BTREE, Bdb::DB_CREATE, 0) index_db.open(transaction, "#{name}_by_#{field}", nil, Bdb::Db::BTREE, open_flags, 0)
primary_db.associate(transaction, index_db, Bdb::DB_CREATE, index_callback) primary_db.associate(transaction, index_db, open_flags, index_callback)
@db[field] = index_db @db[field] = index_db
end end
end end
end end
@db[index || :primary_key] @db[index || :primary_key]
rescue Bdb::DbError => e
# Retry if the database doesn't exist and we are a replication client.
if not master? and e.code == Errno::ENOENT::Errno
close
sleep 1
retry
else
raise(e)
end
end end
def close def close
@ -131,12 +141,17 @@ class Bdb::Database < Bdb::Base
set.results set.results
end end
def [](key)
get(key).first
end
def set(key, value, opts = {}) def set(key, value, opts = {})
synchronize do synchronize do
key = Tuple.dump(key) key = Tuple.dump(key)
value = Marshal.dump(value) value = Marshal.dump(value)
flags = opts[:create] ? Bdb::DB_NOOVERWRITE : 0 flags = opts[:create] ? Bdb::DB_NOOVERWRITE : 0
db.put(transaction, key, value, flags) db.put(transaction, key, value, flags)
value
end end
end end
@ -154,6 +169,10 @@ class Bdb::Database < Bdb::Base
end end
end end
def sync
db.sync
end
private private
def get_key(key, opts) def get_key(key, opts)

View file

@ -1,12 +1,25 @@
require 'thread'
require 'bdb/replication'
class Bdb::Environment class Bdb::Environment
@@env = {} @@env = {}
def self.new(path, database = nil) def self.new(path, database = nil)
# Only allow one environment per path.
path = File.expand_path(path) path = File.expand_path(path)
@@env[path] ||= super(path) @@env[path] ||= super(path)
@@env[path].databases << database if database @@env[path].databases << database if database
@@env[path] @@env[path]
end end
def initialize(path)
@path = path
end
attr_reader :path
def self.[](path)
new(path)
end
def self.config(config = {}) def self.config(config = {})
@config ||= { @config ||= {
:max_locks => 5000, :max_locks => 5000,
@ -22,10 +35,10 @@ class Bdb::Environment
@config.merge!(config) @config.merge!(config)
end end
def initialize(path) include Replication
@path = path def self.replicate(path, opts)
self[path].replicate(opts)
end end
attr_reader :path
def databases def databases
@databases ||= [] @databases ||= []
@ -40,16 +53,19 @@ class Bdb::Environment
else else
env_flags = Bdb::DB_CREATE | Bdb::DB_INIT_TXN | Bdb::DB_INIT_LOCK | env_flags = Bdb::DB_CREATE | Bdb::DB_INIT_TXN | Bdb::DB_INIT_LOCK |
Bdb::DB_REGISTER | Bdb::DB_RECOVER | Bdb::DB_INIT_MPOOL | Bdb::DB_THREAD Bdb::DB_REGISTER | Bdb::DB_RECOVER | Bdb::DB_INIT_MPOOL | Bdb::DB_THREAD
end
env_flags |= Bdb::DB_INIT_REP if replicate?
end
@env.cachesize = config[:cache_size] if config[:cache_size] @env.cachesize = config[:cache_size] if config[:cache_size]
@env.set_timeout(config[:txn_timeout], Bdb::DB_SET_TXN_TIMEOUT) if config[:txn_timeout] @env.set_timeout(config[:txn_timeout], Bdb::DB_SET_TXN_TIMEOUT) if config[:txn_timeout]
@env.set_timeout(config[:lock_timeout], Bdb::DB_SET_LOCK_TIMEOUT) if config[:lock_timeout] @env.set_timeout(config[:lock_timeout], Bdb::DB_SET_LOCK_TIMEOUT) if config[:lock_timeout]
@env.set_lk_max_locks(config[:max_locks]) if config[:max_locks] @env.set_lk_max_locks(config[:max_locks]) if config[:max_locks]
@env.set_lk_detect(Bdb::DB_LOCK_RANDOM) @env.set_lk_detect(Bdb::DB_LOCK_RANDOM)
@env.flags_on = Bdb::DB_TXN_WRITE_NOSYNC | Bdb::DB_TIME_NOTGRANTED @env.flags_on = Bdb::DB_TXN_WRITE_NOSYNC | Bdb::DB_TIME_NOTGRANTED
@env.open(path, env_flags, 0) init_replication(@env) if replicate?
@env.open(path, env_flags, 0)
start_replication(@env) if replicate?
@exit_handler ||= at_exit { close } @exit_handler ||= at_exit { close }
end end
end end

View file

@ -1,6 +1,13 @@
require 'test/unit' require 'test/unit'
require 'fileutils' require 'fileutils'
require File.dirname(__FILE__) + '/../ext/bdb' require 'pp'
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../ext')
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../tuple/ext')
require 'bdb'
require 'bdb/database'
class Test::Unit::TestCase class Test::Unit::TestCase
include FileUtils include FileUtils