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
database, all read-type queries (SELECTs) go to the slave database.
Basically, any method request that L<DBIx::Class::Storage::DBI> would normally
-handle gets delegated to one of the two attributes: L</master_storage> or to
-L</current_replicant_storage>. Additionally, some methods need to be distributed
+handle gets delegated to one of the two attributes: L</read_handler> or to
+L</write_handler>. Additionally, some methods need to be distributed
to all existing storages. This way our storage class is a drop in replacement
for L<DBIx::Class::Storage::DBI>.
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
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<DBIx::Class::Storage::Replicated::Pool> for available arguments.
+
+=cut
+
+has 'pool_args' => (
+ is=>'ro',
+ isa=>'HashRef',
+ lazy=>1,
+ required=>1,
+ default=>sub { {} },
+);
+
=head2 balancer_type
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<DBIx::Class::Storage::Replicated::Balancer> for available arguments.
+
+=cut
+
+has 'balancer_args' => (
+ is=>'ro',
+ isa=>'HashRef',
+ lazy=>1,
+ required=>1,
+ default=>sub { {} },
+);
=head2 pool
connect_replicants
replicants
has_replicants
- num_replicants
- delete_replicant
/],
);
-
=head2 balancer
Is a <DBIx::Class::Storage::DBI::Replicated::Balancer> or derived class. This
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
lazy_build=>1,
);
-
=head1 ATTRIBUTES IMPLEMENTING THE DBIx::Storage::DBI INTERFACE
The following methods are delegated all the methods required for the
/],
);
-
=head2 write_handler
Defines an object that implements the write side of L<BIx::Class::Storage::DBI>.
update
delete
dbh
- txn_do
txn_commit
txn_rollback
sth
/],
);
-
=head1 METHODS
This class defines the following methods.
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,
DBIx::Class::Storage::DBI->new;
}
+=head2 _build_pool_type
+
+Lazy builder for the L</pool_type> attribute.
+
+=cut
+
+sub _build_pool_type {
+ return 'DBIx::Class::Storage::DBI::Replicated::Pool';
+}
+
=head2 _build_pool
Lazy builder for the L</pool> 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</balancer_type> attribute.
+
+=cut
+
+sub _build_balancer_type {
+ return 'DBIx::Class::Storage::DBI::Replicated::Balancer::First';
}
=head2 _build_balancer
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
);
}
+=head2 execute_reliably ($coderef, ?@args)
+
+Given a coderef, saves the current state of the L</read_handler>, 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});
+ };
+
+ $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
+ eval {
+ $coderef->(@args);
+ };
+
+ if($@) {
+ $self->throw_exception("coderef returned an error: $@");
+ }
+
+ ##Reset to the original state
+ $self->schema->storage->read_handler($current);
+}
+
=head2 set_reliable_storage
Sets the current $schema to be 'reliable', that is all queries, both read and
$schema->storage->read_handler($write_handler);
}
+=head2 txn_do ($coderef)
+
+Overload to the txn_do method, which is delegated to whatever the
+L<write_handler> is set to. We overload this in order to wrap in inside a
+L</execute_reliably> method.
+
+=cut
+
+sub txn_do {
+ my($self, $coderef, @args) = @_;
+ $self->execute_reliably($coderef, @args);
+}
+
=head2 connected
Check that the master and at least one of the replicants is connected.
=head1 AUTHOR
-Norbert Csongrádi <bert@cpan.org>
+ John Napiorkowski <john.napiorkowski@takkle.com>
-Peter Siklósi <einon@einon.hu>
+Based on code originated by:
-John Napiorkowski <john.napiorkowski@takkle.com>
+ Norbert Csongrádi <bert@cpan.org>
+ Peter Siklósi <einon@einon.hu>
=head1 LICENSE