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