refactored the duties of the different balancer classes, added tests and docs
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Replicated.pm
1 package DBIx::Class::Storage::DBI::Replicated;
2
3 use Moose;
4 use DBIx::Class::Storage::DBI;
5 use DBIx::Class::Storage::DBI::Replicated::Pool;
6 use DBIx::Class::Storage::DBI::Replicated::Balancer;
7 use Scalar::Util qw(blessed);
8
9 extends 'DBIx::Class::Storage::DBI', 'Moose::Object';
10
11 =head1 NAME
12
13 DBIx::Class::Storage::DBI::Replicated - ALPHA Replicated database support
14
15 =head1 SYNOPSIS
16
17 The Following example shows how to change an existing $schema to a replicated
18 storage type, add some replicated (readonly) databases, and perform reporting
19 tasks.
20
21     ## Change storage_type in your schema class
22     $schema->storage_type( '::DBI::Replicated' );
23     
24     ## Add some slaves.  Basically this is an array of arrayrefs, where each
25     ## arrayref is database connect information
26     
27     $schema->storage->connect_replicants(
28         [$dsn1, $user, $pass, \%opts],
29         [$dsn1, $user, $pass, \%opts],
30         [$dsn1, $user, $pass, \%opts],
31     );
32     
33 =head1 DESCRIPTION
34
35 Warning: This class is marked ALPHA.  We are using this in development and have
36 some basic test coverage but the code hasn't yet been stressed by a variety
37 of databases.  Individual DB's may have quirks we are not aware of.  Please
38 use this in development and pass along your experiences/bug fixes.
39
40 This class implements replicated data store for DBI. Currently you can define
41 one master and numerous slave database connections. All write-type queries
42 (INSERT, UPDATE, DELETE and even LAST_INSERT_ID) are routed to master
43 database, all read-type queries (SELECTs) go to the slave database.
44
45 Basically, any method request that L<DBIx::Class::Storage::DBI> would normally
46 handle gets delegated to one of the two attributes: L</master_storage> or to
47 L</current_replicant_storage>.  Additionally, some methods need to be distributed
48 to all existing storages.  This way our storage class is a drop in replacement
49 for L<DBIx::Class::Storage::DBI>.
50
51 Read traffic is spread across the replicants (slaves) occuring to a user
52 selected algorithm.  The default algorithm is random weighted.
53
54 TODO more details about the algorithm.
55
56 =head1 ATTRIBUTES
57
58 This class defines the following attributes.
59
60 =head2 pool_type
61
62 Contains the classname which will instantiate the L</pool> object.  Defaults 
63 to: L<DBIx::Class::Storage::DBI::Replicated::Pool>.
64
65 =cut
66
67 has 'pool_type' => (
68     is=>'ro',
69     isa=>'ClassName',
70     required=>1,
71     lazy=>1,
72     default=>'DBIx::Class::Storage::DBI::Replicated::Pool',
73     handles=>{
74         'create_pool' => 'new',
75     },
76 );
77
78
79 =head2 balancer_type
80
81 The replication pool requires a balance class to provider the methods for
82 choose how to spread the query load across each replicant in the pool.
83
84 =cut
85
86 has 'balancer_type' => (
87     is=>'ro',
88     isa=>'ClassName',
89     required=>1,
90     lazy=>1,
91     default=>'DBIx::Class::Storage::DBI::Replicated::Balancer',
92     handles=>{
93         'create_balancer' => 'new',
94     },
95 );
96
97
98 =head2 pool
99
100 Is a <DBIx::Class::Storage::DBI::Replicated::Pool> or derived class.  This is a
101 container class for one or more replicated databases.
102
103 =cut
104
105 has 'pool' => (
106     is=>'ro',
107     isa=>'DBIx::Class::Storage::DBI::Replicated::Pool',
108     lazy_build=>1,
109     handles=>[qw/
110         connect_replicants    
111         replicants
112         has_replicants
113         num_replicants
114         delete_replicant
115     /],
116 );
117
118
119 =head2 balancer
120
121 Is a <DBIx::Class::Storage::DBI::Replicated::Balancer> or derived class.  This 
122 is a class that takes a pool (<DBIx::Class::Storage::DBI::Replicated::Pool>)
123
124 =cut
125
126 has 'balancer' => (
127     is=>'ro',
128     isa=>'DBIx::Class::Storage::DBI::Replicated::Balancer',
129     lazy_build=>1,
130 );
131
132
133 =head2 master
134
135 The master defines the canonical state for a pool of connected databases.  All
136 the replicants are expected to match this databases state.  Thus, in a classic
137 Master / Slaves distributed system, all the slaves are expected to replicate
138 the Master's state as quick as possible.  This is the only database in the
139 pool of databases that is allowed to handle write traffic.
140
141 =cut
142
143 has 'master' => (
144     is=> 'ro',
145     isa=>'DBIx::Class::Storage::DBI',
146     lazy_build=>1,
147 );
148
149
150 =head1 ATTRIBUTES IMPLEMENTING THE DBIx::Storage::DBI INTERFACE
151
152 The following methods are delegated all the methods required for the 
153 L<DBIx::Class::Storage::DBI> interface.
154
155 =head2 read_handler
156
157 Defines an object that implements the read side of L<BIx::Class::Storage::DBI>.
158
159 =cut
160
161 has 'read_handler' => (
162     is=>'rw',
163     isa=>'Object',
164     lazy_build=>1,
165     handles=>[qw/
166         select
167         select_single
168         columns_info_for
169     /],    
170 );
171
172
173 =head2 write_handler
174
175 Defines an object that implements the write side of L<BIx::Class::Storage::DBI>.
176
177 =cut
178
179 has 'write_handler' => (
180     is=>'ro',
181     isa=>'Object',
182     lazy_build=>1,
183     lazy_build=>1,
184     handles=>[qw/   
185         on_connect_do
186         on_disconnect_do       
187         connect_info
188         throw_exception
189         sql_maker
190         sqlt_type
191         create_ddl_dir
192         deployment_statements
193         datetime_parser
194         datetime_parser_type        
195         last_insert_id
196         insert
197         insert_bulk
198         update
199         delete
200         dbh
201         txn_do
202         txn_commit
203         txn_rollback
204         sth
205         deploy
206         schema
207     /],
208 );
209
210
211 =head1 METHODS
212
213 This class defines the following methods.
214
215 =head2 new
216
217 L<DBIx::Class::Schema> when instantiating it's storage passed itself as the
218 first argument.  We need to invoke L</new> on the underlying parent class, make
219 sure we properly give it a L<Moose> meta class, and then correctly instantiate
220 our attributes.  Basically we pass on whatever the schema has in it's class
221 data for 'storage_type_args' to our replicated storage type.
222
223 =cut
224
225 sub new {
226     my $class = shift @_;
227     my $schema = shift @_;
228     my $storage_type_args = shift @_;
229     my $obj = $class->SUPER::new($schema, $storage_type_args, @_);
230   
231     return $class->meta->new_object(
232         __INSTANCE__ => $obj,
233         %$storage_type_args,
234         @_,
235     );
236 }
237
238 =head2 _build_master
239
240 Lazy builder for the L</master> attribute.
241
242 =cut
243
244 sub _build_master {
245         DBIx::Class::Storage::DBI->new;
246 }
247
248 =head2 _build_pool
249
250 Lazy builder for the L</pool> attribute.
251
252 =cut
253
254 sub _build_pool {
255     shift->create_pool;
256 }
257
258 =head2 _build_balancer
259
260 Lazy builder for the L</balancer> attribute.  This takes a Pool object so that
261 the balancer knows which pool it's balancing.
262
263 =cut
264
265 sub _build_balancer {
266     my $self = shift @_;
267     $self->create_balancer(pool=>$self->pool);
268 }
269
270 =head2 _build_write_handler
271
272 Lazy builder for the L</write_handler> attribute.  The default is to set this to
273 the L</master>.
274
275 =cut
276
277 sub _build_write_handler {
278     return shift->master;
279 }
280
281 =head2 _build_read_handler
282
283 Lazy builder for the L</read_handler> attribute.  The default is to set this to
284 the L</balancer>.
285
286 =cut
287
288 sub _build_read_handler {
289     return shift->balancer;
290 }
291
292 =head2 around: connect_replicants
293
294 All calls to connect_replicants needs to have an existing $schema tacked onto
295 top of the args, since L<DBIx::Storage::DBI> needs it.
296
297 =cut
298
299 around 'connect_replicants' => sub {
300         my ($method, $self, @args) = @_;
301         $self->$method($self->schema, @args);
302 };
303
304 =head2 all_storages
305
306 Returns an array of of all the connected storage backends.  The first element
307 in the returned array is the master, and the remainings are each of the
308 replicants.
309
310 =cut
311
312 sub all_storages {
313         my $self = shift @_;
314         
315         return grep {defined $_ && blessed $_} (
316            $self->master,
317            $self->replicants,
318         );
319 }
320
321 =head2 set_reliable_storage
322
323 Sets the current $schema to be 'reliable', that is all queries, both read and
324 write are sent to the master
325     
326 =cut
327
328 sub set_reliable_storage {
329         my $self = shift @_;
330         my $schema = $self->schema;
331         my $write_handler = $self->schema->storage->write_handler;
332         
333         $schema->storage->read_handler($write_handler);
334 }
335
336 =head2 set_balanced_storage
337
338 Sets the current $schema to be use the </balancer> for all reads, while all
339 writea are sent to the master only
340     
341 =cut
342
343 sub set_balanced_storage {
344     my $self = shift @_;
345     my $schema = $self->schema;
346     my $write_handler = $self->schema->storage->balancer;
347     
348     $schema->storage->read_handler($write_handler);
349 }
350
351 =head2 connected
352
353 Check that the master and at least one of the replicants is connected.
354
355 =cut
356
357 sub connected {
358         my $self = shift @_;
359         
360         return
361            $self->master->connected &&
362            $self->pool->connected_replicants;
363 }
364
365 =head2 ensure_connected
366
367 Make sure all the storages are connected.
368
369 =cut
370
371 sub ensure_connected {
372     my $self = shift @_;
373     foreach my $source ($self->all_storages) {
374         $source->ensure_connected(@_);
375     }
376 }
377
378 =head2 limit_dialect
379
380 Set the limit_dialect for all existing storages
381
382 =cut
383
384 sub limit_dialect {
385     my $self = shift @_;
386     foreach my $source ($self->all_storages) {
387         $source->limit_dialect(@_);
388     }
389 }
390
391 =head2 quote_char
392
393 Set the quote_char for all existing storages
394
395 =cut
396
397 sub quote_char {
398     my $self = shift @_;
399     foreach my $source ($self->all_storages) {
400         $source->quote_char(@_);
401     }
402 }
403
404 =head2 name_sep
405
406 Set the name_sep for all existing storages
407
408 =cut
409
410 sub name_sep {
411     my $self = shift @_;
412     foreach my $source ($self->all_storages) {
413         $source->name_sep(@_);
414     }
415 }
416
417 =head2 set_schema
418
419 Set the schema object for all existing storages
420
421 =cut
422
423 sub set_schema {
424         my $self = shift @_;
425         foreach my $source ($self->all_storages) {
426                 $source->set_schema(@_);
427         }
428 }
429
430 =head2 debug
431
432 set a debug flag across all storages
433
434 =cut
435
436 sub debug {
437     my $self = shift @_;
438     foreach my $source ($self->all_storages) {
439         $source->debug(@_);
440     }
441 }
442
443 =head2 debugobj
444
445 set a debug object across all storages
446
447 =cut
448
449 sub debugobj {
450     my $self = shift @_;
451     foreach my $source ($self->all_storages) {
452         $source->debugobj(@_);
453     }
454 }
455
456 =head2 debugfh
457
458 set a debugfh object across all storages
459
460 =cut
461
462 sub debugfh {
463     my $self = shift @_;
464     foreach my $source ($self->all_storages) {
465         $source->debugfh(@_);
466     }
467 }
468
469 =head2 debugcb
470
471 set a debug callback across all storages
472
473 =cut
474
475 sub debugcb {
476     my $self = shift @_;
477     foreach my $source ($self->all_storages) {
478         $source->debugcb(@_);
479     }
480 }
481
482 =head2 disconnect
483
484 disconnect everything
485
486 =cut
487
488 sub disconnect {
489     my $self = shift @_;
490     foreach my $source ($self->all_storages) {
491         $source->disconnect(@_);
492     }
493 }
494
495 =head1 AUTHOR
496
497 Norbert Csongrádi <bert@cpan.org>
498
499 Peter Siklósi <einon@einon.hu>
500
501 John Napiorkowski <john.napiorkowski@takkle.com>
502
503 =head1 LICENSE
504
505 You may distribute this code under the same terms as Perl itself.
506
507 =cut
508
509 1;