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 grap 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 :)
130 around 'next_storage' => sub {
131 my ($next_storage, $self, @args) = @_;
134 ## Do we need to validate the replicants?
136 $self->has_auto_validate_every &&
137 ($self->auto_validate_every + $self->pool->last_validated) <= $now
139 $self->pool->validate_replicants;
142 ## Get a replicant, or the master if none
143 if(my $next = $self->$next_storage(@args)) {
146 $self->master->debugobj->print("No Replicants validate, falling back to master reads. ");
147 return $self->master;
151 =head2 increment_storage
153 Rolls the Storage to whatever is next in the queue, as defined by the Balancer.
157 sub increment_storage {
159 my $next_replicant = $self->next_storage;
160 $self->current_replicant($next_replicant);
163 =head2 around: select
165 Advice on the select attribute. Each time we use a replicant
166 we need to change it via the storage pool algorithm. That way we are spreading
167 the load evenly (hopefully) across existing capacity.
171 around 'select' => sub {
172 my ($select, $self, @args) = @_;
174 if (my $forced_pool = $args[-1]->{force_pool}) {
175 delete $args[-1]->{force_pool};
176 return $self->_get_forced_pool($forced_pool)->select(@args);
177 } elsif($self->master->{transaction_depth}) {
178 return $self->master->select(@args);
180 $self->increment_storage;
181 return $self->$select(@args);
185 =head2 around: select_single
187 Advice on the select_single attribute. Each time we use a replicant
188 we need to change it via the storage pool algorithm. That way we are spreading
189 the load evenly (hopefully) across existing capacity.
193 around 'select_single' => sub {
194 my ($select_single, $self, @args) = @_;
196 if (my $forced_pool = $args[-1]->{force_pool}) {
197 delete $args[-1]->{force_pool};
198 return $self->_get_forced_pool($forced_pool)->select_single(@args);
199 } elsif($self->master->{transaction_depth}) {
200 return $self->master->select_single(@args);
202 $self->increment_storage;
203 return $self->$select_single(@args);
207 =head2 before: columns_info_for
209 Advice on the current_replicant_storage attribute. Each time we use a replicant
210 we need to change it via the storage pool algorithm. That way we are spreading
211 the load evenly (hopefully) across existing capacity.
215 before 'columns_info_for' => sub {
217 $self->increment_storage;
220 =head2 _get_forced_pool ($name)
222 Given an identifier, find the most correct storage object to handle the query.
226 sub _get_forced_pool {
227 my ($self, $forced_pool) = @_;
228 if(blessed $forced_pool) {
230 } elsif($forced_pool eq 'master') {
231 return $self->master;
232 } elsif(my $replicant = $self->pool->replicants->{$forced_pool}) {
235 $self->master->throw_exception("$forced_pool is not a named replicant.");
241 John Napiorkowski <jjnapiork@cpan.org>
245 You may distribute this code under the same terms as Perl itself.