1 package DBIx::Class::Storage::DBI::Replicated::Balancer;
4 requires 'next_storage';
8 DBIx::Class::Storage::DBI::Replicated::Balancer - A Software Load Balancer
12 This role is used internally by L<DBIx::Class::Storage::DBI::Replicated>.
16 Given a pool (L<DBIx::Class::Storage::DBI::Replicated::Pool>) of replicated
17 database's (L<DBIx::Class::Storage::DBI::Replicated::Replicant>), defines a
18 method by which query load can be spread out across each replicant in the pool.
22 This class defines the following attributes.
24 =head2 auto_validate_every ($seconds)
26 If auto_validate has some sort of value, run the L<validate_replicants> every
27 $seconds. Be careful with this, because if you set it to 0 you will end up
28 validating every query.
32 has 'auto_validate_every' => (
35 predicate=>'has_auto_validate_every',
40 The L<DBIx::Class::Storage::DBI> object that is the master database all the
41 replicants are trying to follow. The balancer needs to know it since it's the
48 isa=>'DBIx::Class::Storage::DBI',
54 The L<DBIx::Class::Storage::DBI::Replicated::Pool> object that we are trying to
61 isa=>'DBIx::Class::Storage::DBI::Replicated::Pool',
65 =head2 current_replicant
67 Replicant storages (slaves) handle all read only traffic. The assumption is
68 that your database will become readbound well before it becomes write bound
69 and that being able to spread your read only traffic around to multiple
70 databases is going to help you to scale traffic.
72 This attribute returns the next slave to handle a read request. Your L</pool>
73 attribute has methods to help you shuffle through all the available replicants
74 via it's balancer object.
78 has 'current_replicant' => (
80 isa=>'DBIx::Class::Storage::DBI',
91 This class defines the following methods.
93 =head2 _build_current_replicant
95 Lazy builder for the L</current_replicant_storage> attribute.
99 sub _build_current_replicant {
106 This method should be defined in the class which consumes this role.
108 Given a pool object, return the next replicant that will serve queries. The
109 default behavior is to grap the first replicant it finds but you can write
110 your own subclasses of L<DBIx::Class::Storage::DBI::Replicated::Balancer> to
111 support other balance systems.
113 This returns from the pool of active replicants. If there are no active
114 replicants, then you should have it return the master as an ultimate fallback.
116 =head2 around: next_storage
118 Advice on next storage to add the autovalidation. We have this broken out so
119 that it's easier to break out the auto validation into a role.
121 This also returns the master in the case that none of the replicants are active
122 or just just forgot to create them :)
126 around 'next_storage' => sub {
127 my ($next_storage, $self, @args) = @_;
130 ## Do we need to validate the replicants?
132 $self->has_auto_validate_every &&
133 ($self->auto_validate_every + $self->pool->last_validated) <= $now
135 $self->pool->validate_replicants;
138 ## Get a replicant, or the master if none
139 if(my $next = $self->$next_storage(@args)) {
142 $self->master->debugobj->print("No Replicants validate, falling back to master reads. ");
143 return $self->master;
147 =head2 increment_storage
149 Rolls the Storage to whatever is next in the queue, as defined by the Balancer.
153 sub increment_storage {
155 my $next_replicant = $self->next_storage;
156 $self->current_replicant($next_replicant);
159 =head2 around: select
161 Advice on the select attribute. Each time we use a replicant
162 we need to change it via the storage pool algorithm. That way we are spreading
163 the load evenly (hopefully) across existing capacity.
167 around 'select' => sub {
168 my ($select, $self, @args) = @_;
170 if (my $forced_pool = $args[-1]->{force_pool}) {
171 delete $args[-1]->{force_pool};
172 return $self->_get_forced_pool($forced_pool)->select(@args);
174 $self->increment_storage;
175 return $self->$select(@args);
179 =head2 around: select_single
181 Advice on the select_single attribute. Each time we use a replicant
182 we need to change it via the storage pool algorithm. That way we are spreading
183 the load evenly (hopefully) across existing capacity.
187 around 'select_single' => sub {
188 my ($select_single, $self, @args) = @_;
190 if (my $forced_pool = $args[-1]->{force_pool}) {
191 delete $args[-1]->{force_pool};
192 return $self->_get_forced_pool($forced_pool)->select_single(@args);
194 $self->increment_storage;
195 return $self->$select_single(@args);
199 =head2 before: columns_info_for
201 Advice on the current_replicant_storage attribute. Each time we use a replicant
202 we need to change it via the storage pool algorithm. That way we are spreading
203 the load evenly (hopefully) across existing capacity.
207 before 'columns_info_for' => sub {
209 $self->increment_storage;
212 =head2 _get_forced_pool ($name)
214 Given an identifier, find the most correct storage object to handle the query.
218 sub _get_forced_pool {
219 my ($self, $forced_pool) = @_;
220 if(blessed $forced_pool) {
222 } elsif($forced_pool eq 'master') {
223 return $self->master;
224 } elsif(my $replicant = $self->pool->replicants($forced_pool)) {
227 $self->master->throw_exception("$forced_pool is not a named replicant.");
233 John Napiorkowski <john.napiorkowski@takkle.com>
237 You may distribute this code under the same terms as Perl itself.