use Moose::Role;
requires 'next_storage';
+use MooseX::Types::Moose qw/Int/;
+use DBIx::Class::Storage::DBI::Replicated::Pool;
+use DBIx::Class::Storage::DBI::Replicated::Types qw/DBICStorageDBI/;
+use namespace::clean -except => 'meta';
=head1 NAME
-DBIx::Class::Storage::DBI::Replicated::Balancer; A Software Load Balancer
+DBIx::Class::Storage::DBI::Replicated::Balancer - A Software Load Balancer
=head1 SYNOPSIS
This role is used internally by L<DBIx::Class::Storage::DBI::Replicated>.
-
+
=head1 DESCRIPTION
Given a pool (L<DBIx::Class::Storage::DBI::Replicated::Pool>) of replicated
has 'auto_validate_every' => (
is=>'rw',
- isa=>'Int',
+ isa=>Int,
predicate=>'has_auto_validate_every',
);
has 'master' => (
is=>'ro',
- isa=>'DBIx::Class::Storage::DBI',
+ isa=>DBICStorageDBI,
required=>1,
);
This attribute returns the next slave to handle a read request. Your L</pool>
attribute has methods to help you shuffle through all the available replicants
-via it's balancer object.
+via its balancer object.
=cut
has 'current_replicant' => (
is=> 'rw',
- isa=>'DBIx::Class::Storage::DBI',
+ isa=>DBICStorageDBI,
lazy_build=>1,
handles=>[qw/
select
around 'next_storage' => sub {
my ($next_storage, $self, @args) = @_;
my $now = time;
-
+
## Do we need to validate the replicants?
if(
$self->has_auto_validate_every &&
($self->auto_validate_every + $self->pool->last_validated) <= $now
- ) {
+ ) {
$self->pool->validate_replicants;
}
-
+
## Get a replicant, or the master if none
- my $next = $self->$next_storage(@args);
- return $next ? $next:$self->master;
+ if(my $next = $self->$next_storage(@args)) {
+ return $next;
+ } else {
+ $self->master->debugobj->print("No Replicants validate, falling back to master reads. ");
+ return $self->master;
+ }
};
-=head2 before: select
+=head2 increment_storage
-Advice on the select attribute. Each time we use a replicant
-we need to change it via the storage pool algorithm. That way we are spreading
-the load evenly (hopefully) across existing capacity.
+Rolls the Storage to whatever is next in the queue, as defined by the Balancer.
=cut
-before 'select' => sub {
+sub increment_storage {
my $self = shift @_;
my $next_replicant = $self->next_storage;
$self->current_replicant($next_replicant);
+}
+
+=head2 around: select
+
+Advice on the select attribute. Each time we use a replicant
+we need to change it via the storage pool algorithm. That way we are spreading
+the load evenly (hopefully) across existing capacity.
+
+=cut
+
+around 'select' => sub {
+ my ($select, $self, @args) = @_;
+
+ if (my $forced_pool = $args[-1]->{force_pool}) {
+ delete $args[-1]->{force_pool};
+ return $self->_get_forced_pool($forced_pool)->select(@args);
+ } elsif($self->master->{transaction_depth}) {
+ return $self->master->select(@args);
+ } else {
+ $self->increment_storage;
+ return $self->$select(@args);
+ }
};
-=head2 before: select_single
+=head2 around: select_single
Advice on the select_single attribute. Each time we use a replicant
we need to change it via the storage pool algorithm. That way we are spreading
=cut
-before 'select_single' => sub {
- my $self = shift @_;
- my $next_replicant = $self->next_storage;
- $self->current_replicant($next_replicant);
+around 'select_single' => sub {
+ my ($select_single, $self, @args) = @_;
+
+ if (my $forced_pool = $args[-1]->{force_pool}) {
+ delete $args[-1]->{force_pool};
+ return $self->_get_forced_pool($forced_pool)->select_single(@args);
+ } elsif($self->master->{transaction_depth}) {
+ return $self->master->select_single(@args);
+ } else {
+ $self->increment_storage;
+ return $self->$select_single(@args);
+ }
};
=head2 before: columns_info_for
before 'columns_info_for' => sub {
my $self = shift @_;
- my $next_replicant = $self->next_storage;
- $self->current_replicant($next_replicant);
+ $self->increment_storage;
};
+=head2 _get_forced_pool ($name)
+
+Given an identifier, find the most correct storage object to handle the query.
+
+=cut
+
+sub _get_forced_pool {
+ my ($self, $forced_pool) = @_;
+ if(blessed $forced_pool) {
+ return $forced_pool;
+ } elsif($forced_pool eq 'master') {
+ return $self->master;
+ } elsif(my $replicant = $self->pool->replicants->{$forced_pool}) {
+ return $replicant;
+ } else {
+ $self->master->throw_exception("$forced_pool is not a named replicant.");
+ }
+}
+
=head1 AUTHOR
-John Napiorkowski <john.napiorkowski@takkle.com>
+John Napiorkowski <jjnapiork@cpan.org>
=head1 LICENSE