Introduce GOVERNANCE document and empty RESOLUTIONS file.
[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 Scalar::Util 'reftype';
0bd8e058 6use DBI ();
41916570 7use MooseX::Types::Moose qw/Num Int ClassName HashRef/;
cea43436 8use DBIx::Class::Storage::DBI::Replicated::Types 'DBICStorageDBI';
e2741c7f 9use DBIx::Class::_Util qw( dbic_internal_try dbic_internal_catch );
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
8273e845 25read-only traffic. The Pool class manages this replicant, or list of
26ab719a 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
faaba25f 37behind its master, however the implementation of this is database specific, so
4a607d7a 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
faaba25f 54validated. It's nothing fancy, just an integer provided via the perl L<time|perlfunc/time>
48580715 55built-in.
17b05c13 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',
8273e845 83 },
26ab719a 84);
85
26ab719a 86=head2 replicants
87
88A hashref of replicant, with the key being the dsn and the value returning the
48580715 89actual replicant storage. For example, if the $dsn element is something like:
26ab719a 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
48580715 119Removes the replicant under $key from the pool
26ab719a 120
121=back
122
123=cut
124
125has 'replicants' => (
64cdad22 126 is=>'rw',
c4d78acb 127 traits => ['Hash'],
071bbccb 128 isa=>HashRef['Object'],
64cdad22 129 default=>sub {{}},
c4d78acb 130 handles => {
131 'set_replicant' => 'set',
132 'get_replicant' => 'get',
133 'has_replicants' => 'is_empty',
134 'num_replicants' => 'count',
135 'delete_replicant' => 'delete',
136 'all_replicant_storages' => 'values',
64cdad22 137 },
26ab719a 138);
139
c4d78acb 140around has_replicants => sub {
141 my ($orig, $self) = @_;
142 return !$self->$orig;
143};
144
ede99b9f 145has next_unknown_replicant_id => (
146 is => 'rw',
c4d78acb 147 traits => ['Counter'],
ede99b9f 148 isa => Int,
149 default => 1,
c4d78acb 150 handles => {
151 'inc_unknown_replicant_id' => 'inc',
ede99b9f 152 },
153);
154
cea43436 155=head2 master
156
157Reference to the master Storage.
158
159=cut
160
161has master => (is => 'rw', isa => DBICStorageDBI, weak_ref => 1);
162
26ab719a 163=head1 METHODS
164
165This class defines the following methods.
166
955a6df6 167=head2 connect_replicants ($schema, Array[$connect_info])
26ab719a 168
d40080c3 169Given an array of $dsn or connect_info structures suitable for connected to a
170database, create an L<DBIx::Class::Storage::DBI::Replicated::Replicant> object
171and store it in the L</replicants> attribute.
26ab719a 172
173=cut
174
955a6df6 175sub connect_replicants {
64cdad22 176 my $self = shift @_;
177 my $schema = shift @_;
d4daee7b 178
64cdad22 179 my @newly_created = ();
180 foreach my $connect_info (@_) {
2cd3ccc4 181 $connect_info = [ $connect_info ]
9901aad7 182 if reftype $connect_info ne 'ARRAY';
183
0bd8e058 184 my $connect_coderef =
185 (reftype($connect_info->[0])||'') eq 'CODE' ? $connect_info->[0]
186 : (reftype($connect_info->[0])||'') eq 'HASH' &&
187 $connect_info->[0]->{dbh_maker};
188
189 my $dsn;
4c91f824 190 my $replicant = do {
ede99b9f 191# yes this is evil, but it only usually happens once (for coderefs)
192# this will fail if the coderef does not actually DBI::connect
0bd8e058 193 no warnings 'redefine';
194 my $connect = \&DBI::connect;
195 local *DBI::connect = sub {
196 $dsn = $_[1];
197 goto $connect;
198 };
4c91f824 199 $self->connect_replicant($schema, $connect_info);
200 };
201
ede99b9f 202 my $key;
203
204 if (!$dsn) {
205 if (!$connect_coderef) {
206 $dsn = $connect_info->[0];
207 $dsn = $dsn->{dsn} if (reftype($dsn)||'') eq 'HASH';
208 }
209 else {
210 # all attempts to get the DSN failed
211 $key = "UNKNOWN_" . $self->next_unknown_replicant_id;
212 $self->inc_unknown_replicant_id;
213 }
214 }
215 if ($dsn) {
216 $replicant->dsn($dsn);
217 ($key) = ($dsn =~ m/^dbi\:.+\:(.+)$/i);
0bd8e058 218 }
0bd8e058 219
ede99b9f 220 $replicant->id($key);
8273e845 221 $self->set_replicant($key => $replicant);
ede99b9f 222
64cdad22 223 push @newly_created, $replicant;
224 }
d4daee7b 225
64cdad22 226 return @newly_created;
26ab719a 227}
228
bbafcf26 229=head2 connect_replicant ($schema, $connect_info)
230
231Given a schema object and a hashref of $connect_info, connect the replicant
232and return it.
233
234=cut
235
236sub connect_replicant {
237 my ($self, $schema, $connect_info) = @_;
238 my $replicant = $self->create_replicant($schema);
f15afa13 239 $replicant->connect_info($connect_info);
d40080c3 240
241## It is undesirable for catalyst to connect at ->conect_replicants time, as
242## connections should only happen on the first request that uses the database.
243## So we try to set the driver without connecting, however this doesn't always
244## work, as a driver may need to connect to determine the DB version, and this
245## may fail.
d6e80959 246##
247## Why this is necessary at all, is that we need to have the final storage
248## class to apply the Replicant role.
d40080c3 249
250 $self->_safely($replicant, '->_determine_driver', sub {
251 $replicant->_determine_driver
252 });
253
cea43436 254 Moose::Meta::Class->initialize(ref $replicant);
255
ec0946db 256 DBIx::Class::Storage::DBI::Replicated::Replicant->meta->apply($replicant);
cea43436 257
258 # link back to master
259 $replicant->master($self->master);
260
bbafcf26 261 return $replicant;
262}
263
f15afa13 264=head2 _safely_ensure_connected ($replicant)
265
266The standard ensure_connected method with throw an exception should it fail to
267connect. For the master database this is desirable, but since replicants are
268allowed to fail, this behavior is not desirable. This method wraps the call
269to ensure_connected in an eval in order to catch any generated errors. That
48580715 270way a slave can go completely offline (e.g. the box itself can die) without
f15afa13 271bringing down your entire pool of databases.
272
273=cut
274
275sub _safely_ensure_connected {
276 my ($self, $replicant, @args) = @_;
d40080c3 277
278 return $self->_safely($replicant, '->ensure_connected', sub {
279 $replicant->ensure_connected(@args)
280 });
281}
282
283=head2 _safely ($replicant, $name, $code)
284
285Execute C<$code> for operation C<$name> catching any exceptions and printing an
286error message to the C<<$replicant->debugobj>>.
287
288Returns 1 on success and undef on failure.
289
290=cut
291
292sub _safely {
293 my ($self, $replicant, $name, $code) = @_;
294
e2741c7f 295 dbic_internal_try {
52b420dd 296 $code->();
297 1;
e2741c7f 298 }
299 dbic_internal_catch {
d7a58a29 300 $replicant->debugobj->print(sprintf(
301 "Exception trying to $name for replicant %s, error is %s",
9780718f 302 $replicant->_dbi_connect_info->[0], $_)
d7a58a29 303 );
e2741c7f 304
305 # rv
52b420dd 306 undef;
ed7ab0f4 307 };
f15afa13 308}
309
26ab719a 310=head2 connected_replicants
311
312Returns true if there are connected replicants. Actually is overloaded to
313return the number of replicants. So you can do stuff like:
314
64cdad22 315 if( my $num_connected = $storage->has_connected_replicants ) {
316 print "I have $num_connected connected replicants";
317 } else {
318 print "Sorry, no replicants.";
319 }
26ab719a 320
321This method will actually test that each replicant in the L</replicants> hashref
322is actually connected, try not to hit this 10 times a second.
323
324=cut
325
326sub connected_replicants {
87b12551 327 return scalar grep
328 { $_->connected }
329 shift->all_replicants
330 ;
26ab719a 331}
332
50336325 333=head2 active_replicants
334
335This is an array of replicants that are considered to be active in the pool.
336This does not check to see if they are connected, but if they are not, DBIC
337should automatically reconnect them for us when we hit them with a query.
338
339=cut
340
341sub active_replicants {
64cdad22 342 my $self = shift @_;
343 return ( grep {$_} map {
344 $_->active ? $_:0
345 } $self->all_replicants );
50336325 346}
347
26ab719a 348=head2 all_replicants
349
350Just a simple array of all the replicant storages. No particular order to the
351array is given, nor should any meaning be derived.
352
353=cut
354
355sub all_replicants {
64cdad22 356 my $self = shift @_;
357 return values %{$self->replicants};
26ab719a 358}
359
4a607d7a 360=head2 validate_replicants
361
362This does a check to see if 1) each replicate is connected (or reconnectable),
3632) that is ->is_replicating, and 3) that it is not exceeding the lag amount
364defined by L</maximum_lag>. Replicants that fail any of these tests are set to
365inactive, and thus removed from the replication pool.
366
f92a9d79 367This tests L</all_replicants>, since a replicant that has been previous marked
48580715 368as inactive can be reactivated should it start to pass the validation tests again.
4a607d7a 369
370See L<DBIx::Class::Storage::DBI> for more about checking if a replicating
371connection is not following a master or is lagging.
372
373Calling this method will generate queries on the replicant databases so it is
374not recommended that you run them very often.
375
13b9e828 376This method requires that your underlying storage engine supports some sort of
377native replication mechanism. Currently only MySQL native replication is
378supported. Your patches to make other replication types work are welcomed.
379
4a607d7a 380=cut
381
382sub validate_replicants {
64cdad22 383 my $self = shift @_;
384 foreach my $replicant($self->all_replicants) {
13b9e828 385 if($self->_safely_ensure_connected($replicant)) {
386 my $is_replicating = $replicant->is_replicating;
387 unless(defined $is_replicating) {
9901aad7 388 $replicant->debugobj->print("Storage Driver ".ref($self)." Does not support the 'is_replicating' method. Assuming you are manually managing.\n");
13b9e828 389 next;
390 } else {
391 if($is_replicating) {
392 my $lag_behind_master = $replicant->lag_behind_master;
393 unless(defined $lag_behind_master) {
9901aad7 394 $replicant->debugobj->print("Storage Driver ".ref($self)." Does not support the 'lag_behind_master' method. Assuming you are manually managing.\n");
13b9e828 395 next;
396 } else {
397 if($lag_behind_master <= $self->maximum_lag) {
398 $replicant->active(1);
399 } else {
8273e845 400 $replicant->active(0);
13b9e828 401 }
8273e845 402 }
13b9e828 403 } else {
404 $replicant->active(0);
405 }
406 }
64cdad22 407 } else {
64cdad22 408 $replicant->active(0);
7edf5f1c 409 }
64cdad22 410 }
8273e845 411 ## Mark that we completed this validation.
412 $self->_last_validated(time);
4a607d7a 413}
414
a2bd3796 415=head1 FURTHER QUESTIONS?
26ab719a 416
a2bd3796 417Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
26ab719a 418
a2bd3796 419=head1 COPYRIGHT AND LICENSE
26ab719a 420
a2bd3796 421This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
422by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
423redistribute it and/or modify it under the same terms as the
424L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.
26ab719a 425
426=cut
427
c354902c 428__PACKAGE__->meta->make_immutable;
429
cb6ec758 4301;