Merge 'trunk' into 'sqla_1.50_compat'
[dbsrgits/DBIx-Class-Historic.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
21fc4719 8DBIx::Class::Storage::DBI::Replicated::Balancer - A Software Load Balancer
26ab719a 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;
13b9e828 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
13b9e828 134 ) {
64cdad22 135 $self->pool->validate_replicants;
136 }
13b9e828 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 {
13b9e828 142 $self->master->debugobj->print("No Replicants validate, falling back to master reads. ");
bc376f59 143 return $self->master;
144 }
17b05c13 145};
26ab719a 146
bbafcf26 147=head2 increment_storage
cb6ec758 148
bbafcf26 149Rolls the Storage to whatever is next in the queue, as defined by the Balancer.
cb6ec758 150
151=cut
152
bbafcf26 153sub increment_storage {
64cdad22 154 my $self = shift @_;
155 my $next_replicant = $self->next_storage;
156 $self->current_replicant($next_replicant);
bbafcf26 157}
158
159=head2 around: select
160
161Advice on the select attribute. Each time we use a replicant
162we need to change it via the storage pool algorithm. That way we are spreading
163the load evenly (hopefully) across existing capacity.
164
165=cut
166
167around 'select' => sub {
168 my ($select, $self, @args) = @_;
169
7e38d850 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);
bbafcf26 173 } else {
174 $self->increment_storage;
175 return $self->$select(@args);
176 }
cb6ec758 177};
178
bbafcf26 179=head2 around: select_single
cb6ec758 180
181Advice on the select_single attribute. Each time we use a replicant
182we need to change it via the storage pool algorithm. That way we are spreading
183the load evenly (hopefully) across existing capacity.
184
185=cut
186
bbafcf26 187around 'select_single' => sub {
188 my ($select_single, $self, @args) = @_;
189
7e38d850 190 if (my $forced_pool = $args[-1]->{force_pool}) {
bc376f59 191 delete $args[-1]->{force_pool};
192 return $self->_get_forced_pool($forced_pool)->select_single(@args);
bbafcf26 193 } else {
bc376f59 194 $self->increment_storage;
bbafcf26 195 return $self->$select_single(@args);
196 }
cb6ec758 197};
198
106d5f3b 199=head2 before: columns_info_for
cb6ec758 200
201Advice on the current_replicant_storage attribute. Each time we use a replicant
202we need to change it via the storage pool algorithm. That way we are spreading
203the load evenly (hopefully) across existing capacity.
204
205=cut
206
106d5f3b 207before 'columns_info_for' => sub {
64cdad22 208 my $self = shift @_;
bbafcf26 209 $self->increment_storage;
cb6ec758 210};
26ab719a 211
7e38d850 212=head2 _get_forced_pool ($name)
213
214Given an identifier, find the most correct storage object to handle the query.
215
216=cut
217
218sub _get_forced_pool {
219 my ($self, $forced_pool) = @_;
220 if(blessed $forced_pool) {
221 return $forced_pool;
222 } elsif($forced_pool eq 'master') {
223 return $self->master;
224 } elsif(my $replicant = $self->pool->replicants($forced_pool)) {
225 return $replicant;
226 } else {
227 $self->master->throw_exception("$forced_pool is not a named replicant.");
228 }
229}
230
26ab719a 231=head1 AUTHOR
232
233John Napiorkowski <john.napiorkowski@takkle.com>
234
235=head1 LICENSE
236
237You may distribute this code under the same terms as Perl itself.
238
239=cut
240
cb6ec758 2411;