1 package DBIx::Class::Storage::DBI::Replicated::Balancer;
4 requires 'next_storage';
5 use Types::Standard qw/Int/;
6 use Type::Utils qw/class_type/;
7 use Scalar::Util qw/blessed/;
8 use DBIx::Class::Storage::DBI::Replicated::Pool;
9 use DBIx::Class::Storage::DBI::Replicated::Types qw/DBICStorageDBI/;
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=>class_type('DBIx::Class::Storage::DBI::Replicated::Pool'),
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' => (
101 This class defines the following methods.
103 =head2 _build_current_replicant
105 Lazy builder for the L</current_replicant> attribute.
109 sub _build_current_replicant {
116 This method should be defined in the class which consumes this role.
118 Given a pool object, return the next replicant that will serve queries. The
119 default behavior is to grab the first replicant it finds but you can write
120 your own subclasses of L<DBIx::Class::Storage::DBI::Replicated::Balancer> to
121 support other balance systems.
123 This returns from the pool of active replicants. If there are no active
124 replicants, then you should have it return the master as an ultimate fallback.
126 =head2 around: next_storage
128 Advice on next storage to add the autovalidation. We have this broken out so
129 that it's easier to break out the auto validation into a role.
131 This also returns the master in the case that none of the replicants are active
132 or just forgot to create them :)
138 around 'next_storage' => sub {
139 my ($next_storage, $self, @args) = @_;
142 ## Do we need to validate the replicants?
144 $self->has_auto_validate_every &&
145 ($self->auto_validate_every + $self->pool->last_validated) <= $now
147 $self->pool->validate_replicants;
150 ## Get a replicant, or the master if none
151 if(my $next = $self->$next_storage(@args)) {
152 $self->master->debugobj->print("Moved back to slave\n") if $on_master;
156 $self->master->debugobj->print("No Replicants validate, falling back to master reads.\n")
159 return $self->master;
163 =head2 increment_storage
165 Rolls the Storage to whatever is next in the queue, as defined by the Balancer.
169 sub increment_storage {
171 my $next_replicant = $self->next_storage;
172 $self->current_replicant($next_replicant);
175 =head2 around: select
177 Advice on the select attribute. Each time we use a replicant
178 we need to change it via the storage pool algorithm. That way we are spreading
179 the load evenly (hopefully) across existing capacity.
183 around 'select' => sub {
184 my ($select, $self, @args) = @_;
186 if (my $forced_pool = $args[-1]->{force_pool}) {
187 delete $args[-1]->{force_pool};
188 return $self->_get_forced_pool($forced_pool)->select(@args);
189 } elsif($self->master->{transaction_depth}) {
190 return $self->master->select(@args);
192 $self->increment_storage;
193 return $self->$select(@args);
197 =head2 around: select_single
199 Advice on the select_single attribute. Each time we use a replicant
200 we need to change it via the storage pool algorithm. That way we are spreading
201 the load evenly (hopefully) across existing capacity.
205 around 'select_single' => sub {
206 my ($select_single, $self, @args) = @_;
208 if (my $forced_pool = $args[-1]->{force_pool}) {
209 delete $args[-1]->{force_pool};
210 return $self->_get_forced_pool($forced_pool)->select_single(@args);
211 } elsif($self->master->{transaction_depth}) {
212 return $self->master->select_single(@args);
214 $self->increment_storage;
215 return $self->$select_single(@args);
219 =head2 before: columns_info_for
221 Advice on the current_replicant_storage attribute. Each time we use a replicant
222 we need to change it via the storage pool algorithm. That way we are spreading
223 the load evenly (hopefully) across existing capacity.
227 before 'columns_info_for' => sub {
229 $self->increment_storage;
232 =head2 _get_forced_pool ($name)
234 Given an identifier, find the most correct storage object to handle the query.
238 sub _get_forced_pool {
239 my ($self, $forced_pool) = @_;
240 if(blessed $forced_pool) {
242 } elsif($forced_pool eq 'master') {
243 return $self->master;
244 } elsif(my $replicant = $self->pool->replicants->{$forced_pool}) {
247 $self->master->throw_exception("'$forced_pool' is not a named replicant.");
251 =head1 FURTHER QUESTIONS?
253 Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
255 =head1 COPYRIGHT AND LICENSE
257 This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
258 by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
259 redistribute it and/or modify it under the same terms as the
260 L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.