fixed regression in the random balancer that I created when I removed the shuffle...
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Replicated / Balancer.pm
CommitLineData
26ab719a 1package DBIx::Class::Storage::DBI::Replicated::Balancer;
2
17b05c13 3use Moose::Role;
4requires 'next_storage';
26ab719a 5
6=head1 NAME
7
8DBIx::Class::Storage::DBI::Replicated::Balancer; A Software Load Balancer
9
10=head1 SYNOPSIS
11
17b05c13 12This role is used internally by L<DBIx::Class::Storage::DBI::Replicated>.
26ab719a 13
14=head1 DESCRIPTION
15
16Given a pool (L<DBIx::Class::Storage::DBI::Replicated::Pool>) of replicated
17database's (L<DBIx::Class::Storage::DBI::Replicated::Replicant>), defines a
18method by which query load can be spread out across each replicant in the pool.
19
20=head1 ATTRIBUTES
21
22This class defines the following attributes.
23
7edf5f1c 24=head2 auto_validate_every ($seconds)
25
26If 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
28validating every query.
29
30=cut
31
32has 'auto_validate_every' => (
64cdad22 33 is=>'rw',
34 isa=>'Int',
35 predicate=>'has_auto_validate_every',
7edf5f1c 36);
37
106d5f3b 38=head2 master
39
40The L<DBIx::Class::Storage::DBI> object that is the master database all the
41replicants are trying to follow. The balancer needs to know it since it's the
42ultimate fallback.
43
44=cut
45
46has 'master' => (
64cdad22 47 is=>'ro',
48 isa=>'DBIx::Class::Storage::DBI',
49 required=>1,
106d5f3b 50);
51
cb6ec758 52=head2 pool
53
54The L<DBIx::Class::Storage::DBI::Replicated::Pool> object that we are trying to
55balance.
56
57=cut
58
59has 'pool' => (
64cdad22 60 is=>'ro',
61 isa=>'DBIx::Class::Storage::DBI::Replicated::Pool',
62 required=>1,
cb6ec758 63);
64
65=head2 current_replicant
66
67Replicant storages (slaves) handle all read only traffic. The assumption is
68that your database will become readbound well before it becomes write bound
69and that being able to spread your read only traffic around to multiple
70databases is going to help you to scale traffic.
71
72This attribute returns the next slave to handle a read request. Your L</pool>
73attribute has methods to help you shuffle through all the available replicants
74via it's balancer object.
75
76=cut
77
78has 'current_replicant' => (
64cdad22 79 is=> 'rw',
80 isa=>'DBIx::Class::Storage::DBI',
81 lazy_build=>1,
82 handles=>[qw/
83 select
84 select_single
85 columns_info_for
86 /],
cb6ec758 87);
88
26ab719a 89=head1 METHODS
90
91This class defines the following methods.
92
cb6ec758 93=head2 _build_current_replicant
94
95Lazy builder for the L</current_replicant_storage> attribute.
96
97=cut
98
99sub _build_current_replicant {
64cdad22 100 my $self = shift @_;
101 $self->next_storage;
cb6ec758 102}
103
104=head2 next_storage
26ab719a 105
17b05c13 106This method should be defined in the class which consumes this role.
107
26ab719a 108Given a pool object, return the next replicant that will serve queries. The
cb6ec758 109default behavior is to grap the first replicant it finds but you can write
110your own subclasses of L<DBIx::Class::Storage::DBI::Replicated::Balancer> to
111support other balance systems.
26ab719a 112
106d5f3b 113This returns from the pool of active replicants. If there are no active
114replicants, then you should have it return the master as an ultimate fallback.
115
17b05c13 116=head2 around: next_storage
117
118Advice on next storage to add the autovalidation. We have this broken out so
119that it's easier to break out the auto validation into a role.
120
121This also returns the master in the case that none of the replicants are active
122or just just forgot to create them :)
7edf5f1c 123
26ab719a 124=cut
125
17b05c13 126around 'next_storage' => sub {
64cdad22 127 my ($next_storage, $self, @args) = @_;
128 my $now = time;
0c90fabe 129
64cdad22 130 ## Do we need to validate the replicants?
131 if(
132 $self->has_auto_validate_every &&
133 ($self->auto_validate_every + $self->pool->last_validated) <= $now
134 ) {
135 $self->pool->validate_replicants;
136 }
17b05c13 137
64cdad22 138 ## Get a replicant, or the master if none
bc376f59 139 if(my $next = $self->$next_storage(@args)) {
140 return $next;
141 } else {
142 return $self->master;
143 }
17b05c13 144};
26ab719a 145
bbafcf26 146=head2 increment_storage
cb6ec758 147
bbafcf26 148Rolls the Storage to whatever is next in the queue, as defined by the Balancer.
cb6ec758 149
150=cut
151
bbafcf26 152sub increment_storage {
64cdad22 153 my $self = shift @_;
154 my $next_replicant = $self->next_storage;
155 $self->current_replicant($next_replicant);
bbafcf26 156}
157
158=head2 around: select
159
160Advice on the select attribute. Each time we use a replicant
161we need to change it via the storage pool algorithm. That way we are spreading
162the load evenly (hopefully) across existing capacity.
163
164=cut
165
166around 'select' => sub {
167 my ($select, $self, @args) = @_;
168
7e38d850 169 if (my $forced_pool = $args[-1]->{force_pool}) {
170 delete $args[-1]->{force_pool};
171 return $self->_get_forced_pool($forced_pool)->select(@args);
bbafcf26 172 } else {
173 $self->increment_storage;
174 return $self->$select(@args);
175 }
cb6ec758 176};
177
bbafcf26 178=head2 around: select_single
cb6ec758 179
180Advice on the select_single attribute. Each time we use a replicant
181we need to change it via the storage pool algorithm. That way we are spreading
182the load evenly (hopefully) across existing capacity.
183
184=cut
185
bbafcf26 186around 'select_single' => sub {
187 my ($select_single, $self, @args) = @_;
188
7e38d850 189 if (my $forced_pool = $args[-1]->{force_pool}) {
bc376f59 190 delete $args[-1]->{force_pool};
191 return $self->_get_forced_pool($forced_pool)->select_single(@args);
bbafcf26 192 } else {
bc376f59 193 $self->increment_storage;
bbafcf26 194 return $self->$select_single(@args);
195 }
cb6ec758 196};
197
106d5f3b 198=head2 before: columns_info_for
cb6ec758 199
200Advice on the current_replicant_storage attribute. Each time we use a replicant
201we need to change it via the storage pool algorithm. That way we are spreading
202the load evenly (hopefully) across existing capacity.
203
204=cut
205
106d5f3b 206before 'columns_info_for' => sub {
64cdad22 207 my $self = shift @_;
bbafcf26 208 $self->increment_storage;
cb6ec758 209};
26ab719a 210
7e38d850 211=head2 _get_forced_pool ($name)
212
213Given an identifier, find the most correct storage object to handle the query.
214
215=cut
216
217sub _get_forced_pool {
218 my ($self, $forced_pool) = @_;
219 if(blessed $forced_pool) {
220 return $forced_pool;
221 } elsif($forced_pool eq 'master') {
222 return $self->master;
223 } elsif(my $replicant = $self->pool->replicants($forced_pool)) {
224 return $replicant;
225 } else {
226 $self->master->throw_exception("$forced_pool is not a named replicant.");
227 }
228}
229
26ab719a 230=head1 AUTHOR
231
232John Napiorkowski <john.napiorkowski@takkle.com>
233
234=head1 LICENSE
235
236You may distribute this code under the same terms as Perl itself.
237
238=cut
239
cb6ec758 2401;