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
31 L<DBIx::Class::Storage::DBI::Replicated::Pool/validate_replicants>
32 every $seconds. Be careful with this, because if you set it to 0 you
33 will end up validating every query.
37 has 'auto_validate_every' => (
40 predicate=>'has_auto_validate_every',
45 The L<DBIx::Class::Storage::DBI> object that is the master database all the
46 replicants are trying to follow. The balancer needs to know it since it's the
59 The L<DBIx::Class::Storage::DBI::Replicated::Pool> object that we are trying to
66 isa=>'DBIx::Class::Storage::DBI::Replicated::Pool',
70 =head2 current_replicant
72 Replicant storages (slaves) handle all read only traffic. The assumption is
73 that your database will become readbound well before it becomes write bound
74 and that being able to spread your read only traffic around to multiple
75 databases is going to help you to scale traffic.
77 This attribute returns the next slave to handle a read request. Your L</pool>
78 attribute has methods to help you shuffle through all the available replicants
79 via its balancer object.
83 has 'current_replicant' => (
96 This class defines the following methods.
98 =head2 _build_current_replicant
100 Lazy builder for the L</current_replicant_storage> attribute.
104 sub _build_current_replicant {
111 This method should be defined in the class which consumes this role.
113 Given a pool object, return the next replicant that will serve queries. The
114 default behavior is to grab the first replicant it finds but you can write
115 your own subclasses of L<DBIx::Class::Storage::DBI::Replicated::Balancer> to
116 support other balance systems.
118 This returns from the pool of active replicants. If there are no active
119 replicants, then you should have it return the master as an ultimate fallback.
121 =head2 around: next_storage
123 Advice on next storage to add the autovalidation. We have this broken out so
124 that it's easier to break out the auto validation into a role.
126 This also returns the master in the case that none of the replicants are active
127 or just just forgot to create them :)
133 around 'next_storage' => sub {
134 my ($next_storage, $self, @args) = @_;
137 ## Do we need to validate the replicants?
139 $self->has_auto_validate_every &&
140 ($self->auto_validate_every + $self->pool->last_validated) <= $now
142 $self->pool->validate_replicants;
145 ## Get a replicant, or the master if none
146 if(my $next = $self->$next_storage(@args)) {
147 $self->master->debugobj->print("Moved back to slave\n") if $on_master;
151 $self->master->debugobj->print("No Replicants validate, falling back to master reads.\n")
154 return $self->master;
158 =head2 increment_storage
160 Rolls the Storage to whatever is next in the queue, as defined by the Balancer.
164 sub increment_storage {
166 my $next_replicant = $self->next_storage;
167 $self->current_replicant($next_replicant);
170 =head2 around: select
172 Advice on the select attribute. Each time we use a replicant
173 we need to change it via the storage pool algorithm. That way we are spreading
174 the load evenly (hopefully) across existing capacity.
178 around 'select' => sub {
179 my ($select, $self, @args) = @_;
181 if (my $forced_pool = $args[-1]->{force_pool}) {
182 delete $args[-1]->{force_pool};
183 return $self->_get_forced_pool($forced_pool)->select(@args);
184 } elsif($self->master->{transaction_depth}) {
185 return $self->master->select(@args);
187 $self->increment_storage;
188 return $self->$select(@args);
192 =head2 around: select_single
194 Advice on the select_single attribute. Each time we use a replicant
195 we need to change it via the storage pool algorithm. That way we are spreading
196 the load evenly (hopefully) across existing capacity.
200 around 'select_single' => sub {
201 my ($select_single, $self, @args) = @_;
203 if (my $forced_pool = $args[-1]->{force_pool}) {
204 delete $args[-1]->{force_pool};
205 return $self->_get_forced_pool($forced_pool)->select_single(@args);
206 } elsif($self->master->{transaction_depth}) {
207 return $self->master->select_single(@args);
209 $self->increment_storage;
210 return $self->$select_single(@args);
214 =head2 before: columns_info_for
216 Advice on the current_replicant_storage attribute. Each time we use a replicant
217 we need to change it via the storage pool algorithm. That way we are spreading
218 the load evenly (hopefully) across existing capacity.
222 before 'columns_info_for' => sub {
224 $self->increment_storage;
227 =head2 _get_forced_pool ($name)
229 Given an identifier, find the most correct storage object to handle the query.
233 sub _get_forced_pool {
234 my ($self, $forced_pool) = @_;
235 if(blessed $forced_pool) {
237 } elsif($forced_pool eq 'master') {
238 return $self->master;
239 } elsif(my $replicant = $self->pool->replicants->{$forced_pool}) {
242 $self->master->throw_exception("$forced_pool is not a named replicant.");
248 John Napiorkowski <jjnapiork@cpan.org>
252 You may distribute this code under the same terms as Perl itself.