f69ce8c64eebc5d8b453b9e4fb73aa38b0412a70
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Replicated / Balancer.pm
1 package DBIx::Class::Storage::DBI::Replicated::Balancer;
2
3 use Moose;
4
5 =head1 NAME
6
7 DBIx::Class::Storage::DBI::Replicated::Balancer; A Software Load Balancer 
8
9 =head1 SYNOPSIS
10
11 This class is used internally by L<DBIx::Class::Storage::DBI::Replicated>.  You
12 shouldn't need to create instances of this class.
13     
14 =head1 DESCRIPTION
15
16 Given a pool (L<DBIx::Class::Storage::DBI::Replicated::Pool>) of replicated
17 database's (L<DBIx::Class::Storage::DBI::Replicated::Replicant>), defines a
18 method by which query load can be spread out across each replicant in the pool.
19
20 =head1 ATTRIBUTES
21
22 This class defines the following attributes.
23
24 =head2 auto_validate_every ($seconds)
25
26 If 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
28 validating every query.
29
30 =cut
31
32 has 'auto_validate_every' => (
33     is=>'rw',
34     isa=>'Int',
35     predicate=>'had_auto_validate_every',
36 );
37
38 =head2 last_validated
39
40 This is an integer representing a time since the last time the replicants were
41 validated. It's nothing fancy, just an integer provided via the perl time 
42 builtin.
43
44 =cut
45
46 has '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
57 =head2 master
58
59 The L<DBIx::Class::Storage::DBI> object that is the master database all the
60 replicants are trying to follow.  The balancer needs to know it since it's the
61 ultimate fallback.
62
63 =cut
64
65 has 'master' => (
66     is=>'ro',
67     isa=>'DBIx::Class::Storage::DBI',
68     required=>1,
69 );
70
71 =head2 pool
72
73 The L<DBIx::Class::Storage::DBI::Replicated::Pool> object that we are trying to
74 balance.
75
76 =cut
77
78 has 'pool' => (
79     is=>'ro',
80     isa=>'DBIx::Class::Storage::DBI::Replicated::Pool',
81     required=>1,
82 );
83
84 =head2 current_replicant
85
86 Replicant storages (slaves) handle all read only traffic.  The assumption is
87 that your database will become readbound well before it becomes write bound
88 and that being able to spread your read only traffic around to multiple 
89 databases is going to help you to scale traffic.
90
91 This attribute returns the next slave to handle a read request.  Your L</pool>
92 attribute has methods to help you shuffle through all the available replicants
93 via it's balancer object.
94
95 =cut
96
97 has '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
108 =head1 METHODS
109
110 This class defines the following methods.
111
112 =head2 _build_current_replicant
113
114 Lazy builder for the L</current_replicant_storage> attribute.
115
116 =cut
117
118 sub _build_current_replicant {
119     my $self = shift @_;
120     $self->next_storage;
121 }
122
123 =head2 next_storage
124
125 Given a pool object, return the next replicant that will serve queries.  The
126 default behavior is to grap the first replicant it finds but you can write 
127 your own subclasses of L<DBIx::Class::Storage::DBI::Replicated::Balancer> to 
128 support other balance systems.
129
130 This returns from the pool of active replicants.  If there are no active
131 replicants, then you should have it return the master as an ultimate fallback.
132
133 TODO this needs to wrap for the subclasses better. Maybe good use of INNER?
134
135 =cut
136
137 sub next_storage {
138         my $self = shift @_;
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
150         my $next = ($self->pool->active_replicants)[0];
151         return $next ? $next:$self->master;
152 }
153
154 =head2 before: select
155
156 Advice on the select attribute.  Each time we use a replicant
157 we need to change it via the storage pool algorithm.  That way we are spreading
158 the load evenly (hopefully) across existing capacity.
159
160 =cut
161
162 before 'select' => sub {
163     my $self = shift @_;
164     my $next_replicant = $self->next_storage;
165     $self->current_replicant($next_replicant);
166 };
167
168 =head2 before: select_single
169
170 Advice on the select_single attribute.  Each time we use a replicant
171 we need to change it via the storage pool algorithm.  That way we are spreading
172 the load evenly (hopefully) across existing capacity.
173
174 =cut
175
176 before 'select_single' => sub {
177     my $self = shift @_;
178     my $next_replicant = $self->next_storage;
179     $self->current_replicant($next_replicant);
180 };
181
182 =head2 before: columns_info_for
183
184 Advice on the current_replicant_storage attribute.  Each time we use a replicant
185 we need to change it via the storage pool algorithm.  That way we are spreading
186 the load evenly (hopefully) across existing capacity.
187
188 =cut
189
190 before 'columns_info_for' => sub {
191     my $self = shift @_;
192     my $next_replicant = $self->next_storage;
193     $self->current_replicant($next_replicant);
194 };
195
196 =head1 AUTHOR
197
198 John Napiorkowski <john.napiorkowski@takkle.com>
199
200 =head1 LICENSE
201
202 You may distribute this code under the same terms as Perl itself.
203
204 =cut
205
206 1;