1 package DBIx::Class::Storage::DBI::Replicated::Balancer;
4 requires 'next_storage';
5 use MooseX::Types::Moose qw/Int/;
7 use namespace::clean -except => 'meta';
11 DBIx::Class::Storage::DBI::Replicated::Balancer - A Software Load Balancer
15 This role is used internally by L<DBIx::Class::Storage::DBI::Replicated>.
19 Given a pool (L<DBIx::Class::Storage::DBI::Replicated::Pool>) of replicated
20 database's (L<DBIx::Class::Storage::DBI::Replicated::Replicant>), defines a
21 method by which query load can be spread out across each replicant in the pool.
25 This class defines the following attributes.
27 =head2 auto_validate_every ($seconds)
29 If auto_validate has some sort of value, run the L<validate_replicants> every
30 $seconds. Be careful with this, because if you set it to 0 you will end up
31 validating every query.
35 has 'auto_validate_every' => (
38 predicate=>'has_auto_validate_every',
43 The L<DBIx::Class::Storage::DBI> object that is the master database all the
44 replicants are trying to follow. The balancer needs to know it since it's the
51 isa=>'DBIx::Class::Storage::DBI',
57 The L<DBIx::Class::Storage::DBI::Replicated::Pool> object that we are trying to
64 isa=>'DBIx::Class::Storage::DBI::Replicated::Pool',
68 =head2 current_replicant
70 Replicant storages (slaves) handle all read only traffic. The assumption is
71 that your database will become readbound well before it becomes write bound
72 and that being able to spread your read only traffic around to multiple
73 databases is going to help you to scale traffic.
75 This attribute returns the next slave to handle a read request. Your L</pool>
76 attribute has methods to help you shuffle through all the available replicants
77 via it's balancer object.
81 has 'current_replicant' => (
83 isa=>'DBIx::Class::Storage::DBI',
94 This class defines the following methods.
96 =head2 _build_current_replicant
98 Lazy builder for the L</current_replicant_storage> attribute.
102 sub _build_current_replicant {
109 This method should be defined in the class which consumes this role.
111 Given a pool object, return the next replicant that will serve queries. The
112 default behavior is to grap the first replicant it finds but you can write
113 your own subclasses of L<DBIx::Class::Storage::DBI::Replicated::Balancer> to
114 support other balance systems.
116 This returns from the pool of active replicants. If there are no active
117 replicants, then you should have it return the master as an ultimate fallback.
119 =head2 around: next_storage
121 Advice on next storage to add the autovalidation. We have this broken out so
122 that it's easier to break out the auto validation into a role.
124 This also returns the master in the case that none of the replicants are active
125 or just just forgot to create them :)
129 around 'next_storage' => sub {
130 my ($next_storage, $self, @args) = @_;
133 ## Do we need to validate the replicants?
135 $self->has_auto_validate_every &&
136 ($self->auto_validate_every + $self->pool->last_validated) <= $now
138 $self->pool->validate_replicants;
141 ## Get a replicant, or the master if none
142 if(my $next = $self->$next_storage(@args)) {
145 $self->master->debugobj->print("No Replicants validate, falling back to master reads. ");
146 return $self->master;
150 =head2 increment_storage
152 Rolls the Storage to whatever is next in the queue, as defined by the Balancer.
156 sub increment_storage {
158 my $next_replicant = $self->next_storage;
159 $self->current_replicant($next_replicant);
162 =head2 around: select
164 Advice on the select attribute. Each time we use a replicant
165 we need to change it via the storage pool algorithm. That way we are spreading
166 the load evenly (hopefully) across existing capacity.
170 around 'select' => sub {
171 my ($select, $self, @args) = @_;
173 if (my $forced_pool = $args[-1]->{force_pool}) {
174 delete $args[-1]->{force_pool};
175 return $self->_get_forced_pool($forced_pool)->select(@args);
176 } elsif($self->master->{transaction_depth}) {
177 return $self->master->select(@args);
179 $self->increment_storage;
180 return $self->$select(@args);
184 =head2 around: select_single
186 Advice on the select_single attribute. Each time we use a replicant
187 we need to change it via the storage pool algorithm. That way we are spreading
188 the load evenly (hopefully) across existing capacity.
192 around 'select_single' => sub {
193 my ($select_single, $self, @args) = @_;
195 if (my $forced_pool = $args[-1]->{force_pool}) {
196 delete $args[-1]->{force_pool};
197 return $self->_get_forced_pool($forced_pool)->select_single(@args);
198 } elsif($self->master->{transaction_depth}) {
199 return $self->master->select_single(@args);
201 $self->increment_storage;
202 return $self->$select_single(@args);
206 =head2 before: columns_info_for
208 Advice on the current_replicant_storage attribute. Each time we use a replicant
209 we need to change it via the storage pool algorithm. That way we are spreading
210 the load evenly (hopefully) across existing capacity.
214 before 'columns_info_for' => sub {
216 $self->increment_storage;
219 =head2 _get_forced_pool ($name)
221 Given an identifier, find the most correct storage object to handle the query.
225 sub _get_forced_pool {
226 my ($self, $forced_pool) = @_;
227 if(blessed $forced_pool) {
229 } elsif($forced_pool eq 'master') {
230 return $self->master;
231 } elsif(my $replicant = $self->pool->replicants->{$forced_pool}) {
234 $self->master->throw_exception("$forced_pool is not a named replicant.");
240 John Napiorkowski <jjnapiork@cpan.org>
244 You may distribute this code under the same terms as Perl itself.