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