1 package DBIx::Class::Storage::DBI::Replicated::Balancer;
4 requires 'next_storage';
6 use DBIx::Class::_Types qw(PositiveInteger DBICStorageDBI DBICStorageDBIReplicatedPool);
7 use DBIx::Class::Storage::DBI::Replicated::Pool;
8 use Scalar::Util qw(blessed);
14 DBIx::Class::Storage::DBI::Replicated::Balancer - A Software Load Balancer
18 This role is used internally by L<DBIx::Class::Storage::DBI::Replicated>.
22 Given a pool (L<DBIx::Class::Storage::DBI::Replicated::Pool>) of replicated
23 database's (L<DBIx::Class::Storage::DBI::Replicated::Replicant>), defines a
24 method by which query load can be spread out across each replicant in the pool.
28 This class defines the following attributes.
30 =head2 auto_validate_every ($seconds)
32 If auto_validate has some sort of value, run
33 L<DBIx::Class::Storage::DBI::Replicated::Pool/validate_replicants>
34 every $seconds. Be careful with this, because if you set it to 0 you
35 will end up validating every query.
39 has 'auto_validate_every' => (
42 predicate=>'has_auto_validate_every',
47 The L<DBIx::Class::Storage::DBI> object that is the master database all the
48 replicants are trying to follow. The balancer needs to know it since it's the
61 The L<DBIx::Class::Storage::DBI::Replicated::Pool> object that we are trying to
68 isa=>DBICStorageDBIReplicatedPool,
72 =head2 current_replicant
74 Replicant storages (slaves) handle all read only traffic. The assumption is
75 that your database will become readbound well before it becomes write bound
76 and that being able to spread your read only traffic around to multiple
77 databases is going to help you to scale traffic.
79 This attribute returns the next slave to handle a read request. Your L</pool>
80 attribute has methods to help you shuffle through all the available replicants
81 via its balancer object.
85 has 'current_replicant' => (
99 This class defines the following methods.
101 =head2 _build_current_replicant
103 Lazy builder for the L</current_replicant> attribute.
107 sub _build_current_replicant {
114 This method should be defined in the class which consumes this role.
116 Given a pool object, return the next replicant that will serve queries. The
117 default behavior is to grab the first replicant it finds but you can write
118 your own subclasses of L<DBIx::Class::Storage::DBI::Replicated::Balancer> to
119 support other balance systems.
121 This returns from the pool of active replicants. If there are no active
122 replicants, then you should have it return the master as an ultimate fallback.
124 =head2 around: next_storage
126 Advice on next storage to add the autovalidation. We have this broken out so
127 that it's easier to break out the auto validation into a role.
129 This also returns the master in the case that none of the replicants are active
130 or just forgot to create them :)
136 around 'next_storage' => sub {
137 my ($next_storage, $self, @args) = @_;
140 ## Do we need to validate the replicants?
142 $self->has_auto_validate_every &&
143 ($self->auto_validate_every + $self->pool->last_validated) <= $now
145 $self->pool->validate_replicants;
148 ## Get a replicant, or the master if none
149 if(my $next = $self->$next_storage(@args)) {
150 $self->master->debugobj->print("Moved back to slave\n") if $on_master;
154 $self->master->debugobj->print("No Replicants validate, falling back to master reads.\n")
157 return $self->master;
161 =head2 increment_storage
163 Rolls the Storage to whatever is next in the queue, as defined by the Balancer.
167 sub increment_storage {
169 my $next_replicant = $self->next_storage;
170 $self->current_replicant($next_replicant);
173 =head2 around: select
175 Advice on the select attribute. Each time we use a replicant
176 we need to change it via the storage pool algorithm. That way we are spreading
177 the load evenly (hopefully) across existing capacity.
181 around 'select' => sub {
182 my ($select, $self, @args) = @_;
184 if (my $forced_pool = $args[-1]->{force_pool}) {
185 delete $args[-1]->{force_pool};
186 return $self->_get_forced_pool($forced_pool)->select(@args);
187 } elsif($self->master->{transaction_depth}) {
188 return $self->master->select(@args);
190 $self->increment_storage;
191 return $self->$select(@args);
195 =head2 around: select_single
197 Advice on the select_single attribute. Each time we use a replicant
198 we need to change it via the storage pool algorithm. That way we are spreading
199 the load evenly (hopefully) across existing capacity.
203 around 'select_single' => sub {
204 my ($select_single, $self, @args) = @_;
206 if (my $forced_pool = $args[-1]->{force_pool}) {
207 delete $args[-1]->{force_pool};
208 return $self->_get_forced_pool($forced_pool)->select_single(@args);
209 } elsif($self->master->{transaction_depth}) {
210 return $self->master->select_single(@args);
212 $self->increment_storage;
213 return $self->$select_single(@args);
217 =head2 before: columns_info_for
219 Advice on the current_replicant_storage attribute. Each time we use a replicant
220 we need to change it via the storage pool algorithm. That way we are spreading
221 the load evenly (hopefully) across existing capacity.
225 before 'columns_info_for' => sub {
227 $self->increment_storage;
230 =head2 _get_forced_pool ($name)
232 Given an identifier, find the most correct storage object to handle the query.
236 sub _get_forced_pool {
237 my ($self, $forced_pool) = @_;
238 if(blessed $forced_pool) {
240 } elsif($forced_pool eq 'master') {
241 return $self->master;
242 } elsif(my $replicant = $self->pool->replicants->{$forced_pool}) {
245 $self->master->throw_exception("'$forced_pool' is not a named replicant.");
249 =head1 FURTHER QUESTIONS?
251 Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
253 =head1 COPYRIGHT AND LICENSE
255 This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
256 by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
257 redistribute it and/or modify it under the same terms as the
258 L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.