good start on the validation of replicants and a system to automatically validate...
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Replicated / Balancer.pm
CommitLineData
26ab719a 1package DBIx::Class::Storage::DBI::Replicated::Balancer;
2
3use Moose;
26ab719a 4
5=head1 NAME
6
7DBIx::Class::Storage::DBI::Replicated::Balancer; A Software Load Balancer
8
9=head1 SYNOPSIS
10
11This class is used internally by L<DBIx::Class::Storage::DBI::Replicated>. You
12shouldn't need to create instances of this class.
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' => (
33 is=>'rw',
34 isa=>'Int',
35 predicate=>'had_auto_validate_every',
36);
37
38=head2 last_validated
39
40This is an integer representing a time since the last time the replicants were
41validated. It's nothing fancy, just an integer provided via the perl time
42builtin.
43
44=cut
45
46has 'last_validated' => (
47 is=>'rw',
48 isa=>'Int',
49 reader=>'last_validated',
50 writer=>'_last_validated',
51 lazy=>1,
52 default=>sub {
53 time;
54 },
55);
56
106d5f3b 57=head2 master
58
59The L<DBIx::Class::Storage::DBI> object that is the master database all the
60replicants are trying to follow. The balancer needs to know it since it's the
61ultimate fallback.
62
63=cut
64
65has 'master' => (
66 is=>'ro',
67 isa=>'DBIx::Class::Storage::DBI',
68 required=>1,
69);
70
cb6ec758 71=head2 pool
72
73The L<DBIx::Class::Storage::DBI::Replicated::Pool> object that we are trying to
74balance.
75
76=cut
77
78has 'pool' => (
79 is=>'ro',
80 isa=>'DBIx::Class::Storage::DBI::Replicated::Pool',
81 required=>1,
82);
83
84=head2 current_replicant
85
86Replicant storages (slaves) handle all read only traffic. The assumption is
87that your database will become readbound well before it becomes write bound
88and that being able to spread your read only traffic around to multiple
89databases is going to help you to scale traffic.
90
91This attribute returns the next slave to handle a read request. Your L</pool>
92attribute has methods to help you shuffle through all the available replicants
93via it's balancer object.
94
95=cut
96
97has 'current_replicant' => (
98 is=> 'rw',
99 isa=>'DBIx::Class::Storage::DBI',
100 lazy_build=>1,
101 handles=>[qw/
102 select
103 select_single
104 columns_info_for
105 /],
106);
107
26ab719a 108=head1 METHODS
109
110This class defines the following methods.
111
cb6ec758 112=head2 _build_current_replicant
113
114Lazy builder for the L</current_replicant_storage> attribute.
115
116=cut
117
118sub _build_current_replicant {
119 my $self = shift @_;
106d5f3b 120 $self->next_storage;
cb6ec758 121}
122
123=head2 next_storage
26ab719a 124
125Given a pool object, return the next replicant that will serve queries. The
cb6ec758 126default behavior is to grap the first replicant it finds but you can write
127your own subclasses of L<DBIx::Class::Storage::DBI::Replicated::Balancer> to
128support other balance systems.
26ab719a 129
106d5f3b 130This returns from the pool of active replicants. If there are no active
131replicants, then you should have it return the master as an ultimate fallback.
132
7edf5f1c 133TODO this needs to wrap for the subclasses better. Maybe good use of INNER?
134
26ab719a 135=cut
136
137sub next_storage {
138 my $self = shift @_;
7edf5f1c 139
140 ## Do we need to validate the replicants?
141 if(
142 $self->had_auto_validate_every &&
143 ($self->auto_validate_every + $self->last_validated) > time
144 ) {
145 $self->pool->validate_replicants;
146 $self->_last_validated(time);
147 }
148
149 ## Get a replicant, or the master if none
106d5f3b 150 my $next = ($self->pool->active_replicants)[0];
151 return $next ? $next:$self->master;
26ab719a 152}
153
106d5f3b 154=head2 before: select
cb6ec758 155
156Advice on the select attribute. Each time we use a replicant
157we need to change it via the storage pool algorithm. That way we are spreading
158the load evenly (hopefully) across existing capacity.
159
160=cut
161
106d5f3b 162before 'select' => sub {
cb6ec758 163 my $self = shift @_;
164 my $next_replicant = $self->next_storage;
165 $self->current_replicant($next_replicant);
166};
167
106d5f3b 168=head2 before: select_single
cb6ec758 169
170Advice on the select_single attribute. Each time we use a replicant
171we need to change it via the storage pool algorithm. That way we are spreading
172the load evenly (hopefully) across existing capacity.
173
174=cut
175
106d5f3b 176before 'select_single' => sub {
cb6ec758 177 my $self = shift @_;
178 my $next_replicant = $self->next_storage;
179 $self->current_replicant($next_replicant);
180};
181
106d5f3b 182=head2 before: columns_info_for
cb6ec758 183
184Advice on the current_replicant_storage 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
106d5f3b 190before 'columns_info_for' => sub {
cb6ec758 191 my $self = shift @_;
192 my $next_replicant = $self->next_storage;
193 $self->current_replicant($next_replicant);
194};
26ab719a 195
196=head1 AUTHOR
197
198John Napiorkowski <john.napiorkowski@takkle.com>
199
200=head1 LICENSE
201
202You may distribute this code under the same terms as Perl itself.
203
204=cut
205
cb6ec758 2061;