4f406c3333835b117efc06eea75424bdf69e174f
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Replicated / Pool.pm
1 package DBIx::Class::Storage::DBI::Replicated::Pool;
2
3 use Moose;
4 use MooseX::AttributeHelpers;
5 use DBIx::Class::Storage::DBI::Replicated::Replicant;
6 use List::Util qw(sum);
7
8 =head1 NAME
9
10 DBIx::Class::Storage::DBI::Replicated::Pool; Manage a pool of replicants
11
12 =head1 SYNOPSIS
13
14 This class is used internally by L<DBIx::Class::Storage::DBI::Replicated>.  You
15 shouldn't need to create instances of this class.
16     
17 =head1 DESCRIPTION
18
19 In a replicated storage type, there is at least one replicant to handle the
20 read only traffic.  The Pool class manages this replicant, or list of 
21 replicants, and gives some methods for querying information about their status.
22
23 =head1 ATTRIBUTES
24
25 This class defines the following attributes.
26
27 =head2 maximum_lag ($num)
28
29 This is a number which defines the maximum allowed lag returned by the
30 L<DBIx::Class::Storage::DBI/lag_behind_master> method.  The default is 0.  In
31 general, this should return a larger number when the replicant is lagging
32 behind it's master, however the implementation of this is database specific, so
33 don't count on this number having a fixed meaning.  For example, MySQL will
34 return a number of seconds that the replicating database is lagging.
35
36 =cut
37
38 has 'maximum_lag' => (
39     is=>'rw',
40     isa=>'Num',
41     required=>1,
42     lazy=>1,
43     default=>0,
44 );
45
46 =head2 replicant_type ($classname)
47
48 Base class used to instantiate replicants that are in the pool.  Unless you
49 need to subclass L<DBIx::Class::Storage::DBI::Replicated::Replicant> you should
50 just leave this alone.
51
52 =cut
53
54 has 'replicant_type' => (
55     is=>'ro',
56     isa=>'ClassName',
57     required=>1,
58     default=>'DBIx::Class::Storage::DBI',
59     handles=>{
60         'create_replicant' => 'new',
61     },  
62 );
63
64 =head2 replicants
65
66 A hashref of replicant, with the key being the dsn and the value returning the
67 actual replicant storage.  For example if the $dsn element is something like:
68
69     "dbi:SQLite:dbname=dbfile"
70     
71 You could access the specific replicant via:
72
73     $schema->storage->replicants->{'dbname=dbfile'}
74     
75 This attributes also supports the following helper methods
76
77 =over 4
78
79 =item set_replicant($key=>$storage)
80
81 Pushes a replicant onto the HashRef under $key
82
83 =item get_replicant($key)
84
85 Retrieves the named replicant
86
87 =item has_replicants
88
89 Returns true if the Pool defines replicants.
90
91 =item num_replicants
92
93 The number of replicants in the pool
94
95 =item delete_replicant ($key)
96
97 removes the replicant under $key from the pool
98
99 =back
100
101 =cut
102
103 has 'replicants' => (
104     is=>'rw',
105     metaclass => 'Collection::Hash',
106     isa=>'HashRef[DBIx::Class::Storage::DBI]',
107     default=>sub {{}},
108     provides  => {
109                 'set' => 'set_replicant',
110                 'get' => 'get_replicant',            
111                 'empty' => 'has_replicants',
112                 'count' => 'num_replicants',
113                 'delete' => 'delete_replicant',
114         },
115 );
116
117 =head1 METHODS
118
119 This class defines the following methods.
120
121 =head2 connect_replicants ($schema, Array[$connect_info])
122
123 Given an array of $dsn suitable for connected to a database, create an
124 L<DBIx::Class::Storage::DBI::Replicated::Replicant> object and store it in the
125 L</replicants> attribute.
126
127 =cut
128
129 sub connect_replicants {
130         my $self = shift @_;
131         my $schema = shift @_;
132         
133         my @newly_created = ();
134         foreach my $connect_info (@_) {
135                 
136                 my $replicant = $self->create_replicant($schema);
137                 $replicant->connect_info($connect_info);        
138                 $replicant->ensure_connected;
139                 DBIx::Class::Storage::DBI::Replicated::Replicant->meta->apply($replicant);
140                 
141                 my ($key) = ($connect_info->[0]=~m/^dbi\:.+\:(.+)$/);
142                 $self->set_replicant( $key => $replicant);      
143                 push @newly_created, $replicant;
144         }
145         
146         return @newly_created;
147 }
148
149 =head2 connected_replicants
150
151 Returns true if there are connected replicants.  Actually is overloaded to
152 return the number of replicants.  So you can do stuff like:
153
154     if( my $num_connected = $storage->has_connected_replicants ) {
155         print "I have $num_connected connected replicants";
156     } else {
157         print "Sorry, no replicants.";
158     }
159
160 This method will actually test that each replicant in the L</replicants> hashref
161 is actually connected, try not to hit this 10 times a second.
162
163 =cut
164
165 sub connected_replicants {
166         my $self = shift @_;
167         return sum( map {
168                 $_->connected ? 1:0
169         } $self->all_replicants );
170 }
171
172 =head2 active_replicants
173
174 This is an array of replicants that are considered to be active in the pool.
175 This does not check to see if they are connected, but if they are not, DBIC
176 should automatically reconnect them for us when we hit them with a query.
177
178 =cut
179
180 sub active_replicants {
181     my $self = shift @_;
182     return ( grep {$_} map {
183         $_->active ? $_:0
184     } $self->all_replicants );
185 }
186
187 =head2 all_replicants
188
189 Just a simple array of all the replicant storages.  No particular order to the
190 array is given, nor should any meaning be derived.
191
192 =cut
193
194 sub all_replicants {
195     my $self = shift @_;
196     return values %{$self->replicants};
197 }
198
199 =head2 validate_replicants
200
201 This does a check to see if 1) each replicate is connected (or reconnectable),
202 2) that is ->is_replicating, and 3) that it is not exceeding the lag amount
203 defined by L</maximum_lag>.  Replicants that fail any of these tests are set to
204 inactive, and thus removed from the replication pool.
205
206 This tests L<all_replicants>, since a replicant that has been previous marked
207 as inactive can be reactived should it start to pass the validation tests again.
208
209 See L<DBIx::Class::Storage::DBI> for more about checking if a replicating
210 connection is not following a master or is lagging.
211
212 Calling this method will generate queries on the replicant databases so it is
213 not recommended that you run them very often.
214
215 =cut
216
217 sub validate_replicants {
218     my $self = shift @_;
219     foreach my $replicant($self->all_replicants) {
220         if(
221             $replicant->is_replicating &&
222             $replicant->lag_behind_master <= $self->maximum_lag &&
223             $replicant->ensure_connected
224         ) {
225                 ## TODO:: Hook debug for this
226             $replicant->active(1)
227         } else {
228                 ## TODO:: Hook debug for this
229             $replicant->active(0);
230         }
231     }
232 }
233
234 =head1 AUTHOR
235
236 John Napiorkowski <john.napiorkowski@takkle.com>
237
238 =head1 LICENSE
239
240 You may distribute this code under the same terms as Perl itself.
241
242 =cut
243
244 1;