1 package DBIx::Class::Storage::DBI::Replicated;
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);
9 extends 'DBIx::Class::Storage::DBI', 'Moose::Object';
13 DBIx::Class::Storage::DBI::Replicated - ALPHA Replicated database support
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
21 ## Change storage_type in your schema class
22 $schema->storage_type( '::DBI::Replicated' );
24 ## Add some slaves. Basically this is an array of arrayrefs, where each
25 ## arrayref is database connect information
27 $schema->storage->connect_replicants(
28 [$dsn1, $user, $pass, \%opts],
29 [$dsn1, $user, $pass, \%opts],
30 [$dsn1, $user, $pass, \%opts],
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.
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.
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>.
51 Read traffic is spread across the replicants (slaves) occuring to a user
52 selected algorithm. The default algorithm is random weighted.
54 TODO more details about the algorithm.
58 This class defines the following attributes.
62 The master defines the canonical state for a pool of connected databases. All
63 the replicants are expected to match this databases state. Thus, in a classic
64 Master / Slaves distributed system, all the slaves are expected to replicate
65 the Master's state as quick as possible. This is the only database in the
66 pool of databases that is allowed to handle write traffic.
72 isa=>'DBIx::Class::Storage::DBI',
101 =head2 current_replicant
103 Replicant storages (slaves) handle all read only traffic. The assumption is
104 that your database will become readbound well before it becomes write bound
105 and that being able to spread your read only traffic around to multiple
106 databases is going to help you to scale traffic.
108 This attribute returns the next slave to handle a read request. Your L</pool>
109 attribute has methods to help you shuffle through all the available replicants
110 via it's balancer object.
112 We split the reader/writer to make it easier to selectively override how the
113 replicant is altered.
117 has 'current_replicant' => (
119 isa=>'DBIx::Class::Storage::DBI',
131 Contains the classname which will instantiate the L</pool> object. Defaults
132 to: L<DBIx::Class::Storage::DBI::Replicated::Pool>.
141 default=>'DBIx::Class::Storage::DBI::Replicated::Pool',
143 'create_pool' => 'new',
150 The replication pool requires a balance class to provider the methods for
151 choose how to spread the query load across each replicant in the pool.
155 has 'balancer_type' => (
160 default=>'DBIx::Class::Storage::DBI::Replicated::Balancer',
162 'create_balancer' => 'new',
169 Is a <DBIx::Class::Storage::DBI::Replicated::Pool> or derived class. This is a
170 container class for one or more replicated databases.
176 isa=>'DBIx::Class::Storage::DBI::Replicated::Pool',
190 Is a <DBIx::Class::Storage::DBI::Replicated::Balancer> or derived class. This
191 is a class that takes a pool (<DBIx::Class::Storage::DBI::Replicated::Pool>)
197 isa=>'DBIx::Class::Storage::DBI::Replicated::Balancer',
199 handles=>[qw/next_storage/],
204 This class defines the following methods.
208 Lazy builder for the L</master> attribute.
213 DBIx::Class::Storage::DBI->new;
217 =head2 _build_current_replicant
219 Lazy builder for the L</current_replicant_storage> attribute.
223 sub _build_current_replicant {
225 $self->next_storage($self->pool);
231 Lazy builder for the L</pool> attribute.
241 =head2 _build_balancer
243 Lazy builder for the L</balancer> attribute.
247 sub _build_balancer {
249 $self->create_balancer;
253 =head2 around: create_replicants
255 All calls to create_replicants needs to have an existing $schema tacked onto
260 around 'connect_replicants' => sub {
261 my ($method, $self, @args) = @_;
262 $self->$method($self->schema, @args);
266 =head2 after: select, select_single, columns_info_for
268 Advice on the current_replicant_storage attribute. Each time we use a replicant
269 we need to change it via the storage pool algorithm. That way we are spreading
270 the load evenly (hopefully) across existing capacity.
274 after 'select' => sub {
276 my $next_replicant = $self->next_storage($self->pool);
278 $self->current_replicant($next_replicant);
281 after 'select_single' => sub {
283 my $next_replicant = $self->next_storage($self->pool);
285 $self->current_replicant($next_replicant);
288 after 'columns_info_for' => sub {
290 my $next_replicant = $self->next_storage($self->pool);
292 $self->current_replicant($next_replicant);
297 Returns an array of of all the connected storage backends. The first element
298 in the returned array is the master, and the remainings are each of the
306 return grep {defined $_ && blessed $_} (
315 Check that the master and at least one of the replicants is connected.
323 $self->master->connected &&
324 $self->pool->connected_replicants;
328 =head2 ensure_connected
330 Make sure all the storages are connected.
334 sub ensure_connected {
336 foreach my $source ($self->all_storages) {
337 $source->ensure_connected(@_);
344 Set the limit_dialect for all existing storages
350 foreach my $source ($self->all_storages) {
351 $source->limit_dialect(@_);
358 Set the quote_char for all existing storages
364 foreach my $source ($self->all_storages) {
365 $source->quote_char(@_);
372 Set the name_sep for all existing storages
378 foreach my $source ($self->all_storages) {
379 $source->name_sep(@_);
386 Set the schema object for all existing storages
392 foreach my $source ($self->all_storages) {
393 $source->set_schema(@_);
400 set a debug flag across all storages
406 foreach my $source ($self->all_storages) {
414 set a debug object across all storages
420 foreach my $source ($self->all_storages) {
421 $source->debugobj(@_);
428 set a debugfh object across all storages
434 foreach my $source ($self->all_storages) {
435 $source->debugfh(@_);
442 set a debug callback across all storages
448 foreach my $source ($self->all_storages) {
449 $source->debugcb(@_);
456 disconnect everything
462 foreach my $source ($self->all_storages) {
463 $source->disconnect(@_);
470 Make sure we pass destroy events down to the storage handlers
476 ## TODO, maybe we can just leave this alone ???
482 Norbert Csongrádi <bert@cpan.org>
484 Peter Siklósi <einon@einon.hu>
486 John Napiorkowski <john.napiorkowski@takkle.com>
490 You may distribute this code under the same terms as Perl itself.
501 use DBIx::Class::Storage::DBI;
504 use base qw/Class::Accessor::Fast/;
506 __PACKAGE__->mk_accessors( qw/read_source write_source/ );
510 DBIx::Class::Storage::DBI::Replicated - ALPHA Replicated database support
514 The Following example shows how to change an existing $schema to a replicated
515 storage type and update it's connection information to contain a master DSN and
518 ## Change storage_type in your schema class
519 $schema->storage_type( '::DBI::Replicated' );
521 ## Set your connection.
523 $dsn, $user, $password, {
525 ## Other standard DBI connection or DBD custom attributes added as
526 ## usual. Additionally, we have two custom attributes for defining
527 ## slave information and controlling how the underlying DBD::Multi
528 connect_replicants => [
529 ## Define each slave like a 'normal' DBI connection, but you add
530 ## in a DBD::Multi custom attribute to define how the slave is
531 ## prioritized. Please see DBD::Multi for more.
532 [$slave1dsn, $user, $password, {%slave1opts}],
533 [$slave2dsn, $user, $password, {%slave2opts}],
534 [$slave3dsn, $user, $password, {%slave3opts}],
539 ## Now, just use the schema as normal
540 $schema->resultset('Table')->find(< unique >); ## Reads will use slaves
541 $schema->resultset('Table')->create(\%info); ## Writes will use master
545 Warning: This class is marked ALPHA. We are using this in development and have
546 some basic test coverage but the code hasn't yet been stressed by a variety
547 of databases. Individual DB's may have quirks we are not aware of. Please
548 use this in development and pass along your experiences/bug fixes.
550 This class implements replicated data store for DBI. Currently you can define
551 one master and numerous slave database connections. All write-type queries
552 (INSERT, UPDATE, DELETE and even LAST_INSERT_ID) are routed to master
553 database, all read-type queries (SELECTs) go to the slave database.
555 For every slave database you can define a priority value, which controls data
556 source usage pattern. It uses L<DBD::Multi>, so first the lower priority data
557 sources used (if they have the same priority, the are used randomized), than
558 if all low priority data sources fail, higher ones tried in order.
562 Please see L<DBD::Multi> for most configuration information.
568 my $class = ref( $proto ) || $proto;
571 bless( $self, $class );
573 $self->write_source( DBIx::Class::Storage::DBI->new );
574 $self->read_source( DBIx::Class::Storage::DBI->new );
582 my @sources = ($self->read_source, $self->write_source);
584 return wantarray ? @sources : \@sources;
589 my $master = $self->write_source->_connect_info;
590 $master->[-1]->{slave_connect_info} = $self->read_source->_connect_info;
595 my ($self, $source_info) = @_;
597 ## if there is no $source_info, treat this sub like an accessor
598 return $self->_connect_info
601 ## Alright, let's conect the master
602 $self->write_source->connect_info($source_info);
604 ## Now, build and then connect the Slaves
605 my @slaves_connect_info = @{$source_info->[-1]->{slaves_connect_info}};
606 my $dbd_multi_config = ref $slaves_connect_info[-1] eq 'HASH'
607 ? pop @slaves_connect_info : {};
609 ## We need to do this since SQL::Abstract::Limit can't guess what DBD::Multi is
610 $dbd_multi_config->{limit_dialect} = $self->write_source->sql_maker->limit_dialect
611 unless defined $dbd_multi_config->{limit_dialect};
613 @slaves_connect_info = map {
614 ## if the first element in the arrayhash is a ref, make that the value
615 my $db = ref $_->[0] ? $_->[0] : $_;
616 my $priority = $_->[-1]->{priority} || 10; ## default priority is 10
618 } @slaves_connect_info;
620 $self->read_source->connect_info([
621 'dbi:Multi:', undef, undef, {
622 dsns => [@slaves_connect_info],
627 ## Return the formated connection information
628 return $self->_connect_info;
632 shift->read_source->select( @_ );
635 shift->read_source->select_single( @_ );
637 sub throw_exception {
638 shift->read_source->throw_exception( @_ );
641 shift->read_source->sql_maker( @_ );
643 sub columns_info_for {
644 shift->read_source->columns_info_for( @_ );
647 shift->read_source->sqlt_type( @_ );
650 shift->read_source->create_ddl_dir( @_ );
652 sub deployment_statements {
653 shift->read_source->deployment_statements( @_ );
655 sub datetime_parser {
656 shift->read_source->datetime_parser( @_ );
658 sub datetime_parser_type {
659 shift->read_source->datetime_parser_type( @_ );
661 sub build_datetime_parser {
662 shift->read_source->build_datetime_parser( @_ );
665 sub limit_dialect { $_->limit_dialect( @_ ) for( shift->all_sources ) }
666 sub quote_char { $_->quote_char( @_ ) for( shift->all_sources ) }
667 sub name_sep { $_->quote_char( @_ ) for( shift->all_sources ) }
668 sub disconnect { $_->disconnect( @_ ) for( shift->all_sources ) }
669 sub set_schema { $_->set_schema( @_ ) for( shift->all_sources ) }
674 undef $self->{write_source};
675 undef $self->{read_sources};
679 shift->write_source->last_insert_id( @_ );
682 shift->write_source->insert( @_ );
685 shift->write_source->update( @_ );
688 shift->write_source->update_all( @_ );
691 shift->write_source->delete( @_ );
694 shift->write_source->delete_all( @_ );
697 shift->write_source->create( @_ );
700 shift->write_source->find_or_create( @_ );
702 sub update_or_create {
703 shift->write_source->update_or_create( @_ );
706 shift->write_source->connected( @_ );
708 sub ensure_connected {
709 shift->write_source->ensure_connected( @_ );
712 shift->write_source->dbh( @_ );
715 shift->write_source->txn_do( @_ );
718 shift->write_source->txn_commit( @_ );
721 shift->write_source->txn_rollback( @_ );
724 shift->write_source->sth( @_ );
727 shift->write_source->deploy( @_ );
729 sub _prep_for_execute {
730 shift->write_source->_prep_for_execute(@_);
734 shift->write_source->debugobj(@_);
737 shift->write_source->debug(@_);
740 sub debugfh { shift->_not_supported( 'debugfh' ) };
741 sub debugcb { shift->_not_supported( 'debugcb' ) };
744 my( $self, $method ) = @_;
746 die "This Storage does not support $method method.";
751 L<DBI::Class::Storage::DBI>, L<DBD::Multi>, L<DBI>
755 Norbert Csongrádi <bert@cpan.org>
757 Peter Siklósi <einon@einon.hu>
759 John Napiorkowski <john.napiorkowski@takkle.com>
763 You may distribute this code under the same terms as Perl itself.