X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI%2FReplicated.pm;h=ad3fff16e7bd416b09a7fb745f28f8d2da73f1db;hb=243a6b72cfb2a6f2ccfcd84b665f1880790e2ff9;hp=6f81945e975846cd0dc725181cdb0337f42a6ac2;hpb=cb6ec758e3c4607ec8e30dd943a500a1d70d8940;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Storage/DBI/Replicated.pm b/lib/DBIx/Class/Storage/DBI/Replicated.pm index 6f81945..ad3fff1 100644 --- a/lib/DBIx/Class/Storage/DBI/Replicated.pm +++ b/lib/DBIx/Class/Storage/DBI/Replicated.pm @@ -19,7 +19,7 @@ storage type, add some replicated (readonly) databases, and perform reporting tasks. ## Change storage_type in your schema class - $schema->storage_type( '::DBI::Replicated' ); + $schema->storage_type( ['::DBI::Replicated', {balancer=>'::Random'}] ); ## Add some slaves. Basically this is an array of arrayrefs, where each ## arrayref is database connect information @@ -43,15 +43,21 @@ one master and numerous slave database connections. All write-type queries database, all read-type queries (SELECTs) go to the slave database. Basically, any method request that L would normally -handle gets delegated to one of the two attributes: L or to -L. Additionally, some methods need to be distributed +handle gets delegated to one of the two attributes: L or to +L. Additionally, some methods need to be distributed to all existing storages. This way our storage class is a drop in replacement for L. Read traffic is spread across the replicants (slaves) occuring to a user selected algorithm. The default algorithm is random weighted. -TODO more details about the algorithm. +=head1 NOTES + +The consistancy betweeen master and replicants is database specific. The Pool +gives you a method to validate it's replicants, removing and replacing them +when they fail/pass predefined criteria. It is recommened that your application +define two schemas, one using the replicated storage and another that just +connects to the master. =head1 ATTRIBUTES @@ -67,14 +73,27 @@ to: L. has 'pool_type' => ( is=>'ro', isa=>'ClassName', - required=>1, - lazy=>1, - default=>'DBIx::Class::Storage::DBI::Replicated::Pool', + lazy_build=>1, handles=>{ 'create_pool' => 'new', }, ); +=head2 pool_args + +Contains a hashref of initialized information to pass to the Balancer object. +See L for available arguments. + +=cut + +has 'pool_args' => ( + is=>'ro', + isa=>'HashRef', + lazy=>1, + required=>1, + default=>sub { {} }, +); + =head2 balancer_type @@ -86,14 +105,26 @@ choose how to spread the query load across each replicant in the pool. has 'balancer_type' => ( is=>'ro', isa=>'ClassName', - required=>1, - lazy=>1, - default=>'DBIx::Class::Storage::DBI::Replicated::Balancer', + lazy_build=>1, handles=>{ 'create_balancer' => 'new', }, ); +=head2 balancer_args + +Contains a hashref of initialized information to pass to the Balancer object. +See L for available arguments. + +=cut + +has 'balancer_args' => ( + is=>'ro', + isa=>'HashRef', + lazy=>1, + required=>1, + default=>sub { {} }, +); =head2 pool @@ -110,12 +141,9 @@ has 'pool' => ( connect_replicants replicants has_replicants - num_replicants - delete_replicant /], ); - =head2 balancer Is a or derived class. This @@ -127,9 +155,9 @@ has 'balancer' => ( is=>'ro', isa=>'DBIx::Class::Storage::DBI::Replicated::Balancer', lazy_build=>1, + handles=>[qw/auto_validate_every/], ); - =head2 master The master defines the canonical state for a pool of connected databases. All @@ -146,7 +174,6 @@ has 'master' => ( lazy_build=>1, ); - =head1 ATTRIBUTES IMPLEMENTING THE DBIx::Storage::DBI INTERFACE The following methods are delegated all the methods required for the @@ -169,7 +196,6 @@ has 'read_handler' => ( /], ); - =head2 write_handler Defines an object that implements the write side of L. @@ -198,16 +224,15 @@ has 'write_handler' => ( update delete dbh - txn_do txn_commit txn_rollback sth deploy schema + reload_row /], ); - =head1 METHODS This class defines the following methods. @@ -227,7 +252,14 @@ sub new { my $schema = shift @_; my $storage_type_args = shift @_; my $obj = $class->SUPER::new($schema, $storage_type_args, @_); - + + ## Hate to do it this way, but can't seem to get advice on the attribute working right + ## maybe we can do a type and coercion for it. + if( $storage_type_args->{balancer_type} && $storage_type_args->{balancer_type}=~m/^::/) { + $storage_type_args->{balancer_type} = 'DBIx::Class::Storage::DBI::Replicated::Balancer'.$storage_type_args->{balancer_type}; + eval "require $storage_type_args->{balancer_type}"; + } + return $class->meta->new_object( __INSTANCE__ => $obj, %$storage_type_args, @@ -245,6 +277,16 @@ sub _build_master { DBIx::Class::Storage::DBI->new; } +=head2 _build_pool_type + +Lazy builder for the L attribute. + +=cut + +sub _build_pool_type { + return 'DBIx::Class::Storage::DBI::Replicated::Pool'; +} + =head2 _build_pool Lazy builder for the L attribute. @@ -252,7 +294,18 @@ Lazy builder for the L attribute. =cut sub _build_pool { - shift->create_pool; + my $self = shift @_; + $self->create_pool(%{$self->pool_args}); +} + +=head2 _build_balancer_type + +Lazy builder for the L attribute. + +=cut + +sub _build_balancer_type { + return 'DBIx::Class::Storage::DBI::Replicated::Balancer::First'; } =head2 _build_balancer @@ -264,7 +317,10 @@ the balancer knows which pool it's balancing. sub _build_balancer { my $self = shift @_; - $self->create_balancer(pool=>$self->pool); + $self->create_balancer( + pool=>$self->pool, + master=>$self->master, + %{$self->balancer_args},); } =head2 _build_write_handler @@ -318,6 +374,72 @@ sub all_storages { ); } +=head2 execute_reliably ($coderef, ?@args) + +Given a coderef, saves the current state of the L, forces it to +use reliable storage (ie sets it to the master), executes a coderef and then +restores the original state. + +Example: + + my $reliably = sub { + my $name = shift @_; + $schema->resultset('User')->create({name=>$name}); + my $user_rs = $schema->resultset('User')->find({name=>$name}); + return $user_rs; + }; + + my $user_rs = $schema->storage->execute_reliably($reliably, 'John'); + +Use this when you must be certain of your database state, such as when you just +inserted something and need to get a resultset including it, etc. + +=cut + +sub execute_reliably { + my ($self, $coderef, @args) = @_; + + unless( ref $coderef eq 'CODE') { + $self->throw_exception('Second argument must be a coderef'); + } + + ##Get copy of master storage + my $master = $self->master; + + ##Get whatever the current read hander is + my $current = $self->read_handler; + + ##Set the read handler to master + $self->read_handler($master); + + ## do whatever the caller needs + my @result; + my $want_array = wantarray; + + eval { + if($want_array) { + @result = $coderef->(@args); + } + elsif(defined $want_array) { + ($result[0]) = ($coderef->(@args)); + } else { + $coderef->(@args); + } + }; + + ##Reset to the original state + $self->schema->storage->read_handler($current); + + ##Exception testing has to come last, otherwise you might leave the + ##read_handler set to master. + + if($@) { + $self->throw_exception("coderef returned an error: $@"); + } else { + return $want_array ? @result : $result[0]; + } +} + =head2 set_reliable_storage Sets the current $schema to be 'reliable', that is all queries, both read and @@ -348,6 +470,33 @@ sub set_balanced_storage { $schema->storage->read_handler($write_handler); } +=head2 txn_do ($coderef) + +Overload to the txn_do method, which is delegated to whatever the +L is set to. We overload this in order to wrap in inside a +L method. + +=cut + +sub txn_do { + my($self, $coderef, @args) = @_; + $self->execute_reliably($coderef, @args); +} + +=head2 reload_row ($row) + +Overload to the reload_row method so that the reloading is always directed to +the master storage. + +=cut + +around 'reload_row' => sub { + my ($reload_row, $self, $row) = @_; + return $self->execute_reliably(sub { + return $self->$reload_row(shift); + }, $row); +}; + =head2 connected Check that the master and at least one of the replicants is connected. @@ -494,11 +643,12 @@ sub disconnect { =head1 AUTHOR -Norbert Csongrádi + John Napiorkowski -Peter Siklósi +Based on code originated by: -John Napiorkowski + Norbert Csongrádi + Peter Siklósi =head1 LICENSE