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