A couple of typos, and general whitespace cleanup (ick)
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Replicated / Pool.pm
CommitLineData
26ab719a 1package DBIx::Class::Storage::DBI::Replicated::Pool;
2
3use Moose;
4use MooseX::AttributeHelpers;
5use DBIx::Class::Storage::DBI::Replicated::Replicant;
9901aad7 6use List::Util 'sum';
7use Scalar::Util 'reftype';
8use Carp::Clan qw/^DBIx::Class/;
41916570 9use MooseX::Types::Moose qw/Num Int ClassName HashRef/;
9901aad7 10
11use namespace::clean -except => 'meta';
26ab719a 12
13=head1 NAME
14
21fc4719 15DBIx::Class::Storage::DBI::Replicated::Pool - Manage a pool of replicants
26ab719a 16
17=head1 SYNOPSIS
18
19This class is used internally by L<DBIx::Class::Storage::DBI::Replicated>. You
20shouldn't need to create instances of this class.
d4daee7b 21
26ab719a 22=head1 DESCRIPTION
23
24In a replicated storage type, there is at least one replicant to handle the
25read only traffic. The Pool class manages this replicant, or list of
26replicants, and gives some methods for querying information about their status.
27
28=head1 ATTRIBUTES
29
30This class defines the following attributes.
31
4a607d7a 32=head2 maximum_lag ($num)
33
34This is a number which defines the maximum allowed lag returned by the
35L<DBIx::Class::Storage::DBI/lag_behind_master> method. The default is 0. In
36general, this should return a larger number when the replicant is lagging
37behind it's master, however the implementation of this is database specific, so
38don't count on this number having a fixed meaning. For example, MySQL will
39return a number of seconds that the replicating database is lagging.
40
41=cut
42
43has 'maximum_lag' => (
64cdad22 44 is=>'rw',
41916570 45 isa=>Num,
64cdad22 46 required=>1,
47 lazy=>1,
48 default=>0,
4a607d7a 49);
50
17b05c13 51=head2 last_validated
52
53This is an integer representing a time since the last time the replicants were
54validated. It's nothing fancy, just an integer provided via the perl time
55builtin.
56
57=cut
58
59has 'last_validated' => (
64cdad22 60 is=>'rw',
41916570 61 isa=>Int,
64cdad22 62 reader=>'last_validated',
63 writer=>'_last_validated',
64 lazy=>1,
65 default=>0,
17b05c13 66);
67
4a607d7a 68=head2 replicant_type ($classname)
26ab719a 69
70Base class used to instantiate replicants that are in the pool. Unless you
71need to subclass L<DBIx::Class::Storage::DBI::Replicated::Replicant> you should
72just leave this alone.
73
74=cut
75
76has 'replicant_type' => (
64cdad22 77 is=>'ro',
41916570 78 isa=>ClassName,
64cdad22 79 required=>1,
80 default=>'DBIx::Class::Storage::DBI',
81 handles=>{
82 'create_replicant' => 'new',
83 },
26ab719a 84);
85
26ab719a 86=head2 replicants
87
88A hashref of replicant, with the key being the dsn and the value returning the
89actual replicant storage. For example if the $dsn element is something like:
90
64cdad22 91 "dbi:SQLite:dbname=dbfile"
d4daee7b 92
26ab719a 93You could access the specific replicant via:
94
64cdad22 95 $schema->storage->replicants->{'dbname=dbfile'}
d4daee7b 96
64cdad22 97This attributes also supports the following helper methods:
26ab719a 98
99=over 4
100
101=item set_replicant($key=>$storage)
102
103Pushes a replicant onto the HashRef under $key
104
105=item get_replicant($key)
106
107Retrieves the named replicant
108
109=item has_replicants
110
111Returns true if the Pool defines replicants.
112
113=item num_replicants
114
115The number of replicants in the pool
116
117=item delete_replicant ($key)
118
119removes the replicant under $key from the pool
120
121=back
122
123=cut
124
125has 'replicants' => (
64cdad22 126 is=>'rw',
127 metaclass => 'Collection::Hash',
071bbccb 128 isa=>HashRef['Object'],
64cdad22 129 default=>sub {{}},
130 provides => {
131 'set' => 'set_replicant',
d4daee7b 132 'get' => 'get_replicant',
64cdad22 133 'empty' => 'has_replicants',
134 'count' => 'num_replicants',
135 'delete' => 'delete_replicant',
d4daee7b 136 'values' => 'all_replicant_storages',
64cdad22 137 },
26ab719a 138);
139
26ab719a 140=head1 METHODS
141
142This class defines the following methods.
143
955a6df6 144=head2 connect_replicants ($schema, Array[$connect_info])
26ab719a 145
d40080c3 146Given an array of $dsn or connect_info structures suitable for connected to a
147database, create an L<DBIx::Class::Storage::DBI::Replicated::Replicant> object
148and store it in the L</replicants> attribute.
26ab719a 149
150=cut
151
955a6df6 152sub connect_replicants {
64cdad22 153 my $self = shift @_;
154 my $schema = shift @_;
d4daee7b 155
64cdad22 156 my @newly_created = ();
157 foreach my $connect_info (@_) {
2cd3ccc4 158 $connect_info = [ $connect_info ]
9901aad7 159 if reftype $connect_info ne 'ARRAY';
160
b2e4d522 161 croak "coderef replicant connect_info not supported"
9901aad7 162 if ref $connect_info->[0] && reftype $connect_info->[0] eq 'CODE';
2cd3ccc4 163
bbafcf26 164 my $replicant = $self->connect_replicant($schema, $connect_info);
2cd3ccc4 165
166 my $key = $connect_info->[0];
9901aad7 167 $key = $key->{dsn} if ref $key && reftype $key eq 'HASH';
2cd3ccc4 168 ($key) = ($key =~ m/^dbi\:.+\:(.+)$/);
169
64cdad22 170 $self->set_replicant( $key => $replicant);
171 push @newly_created, $replicant;
172 }
d4daee7b 173
64cdad22 174 return @newly_created;
26ab719a 175}
176
bbafcf26 177=head2 connect_replicant ($schema, $connect_info)
178
179Given a schema object and a hashref of $connect_info, connect the replicant
180and return it.
181
182=cut
183
184sub connect_replicant {
185 my ($self, $schema, $connect_info) = @_;
186 my $replicant = $self->create_replicant($schema);
f15afa13 187 $replicant->connect_info($connect_info);
d40080c3 188
189## It is undesirable for catalyst to connect at ->conect_replicants time, as
190## connections should only happen on the first request that uses the database.
191## So we try to set the driver without connecting, however this doesn't always
192## work, as a driver may need to connect to determine the DB version, and this
193## may fail.
d6e80959 194##
195## Why this is necessary at all, is that we need to have the final storage
196## class to apply the Replicant role.
d40080c3 197
198 $self->_safely($replicant, '->_determine_driver', sub {
199 $replicant->_determine_driver
200 });
201
f15afa13 202 DBIx::Class::Storage::DBI::Replicated::Replicant->meta->apply($replicant);
bbafcf26 203 return $replicant;
204}
205
f15afa13 206=head2 _safely_ensure_connected ($replicant)
207
208The standard ensure_connected method with throw an exception should it fail to
209connect. For the master database this is desirable, but since replicants are
210allowed to fail, this behavior is not desirable. This method wraps the call
211to ensure_connected in an eval in order to catch any generated errors. That
d40080c3 212way a slave can go completely offline (ie, the box itself can die) without
f15afa13 213bringing down your entire pool of databases.
214
215=cut
216
217sub _safely_ensure_connected {
218 my ($self, $replicant, @args) = @_;
d40080c3 219
220 return $self->_safely($replicant, '->ensure_connected', sub {
221 $replicant->ensure_connected(@args)
222 });
223}
224
225=head2 _safely ($replicant, $name, $code)
226
227Execute C<$code> for operation C<$name> catching any exceptions and printing an
228error message to the C<<$replicant->debugobj>>.
229
230Returns 1 on success and undef on failure.
231
232=cut
233
234sub _safely {
235 my ($self, $replicant, $name, $code) = @_;
236
6ffb5be5 237 eval {
d40080c3 238 $code->()
6ffb5be5 239 };
240 if ($@) {
13b9e828 241 $replicant
6ffb5be5 242 ->debugobj
243 ->print(
d40080c3 244 sprintf( "Exception trying to $name for replicant %s, error is %s",
6ffb5be5 245 $replicant->_dbi_connect_info->[0], $@)
13b9e828 246 );
6ffb5be5 247 return;
13b9e828 248 }
6ffb5be5 249 return 1;
f15afa13 250}
251
26ab719a 252=head2 connected_replicants
253
254Returns true if there are connected replicants. Actually is overloaded to
255return the number of replicants. So you can do stuff like:
256
64cdad22 257 if( my $num_connected = $storage->has_connected_replicants ) {
258 print "I have $num_connected connected replicants";
259 } else {
260 print "Sorry, no replicants.";
261 }
26ab719a 262
263This method will actually test that each replicant in the L</replicants> hashref
264is actually connected, try not to hit this 10 times a second.
265
266=cut
267
268sub connected_replicants {
64cdad22 269 my $self = shift @_;
270 return sum( map {
271 $_->connected ? 1:0
272 } $self->all_replicants );
26ab719a 273}
274
50336325 275=head2 active_replicants
276
277This is an array of replicants that are considered to be active in the pool.
278This does not check to see if they are connected, but if they are not, DBIC
279should automatically reconnect them for us when we hit them with a query.
280
281=cut
282
283sub active_replicants {
64cdad22 284 my $self = shift @_;
285 return ( grep {$_} map {
286 $_->active ? $_:0
287 } $self->all_replicants );
50336325 288}
289
26ab719a 290=head2 all_replicants
291
292Just a simple array of all the replicant storages. No particular order to the
293array is given, nor should any meaning be derived.
294
295=cut
296
297sub all_replicants {
64cdad22 298 my $self = shift @_;
299 return values %{$self->replicants};
26ab719a 300}
301
4a607d7a 302=head2 validate_replicants
303
304This does a check to see if 1) each replicate is connected (or reconnectable),
3052) that is ->is_replicating, and 3) that it is not exceeding the lag amount
306defined by L</maximum_lag>. Replicants that fail any of these tests are set to
307inactive, and thus removed from the replication pool.
308
309This tests L<all_replicants>, since a replicant that has been previous marked
310as inactive can be reactived should it start to pass the validation tests again.
311
312See L<DBIx::Class::Storage::DBI> for more about checking if a replicating
313connection is not following a master or is lagging.
314
315Calling this method will generate queries on the replicant databases so it is
316not recommended that you run them very often.
317
13b9e828 318This method requires that your underlying storage engine supports some sort of
319native replication mechanism. Currently only MySQL native replication is
320supported. Your patches to make other replication types work are welcomed.
321
4a607d7a 322=cut
323
324sub validate_replicants {
64cdad22 325 my $self = shift @_;
326 foreach my $replicant($self->all_replicants) {
13b9e828 327 if($self->_safely_ensure_connected($replicant)) {
328 my $is_replicating = $replicant->is_replicating;
329 unless(defined $is_replicating) {
9901aad7 330 $replicant->debugobj->print("Storage Driver ".ref($self)." Does not support the 'is_replicating' method. Assuming you are manually managing.\n");
13b9e828 331 next;
332 } else {
333 if($is_replicating) {
334 my $lag_behind_master = $replicant->lag_behind_master;
335 unless(defined $lag_behind_master) {
9901aad7 336 $replicant->debugobj->print("Storage Driver ".ref($self)." Does not support the 'lag_behind_master' method. Assuming you are manually managing.\n");
13b9e828 337 next;
338 } else {
339 if($lag_behind_master <= $self->maximum_lag) {
340 $replicant->active(1);
341 } else {
342 $replicant->active(0);
343 }
344 }
345 } else {
346 $replicant->active(0);
347 }
348 }
64cdad22 349 } else {
64cdad22 350 $replicant->active(0);
7edf5f1c 351 }
64cdad22 352 }
353 ## Mark that we completed this validation.
13b9e828 354 $self->_last_validated(time);
4a607d7a 355}
356
26ab719a 357=head1 AUTHOR
358
359John Napiorkowski <john.napiorkowski@takkle.com>
360
361=head1 LICENSE
362
363You may distribute this code under the same terms as Perl itself.
364
365=cut
366
c354902c 367__PACKAGE__->meta->make_immutable;
368
cb6ec758 3691;