1 package DBIx::Class::Storage::DBI::Replicated::Balancer;
5 use DBIx::Class::Storage::DBI::Replicated::Types
6 qw(PositiveInteger DBICStorageDBI DBICStorageDBIReplicatedPool);
8 requires 'next_storage';
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',
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=>DBICStorageDBIReplicatedPool,
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' => (
87 builder=>'_build_current_replicant',
97 This class defines the following methods.
99 =head2 _build_current_replicant
101 Lazy builder for the L</current_replicant_storage> attribute.
105 sub _build_current_replicant {
112 This method should be defined in the class which consumes this role.
114 Given a pool object, return the next replicant that will serve queries. The
115 default behavior is to grab the first replicant it finds but you can write
116 your own subclasses of L<DBIx::Class::Storage::DBI::Replicated::Balancer> to
117 support other balance systems.
119 This returns from the pool of active replicants. If there are no active
120 replicants, then you should have it return the master as an ultimate fallback.
122 =head2 around: next_storage
124 Advice on next storage to add the autovalidation. We have this broken out so
125 that it's easier to break out the auto validation into a role.
127 This also returns the master in the case that none of the replicants are active
128 or just just for?blgot to create them :)
134 around 'next_storage' => sub {
135 my ($next_storage, $self, @args) = @_;
138 ## Do we need to validate the replicants?
140 $self->has_auto_validate_every &&
141 ($self->auto_validate_every + $self->pool->last_validated) <= $now
143 $self->pool->validate_replicants;
146 ## Get a replicant, or the master if none
147 if(my $next = $self->$next_storage(@args)) {
148 $self->master->debugobj->print("Moved back to slave\n") if $on_master;
152 $self->master->debugobj->print("No Replicants validate, falling back to master reads.\n")
155 return $self->master;
159 =head2 increment_storage
161 Rolls the Storage to whatever is next in the queue, as defined by the Balancer.
165 sub increment_storage {
167 my $next_replicant = $self->next_storage;
168 $self->current_replicant($next_replicant);
171 =head2 around: select
173 Advice on the select attribute. Each time we use a replicant
174 we need to change it via the storage pool algorithm. That way we are spreading
175 the load evenly (hopefully) across existing capacity.
179 around 'select' => sub {
180 my ($select, $self, @args) = @_;
182 if (my $forced_pool = $args[-1]->{force_pool}) {
183 delete $args[-1]->{force_pool};
184 return $self->_get_forced_pool($forced_pool)->select(@args);
185 } elsif($self->master->{transaction_depth}) {
186 return $self->master->select(@args);
188 $self->increment_storage;
189 return $self->$select(@args);
193 =head2 around: select_single
195 Advice on the select_single attribute. Each time we use a replicant
196 we need to change it via the storage pool algorithm. That way we are spreading
197 the load evenly (hopefully) across existing capacity.
201 around 'select_single' => sub {
202 my ($select_single, $self, @args) = @_;
204 if (my $forced_pool = $args[-1]->{force_pool}) {
205 delete $args[-1]->{force_pool};
206 return $self->_get_forced_pool($forced_pool)->select_single(@args);
207 } elsif($self->master->{transaction_depth}) {
208 return $self->master->select_single(@args);
210 $self->increment_storage;
211 return $self->$select_single(@args);
215 =head2 before: columns_info_for
217 Advice on the current_replicant_storage attribute. Each time we use a replicant
218 we need to change it via the storage pool algorithm. That way we are spreading
219 the load evenly (hopefully) across existing capacity.
223 before 'columns_info_for' => sub {
225 $self->increment_storage;
228 =head2 _get_forced_pool ($name)
230 Given an identifier, find the most correct storage object to handle the query.
234 sub _get_forced_pool {
235 my ($self, $forced_pool) = @_;
236 if(Scalar::Util::blessed($forced_pool)) {
238 } elsif($forced_pool eq 'master') {
239 return $self->master;
240 } elsif(my $replicant = $self->pool->replicants->{$forced_pool}) {
243 $self->master->throw_exception("$forced_pool is not a named replicant.");
249 John Napiorkowski <jjnapiork@cpan.org>
253 You may distribute this code under the same terms as Perl itself.