1 package DBIx::Class::Storage::DBI::Replicated::Balancer;
4 requires 'next_storage';
5 use MooseX::Types::Moose qw/Int/;
6 use DBIx::Class::Storage::DBI::Replicated::Pool;
7 use DBIx::Class::Storage::DBI::Replicated::Types qw/DBICStorageDBI/;
8 use namespace::clean -except => 'meta';
12 DBIx::Class::Storage::DBI::Replicated::Balancer - A Software Load Balancer
16 This role is used internally by L<DBIx::Class::Storage::DBI::Replicated>.
20 Given a pool (L<DBIx::Class::Storage::DBI::Replicated::Pool>) of replicated
21 database's (L<DBIx::Class::Storage::DBI::Replicated::Replicant>), defines a
22 method by which query load can be spread out across each replicant in the pool.
26 This class defines the following attributes.
28 =head2 auto_validate_every ($seconds)
30 If auto_validate has some sort of value, run the L<validate_replicants> every
31 $seconds. Be careful with this, because if you set it to 0 you will end up
32 validating every query.
36 has 'auto_validate_every' => (
39 predicate=>'has_auto_validate_every',
44 The L<DBIx::Class::Storage::DBI> object that is the master database all the
45 replicants are trying to follow. The balancer needs to know it since it's the
58 The L<DBIx::Class::Storage::DBI::Replicated::Pool> object that we are trying to
65 isa=>'DBIx::Class::Storage::DBI::Replicated::Pool',
69 =head2 current_replicant
71 Replicant storages (slaves) handle all read only traffic. The assumption is
72 that your database will become readbound well before it becomes write bound
73 and that being able to spread your read only traffic around to multiple
74 databases is going to help you to scale traffic.
76 This attribute returns the next slave to handle a read request. Your L</pool>
77 attribute has methods to help you shuffle through all the available replicants
78 via its balancer object.
82 has 'current_replicant' => (
95 This class defines the following methods.
97 =head2 _build_current_replicant
99 Lazy builder for the L</current_replicant_storage> attribute.
103 sub _build_current_replicant {
110 This method should be defined in the class which consumes this role.
112 Given a pool object, return the next replicant that will serve queries. The
113 default behavior is to grab the first replicant it finds but you can write
114 your own subclasses of L<DBIx::Class::Storage::DBI::Replicated::Balancer> to
115 support other balance systems.
117 This returns from the pool of active replicants. If there are no active
118 replicants, then you should have it return the master as an ultimate fallback.
120 =head2 around: next_storage
122 Advice on next storage to add the autovalidation. We have this broken out so
123 that it's easier to break out the auto validation into a role.
125 This also returns the master in the case that none of the replicants are active
126 or just just forgot to create them :)
132 around 'next_storage' => sub {
133 my ($next_storage, $self, @args) = @_;
136 ## Do we need to validate the replicants?
138 $self->has_auto_validate_every &&
139 ($self->auto_validate_every + $self->pool->last_validated) <= $now
141 $self->pool->validate_replicants;
144 ## Get a replicant, or the master if none
145 if(my $next = $self->$next_storage(@args)) {
146 $self->master->debugobj->print("Moved back to slave\n") if $on_master;
150 $self->master->debugobj->print("No Replicants validate, falling back to master reads.\n")
153 return $self->master;
157 =head2 increment_storage
159 Rolls the Storage to whatever is next in the queue, as defined by the Balancer.
163 sub increment_storage {
165 my $next_replicant = $self->next_storage;
166 $self->current_replicant($next_replicant);
169 =head2 around: select
171 Advice on the select attribute. Each time we use a replicant
172 we need to change it via the storage pool algorithm. That way we are spreading
173 the load evenly (hopefully) across existing capacity.
177 around 'select' => sub {
178 my ($select, $self, @args) = @_;
180 if (my $forced_pool = $args[-1]->{force_pool}) {
181 delete $args[-1]->{force_pool};
182 return $self->_get_forced_pool($forced_pool)->select(@args);
183 } elsif($self->master->{transaction_depth}) {
184 return $self->master->select(@args);
186 $self->increment_storage;
187 return $self->$select(@args);
191 =head2 around: select_single
193 Advice on the select_single attribute. Each time we use a replicant
194 we need to change it via the storage pool algorithm. That way we are spreading
195 the load evenly (hopefully) across existing capacity.
199 around 'select_single' => sub {
200 my ($select_single, $self, @args) = @_;
202 if (my $forced_pool = $args[-1]->{force_pool}) {
203 delete $args[-1]->{force_pool};
204 return $self->_get_forced_pool($forced_pool)->select_single(@args);
205 } elsif($self->master->{transaction_depth}) {
206 return $self->master->select_single(@args);
208 $self->increment_storage;
209 return $self->$select_single(@args);
213 =head2 before: columns_info_for
215 Advice on the current_replicant_storage attribute. Each time we use a replicant
216 we need to change it via the storage pool algorithm. That way we are spreading
217 the load evenly (hopefully) across existing capacity.
221 before 'columns_info_for' => sub {
223 $self->increment_storage;
226 =head2 _get_forced_pool ($name)
228 Given an identifier, find the most correct storage object to handle the query.
232 sub _get_forced_pool {
233 my ($self, $forced_pool) = @_;
234 if(blessed $forced_pool) {
236 } elsif($forced_pool eq 'master') {
237 return $self->master;
238 } elsif(my $replicant = $self->pool->replicants->{$forced_pool}) {
241 $self->master->throw_exception("$forced_pool is not a named replicant.");
247 John Napiorkowski <jjnapiork@cpan.org>
251 You may distribute this code under the same terms as Perl itself.