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