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