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