| Class-DBI-Replicated documentation | Contained in the Class-DBI-Replicated distribution. |
Class::DBI::Replicated - Replication from single master to multiple slaves
Version 0.040
package My::DBI; use base qw(Class::DBI::Replicated::mysql); # use base qw(Class::DBI::Replicated::Pg::Slony1); My::DBI->replication(\%arg);
Class::DBI::Replicated does some stuff, blah blah.
replication$class->replication(\%arg);
Analogous to connection. Takes a single hashref.
a single arrayref (as passed to connection)
an arrayref of arrayrefs, one per slave. NOTE: currently, using more than one slave does nothing.
If present, these specify the user and password to use for
replication-specific queries (such as MySQL's SHOW MASTER
STATUS).
replication_dbReturns the name of the current database in use (minus the
leading db_).
db_MainReturn a master or slave DBH, as dictated by the current replication state.
db_SlaveAlways returns a DBH for the most recently-used slave.
db_MasterGenerated by Class::DBI.
db_Slave_ReplMost recently-used slave's connection for replication.
Falls back to db_Slave if no user/password given
db_Master_ReplMaster's connection for replication.
Falls back to db_Master if no user/password given
switch_to_masterswitch_to_slavewait_for_slaveThat is, methods dealing specifically with replication positions.
repl_markGet current master position and save it
repl_posClass data accessor/mutator for current marked master position
repl_get_mastervirtual (scalar)
repl_get_slavevirtual (scalar)
repl_check if ($class->repl_check) { ...
repl_wait unless ($class->repl_wait(\%arg)) {
# not up to date
}
Possible arguments:
defaults to 30
slave name, defaults to the first one
die instead of returning 0
return 0 for failure
repl_comparemy $later = $class->repl_compare($my_pos, $master_pos);
virtual (boolean)
return 1 if $my_pos is at least as new as $master_pos return 0 otherwise
before_createbefore_updatebefore_deleteswitch to using master
after_createafter_updateafter_deletemark master position
selectmk_force_mastersmk_markersreplication_argsExtra Params::Validate specifications for replication.
replication_setupCalled automatically inside replication.
Gets a hashref of the arguments to replication.
Hans Dieter Pearcey, <hdp@cpan.org>
Please report any bugs or feature requests to
bug-class-dbi-replicated@rt.cpan.org, or through the web interface at
http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Class-DBI-Replicated.
I will be notified, and then you'll automatically be notified of progress on
your bug as I make changes.
Copyright 2005 Hans Dieter Pearcey, all rights reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| Class-DBI-Replicated documentation | Contained in the Class-DBI-Replicated distribution. |
package Class::DBI::Replicated; use warnings; use strict; use Class::Trigger; use Sub::Install qw(install_sub); use base qw(Class::Accessor::Class Class::Data::Inheritable); use Params::Validate qw(:all); use Devel::Peek qw(CvGV); use Carp qw(croak);
our $VERSION = '0.040';
# we also track the most recent slave db name sub replication_db { my $class = shift; return $class->__replication_db unless @_; my ($db_name) = @_; if ($db_name =~ /^Slave_/) { $class->__slave_db($db_name); } $class->__replication_db($db_name); } __PACKAGE__->mk_classdata('__replication_std_triggers'); __PACKAGE__->mk_classdata('__force_master'); sub replication { my $class = shift; my $arg = { validate_with( params => [ %{ +shift } ], spec => { master => { type => ARRAYREF }, slaves => { type => ARRAYREF }, user => { type => SCALAR, optional => 1, depends => ['password'], }, password => { type => SCALAR, optional => 1, depends => ['user'], }, $class->replication_args, } ) }; $class->mk_class_accessors( '__slave_names', '__slave_db', '__replication_db', '__repl_user', '__repl_pass', '__replication_setup', 'repl_pos', ); if ($arg->{user}) { $class->__repl_user($arg->{user}); $class->__repl_pass($arg->{password}); } $class->__add_std_triggers; $class->replication_setup($arg); my @slaves = @{$arg->{slaves}}; if (!@slaves or @slaves % 2) { croak "list of slaves must be name => dsn pairs\n"; } $class->Ima::DBI::set_db('Master' => @{$arg->{master}}); if ($arg->{user}) { $class->Ima::DBI::set_db( 'Master_Repl', $arg->{master}->[0], $arg->{user}, $arg->{password}, ); } my @names; while (my ($name, $dsn) = splice @slaves, 0, 2) { push @names, $name; my $slave_arg = ref $dsn eq 'HASH' ? $dsn : { dsn => $dsn }; $class->Ima::DBI::set_db("Slave_$name" => @{ $slave_arg->{dsn} }); if ($arg->{user}) { $class->Ima::DBI::set_db( "Slave_$name\_Repl", $slave_arg->{dsn}->[0], $arg->{user}, $arg->{password}, ); } unless ($class->replication_db) { $class->replication_db("Slave_$name"); } } $class->__slave_names([ @names ]); }
sub db_Main { my ($class) = @_; my $db_name = $class->__force_master ? 'db_Master' : 'db_' . $class->replication_db; $class->call_trigger( 'repl_db', $db_name, ); return $class->$db_name; }
sub db_Slave { my ($class) = @_; my $db_name = 'db_' . $class->__slave_db; return $class->$db_name; }
sub db_Slave_Repl { my ($class) = @_; return $class->db_Slave unless $class->__repl_user; my $db_name = 'db_' . $class->__slave_db . '_Repl'; return $class->$db_name; } # any auto-generated db_Master_Repl will override this sub db_Master_Repl { my ($class) = @_; return $class->db_Master; }
sub switch_to_master { my ($class) = @_; return if $class->replication_db eq 'Master'; $class->replication_db('Master'); $class->call_trigger('switch_to_master'); }
sub __default_slave { my ($class) = @_; return $class->__slave_names->[0]; } sub switch_to_slave { my ($class, $name) = @_; $name ||= $class->__default_slave; $class->replication_db("Slave_$name"); $class->call_trigger('switch_to_slave', $name); }
sub wait_for_slave { my ($class, $name) = @_; my $ok = eval { $class->repl_wait({ slave => $name }); }; my $err = $@; die $err if $err; return $ok; }
sub _mk_unimplemented { my ($class, $meth) = @_; no strict 'refs'; *{$class . "::" . $meth} = sub { croak "$class does not implement $meth" }; } sub repl_mark { my ($class) = @_; $class->call_trigger( 'repl_mark', ); my $pos = $class->repl_get_master; $class->repl_pos($pos); } sub repl_wait { my ($class, $arg) = @_; $arg->{timeout} = 30 unless defined $arg->{timeout}; $arg->{slave} ||= $class->__default_slave; my $tries = 0; my $done = $class->repl_check($arg); while ($tries < $arg->{timeout} and not $done) { sleep 1; $done = $class->repl_check($arg); } if ($tries >= $arg->{timeout} and not $done) { die "$arg->{slave} is not up to date after $arg->{timeout} seconds" if $arg->{fatal}; return 0; } unless ($class->replication_db eq "Slave_$arg->{slave}") { $class->switch_to_slave($arg->{slave}); } return 1; } sub repl_check { my ($class, $arg) = @_; $arg->{slave} ||= $class->__default_slave; return 1 unless defined $class->repl_pos; $class->call_trigger( 'repl_check', ); my $get = sub { $class->repl_get_slave }; my $slave_pos = do { if ($arg->{slave} eq $class->__slave_db) { $get->(); } else { my $old = $class->__slave_db; $class->__slave_db("Slave_" . $arg->{slave}); my $return = $get->(); $class->__slave_db($old); $return; } }; if ($slave_pos && $class->repl_compare($slave_pos, $class->repl_pos)) { $class->repl_pos(undef); return 1; } return 0; } for (qw( repl_get_master repl_get_slave repl_compare )) { __PACKAGE__->_mk_unimplemented($_); }
sub _mark { shift->repl_mark; } sub _check { shift->repl_wait({ timeout => 0, }); } # this exists only because you can't take a reference to a # method and still let inheritance have a chance, unlike the # two subs above which actually add functionality sub __master { shift->switch_to_master; } sub __add_std_triggers { my ($class) = @_; return if $class->__replication_std_triggers; $class->__real_add_std_triggers; $class->__replication_std_triggers(1); } sub __real_add_std_triggers { my ($class) = @_; $class->add_trigger( before_create => \&__master, before_update => \&__master, before_delete => \&__master, after_create => \&_mark, after_update => \&_mark, after_delete => \&_mark, select => \&_check, ); }
sub mk_force_masters { my $class = shift; for my $meth (@_) { # XXX this is very disrespectful. my $oldcode = $class->can($meth); install_sub({ code => sub { my $class = shift; my $old = $class->__force_master; $class->__force_master(1); my $r = $class->$oldcode(@_); $class->__force_master($old); return $r; }, into => $class, as => $meth, }); } } # XXX fix duplication here
sub mk_markers { my $class = shift; for my $meth (@_) { # XXX this is very disrespectful. my $oldcode = $class->can($meth); install_sub({ code => sub { my $class = shift; $class->switch_to_master; my $r = $class->$oldcode(@_); $class->repl_mark; $class->repl_wait({ timeout => 0 }); return $r; }, into => $class, as => $meth, }); } }
sub replication_args { () }
sub replication_setup { () }
1; # End of Class::DBI::Replicated