fix master debug output for ::Replicated
[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.
64cdad22 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"
92
26ab719a 93You could access the specific replicant via:
94
64cdad22 95 $schema->storage->replicants->{'dbname=dbfile'}
96
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',
41916570 128 isa=>HashRef['DBIx::Class::Storage::DBI'],
64cdad22 129 default=>sub {{}},
130 provides => {
131 'set' => 'set_replicant',
132 'get' => 'get_replicant',
133 'empty' => 'has_replicants',
134 'count' => 'num_replicants',
135 'delete' => 'delete_replicant',
136 },
26ab719a 137);
138
26ab719a 139=head1 METHODS
140
141This class defines the following methods.
142
955a6df6 143=head2 connect_replicants ($schema, Array[$connect_info])
26ab719a 144
d40080c3 145Given an array of $dsn or connect_info structures suitable for connected to a
146database, create an L<DBIx::Class::Storage::DBI::Replicated::Replicant> object
147and store it in the L</replicants> attribute.
26ab719a 148
149=cut
150
955a6df6 151sub connect_replicants {
64cdad22 152 my $self = shift @_;
153 my $schema = shift @_;
154
155 my @newly_created = ();
156 foreach my $connect_info (@_) {
2cd3ccc4 157 $connect_info = [ $connect_info ]
9901aad7 158 if reftype $connect_info ne 'ARRAY';
159
b2e4d522 160 croak "coderef replicant connect_info not supported"
9901aad7 161 if ref $connect_info->[0] && reftype $connect_info->[0] eq 'CODE';
2cd3ccc4 162
bbafcf26 163 my $replicant = $self->connect_replicant($schema, $connect_info);
2cd3ccc4 164
165 my $key = $connect_info->[0];
9901aad7 166 $key = $key->{dsn} if ref $key && reftype $key eq 'HASH';
2cd3ccc4 167 ($key) = ($key =~ m/^dbi\:.+\:(.+)$/);
168
64cdad22 169 $self->set_replicant( $key => $replicant);
170 push @newly_created, $replicant;
171 }
172
173 return @newly_created;
26ab719a 174}
175
bbafcf26 176=head2 connect_replicant ($schema, $connect_info)
177
178Given a schema object and a hashref of $connect_info, connect the replicant
179and return it.
180
181=cut
182
183sub connect_replicant {
184 my ($self, $schema, $connect_info) = @_;
185 my $replicant = $self->create_replicant($schema);
f15afa13 186 $replicant->connect_info($connect_info);
d40080c3 187
188## It is undesirable for catalyst to connect at ->conect_replicants time, as
189## connections should only happen on the first request that uses the database.
190## So we try to set the driver without connecting, however this doesn't always
191## work, as a driver may need to connect to determine the DB version, and this
192## may fail.
d6e80959 193##
194## Why this is necessary at all, is that we need to have the final storage
195## class to apply the Replicant role.
d40080c3 196
197 $self->_safely($replicant, '->_determine_driver', sub {
198 $replicant->_determine_driver
199 });
200
f15afa13 201 DBIx::Class::Storage::DBI::Replicated::Replicant->meta->apply($replicant);
bbafcf26 202 return $replicant;
203}
204
f15afa13 205=head2 _safely_ensure_connected ($replicant)
206
207The standard ensure_connected method with throw an exception should it fail to
208connect. For the master database this is desirable, but since replicants are
209allowed to fail, this behavior is not desirable. This method wraps the call
210to ensure_connected in an eval in order to catch any generated errors. That
d40080c3 211way a slave can go completely offline (ie, the box itself can die) without
f15afa13 212bringing down your entire pool of databases.
213
214=cut
215
216sub _safely_ensure_connected {
217 my ($self, $replicant, @args) = @_;
d40080c3 218
219 return $self->_safely($replicant, '->ensure_connected', sub {
220 $replicant->ensure_connected(@args)
221 });
222}
223
224=head2 _safely ($replicant, $name, $code)
225
226Execute C<$code> for operation C<$name> catching any exceptions and printing an
227error message to the C<<$replicant->debugobj>>.
228
229Returns 1 on success and undef on failure.
230
231=cut
232
233sub _safely {
234 my ($self, $replicant, $name, $code) = @_;
235
6ffb5be5 236 eval {
d40080c3 237 $code->()
6ffb5be5 238 };
239 if ($@) {
13b9e828 240 $replicant
6ffb5be5 241 ->debugobj
242 ->print(
d40080c3 243 sprintf( "Exception trying to $name for replicant %s, error is %s",
6ffb5be5 244 $replicant->_dbi_connect_info->[0], $@)
13b9e828 245 );
6ffb5be5 246 return;
13b9e828 247 }
6ffb5be5 248 return 1;
f15afa13 249}
250
26ab719a 251=head2 connected_replicants
252
253Returns true if there are connected replicants. Actually is overloaded to
254return the number of replicants. So you can do stuff like:
255
64cdad22 256 if( my $num_connected = $storage->has_connected_replicants ) {
257 print "I have $num_connected connected replicants";
258 } else {
259 print "Sorry, no replicants.";
260 }
26ab719a 261
262This method will actually test that each replicant in the L</replicants> hashref
263is actually connected, try not to hit this 10 times a second.
264
265=cut
266
267sub connected_replicants {
64cdad22 268 my $self = shift @_;
269 return sum( map {
270 $_->connected ? 1:0
271 } $self->all_replicants );
26ab719a 272}
273
50336325 274=head2 active_replicants
275
276This is an array of replicants that are considered to be active in the pool.
277This does not check to see if they are connected, but if they are not, DBIC
278should automatically reconnect them for us when we hit them with a query.
279
280=cut
281
282sub active_replicants {
64cdad22 283 my $self = shift @_;
284 return ( grep {$_} map {
285 $_->active ? $_:0
286 } $self->all_replicants );
50336325 287}
288
26ab719a 289=head2 all_replicants
290
291Just a simple array of all the replicant storages. No particular order to the
292array is given, nor should any meaning be derived.
293
294=cut
295
296sub all_replicants {
64cdad22 297 my $self = shift @_;
298 return values %{$self->replicants};
26ab719a 299}
300
4a607d7a 301=head2 validate_replicants
302
303This does a check to see if 1) each replicate is connected (or reconnectable),
3042) that is ->is_replicating, and 3) that it is not exceeding the lag amount
305defined by L</maximum_lag>. Replicants that fail any of these tests are set to
306inactive, and thus removed from the replication pool.
307
308This tests L<all_replicants>, since a replicant that has been previous marked
309as inactive can be reactived should it start to pass the validation tests again.
310
311See L<DBIx::Class::Storage::DBI> for more about checking if a replicating
312connection is not following a master or is lagging.
313
314Calling this method will generate queries on the replicant databases so it is
315not recommended that you run them very often.
316
13b9e828 317This method requires that your underlying storage engine supports some sort of
318native replication mechanism. Currently only MySQL native replication is
319supported. Your patches to make other replication types work are welcomed.
320
4a607d7a 321=cut
322
323sub validate_replicants {
64cdad22 324 my $self = shift @_;
325 foreach my $replicant($self->all_replicants) {
13b9e828 326 if($self->_safely_ensure_connected($replicant)) {
327 my $is_replicating = $replicant->is_replicating;
328 unless(defined $is_replicating) {
9901aad7 329 $replicant->debugobj->print("Storage Driver ".ref($self)." Does not support the 'is_replicating' method. Assuming you are manually managing.\n");
13b9e828 330 next;
331 } else {
332 if($is_replicating) {
333 my $lag_behind_master = $replicant->lag_behind_master;
334 unless(defined $lag_behind_master) {
9901aad7 335 $replicant->debugobj->print("Storage Driver ".ref($self)." Does not support the 'lag_behind_master' method. Assuming you are manually managing.\n");
13b9e828 336 next;
337 } else {
338 if($lag_behind_master <= $self->maximum_lag) {
339 $replicant->active(1);
340 } else {
341 $replicant->active(0);
342 }
343 }
344 } else {
345 $replicant->active(0);
346 }
347 }
64cdad22 348 } else {
64cdad22 349 $replicant->active(0);
7edf5f1c 350 }
64cdad22 351 }
352 ## Mark that we completed this validation.
13b9e828 353 $self->_last_validated(time);
4a607d7a 354}
355
26ab719a 356=head1 AUTHOR
357
358John Napiorkowski <john.napiorkowski@takkle.com>
359
360=head1 LICENSE
361
362You may distribute this code under the same terms as Perl itself.
363
364=cut
365
c354902c 366__PACKAGE__->meta->make_immutable;
367
cb6ec758 3681;