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->create_replicants(
28 [$dsn1, $user, $pass, \%opts],
29 [$dsn1, $user, $pass, \%opts],
30 [$dsn1, $user, $pass, \%opts],
31 ## This is just going to use the standard DBIC connect method, so it
32 ## supports everything that method supports, such as connecting to an
33 ## existing database handle.
40 Warning: This class is marked ALPHA. We are using this in development and have
41 some basic test coverage but the code hasn't yet been stressed by a variety
42 of databases. Individual DB's may have quirks we are not aware of. Please
43 use this in development and pass along your experiences/bug fixes.
45 This class implements replicated data store for DBI. Currently you can define
46 one master and numerous slave database connections. All write-type queries
47 (INSERT, UPDATE, DELETE and even LAST_INSERT_ID) are routed to master
48 database, all read-type queries (SELECTs) go to the slave database.
50 Basically, any method request that L<DBIx::Class::Storage::DBI> would normally
51 handle gets delegated to one of the two attributes: L</master_storage> or to
52 L</current_replicant_storage>. Additionally, some methods need to be distributed
53 to all existing storages. This way our storage class is a drop in replacement
54 for L<DBIx::Class::Storage::DBI>.
56 Read traffic is spread across the replicants (slaves) occuring to a user
57 selected algorithm. The default algorithm is random weighted.
59 TODO more details about the algorithm.
63 This class defines the following attributes.
67 The master defines the canonical state for a pool of connected databases. All
68 the replicants are expected to match this databases state. Thus, in a classic
69 Master / Slaves distributed system, all the slaves are expected to replicate
70 the Master's state as quick as possible. This is the only database in the
71 pool of databases that is allowed to handle write traffic.
77 isa=>'DBIx::Class::Storage::DBI',
106 =head2 current_replicant
108 Replicant storages (slaves) handle all read only traffic. The assumption is
109 that your database will become readbound well before it becomes write bound
110 and that being able to spread your read only traffic around to multiple
111 databases is going to help you to scale traffic.
113 This attribute returns the next slave to handle a read request. Your L</pool>
114 attribute has methods to help you shuffle through all the available replicants
115 via it's balancer object.
117 This attribute defines the following reader/writer methods
121 =item get_current_replicant
123 Returns the contained L<DBIx::Class::Storage::DBI> replicant
125 =item set_current_replicant
127 Set the attribute to a given L<DBIx::Class::Storage::DBI> (or subclass) object.
131 We split the reader/writer to make it easier to selectively override how the
132 replicant is altered.
136 has 'current_replicant' => (
138 reader=>'get_current_replicant',
139 writer=>'set_current_replicant',
140 isa=>'DBIx::Class::Storage::DBI',
147 trigger=>sub {'xxxxxxxxxxxxxxxxxxxxxxxxxxx'},
153 Contains the classname which will instantiate the L</pool> object. Defaults
154 to: L<DBIx::Class::Storage::DBI::Replicated::Pool>.
163 default=>'DBIx::Class::Storage::DBI::Replicated::Pool',
165 'create_pool' => 'new',
172 The replication pool requires a balance class to provider the methods for
173 choose how to spread the query load across each replicant in the pool.
177 has 'balancer_type' => (
182 default=>'DBIx::Class::Storage::DBI::Replicated::Balancer',
184 'create_balancer' => 'new',
191 Is a <DBIx::Class::Storage::DBI::Replicated::Pool> or derived class. This is a
192 container class for one or more replicated databases.
198 isa=>'DBIx::Class::Storage::DBI::Replicated::Pool',
212 Is a <DBIx::Class::Storage::DBI::Replicated::Balancer> or derived class. This
213 is a class that takes a pool (<DBIx::Class::Storage::DBI::Replicated::Pool>)
219 isa=>'DBIx::Class::Storage::DBI::Replicated::Balancer',
221 handles=>[qw/next_storage/],
226 This class defines the following methods.
230 Lazy builder for the L</master> attribute.
235 DBIx::Class::Storage::DBI->new;
239 =head2 _build_current_replicant
241 Lazy builder for the L</current_replicant_storage> attribute.
245 sub _build_current_replicant {
247 $self->next_storage($self->pool);
253 Lazy builder for the L</pool> attribute.
263 =head2 _build_balancer
265 Lazy builder for the L</balancer> attribute.
269 sub _build_balancer {
271 $self->create_balancer;
275 =head2 around: create_replicants
277 All calls to create_replicants needs to have an existing $schema tacked onto
282 around 'create_replicants' => sub {
283 my ($method, $self, @args) = @_;
284 $self->$method($self->schema, @args);
288 =head2 after: get_current_replicant_storage
290 Advice on the current_replicant_storage attribute. Each time we use a replicant
291 we need to change it via the storage pool algorithm. That way we are spreading
292 the load evenly (hopefully) across existing capacity.
296 after 'current_replicant' => sub {
298 my $next_replicant = $self->next_storage($self->pool);
300 warn '......................';
301 $self->set_current_replicant($next_replicant);
307 Returns an array of of all the connected storage backends. The first element
308 in the returned array is the master, and the remainings are each of the
316 return grep {defined $_ && blessed $_} (
325 Check that the master and at least one of the replicants is connected.
333 $self->master->connected &&
334 $self->pool->connected_replicants;
338 =head2 ensure_connected
340 Make sure all the storages are connected.
344 sub ensure_connected {
346 foreach my $source ($self->all_storages) {
347 $source->ensure_connected(@_);
354 Set the limit_dialect for all existing storages
360 foreach my $source ($self->all_storages) {
361 $source->limit_dialect(@_);
368 Set the quote_char for all existing storages
374 foreach my $source ($self->all_storages) {
375 $source->quote_char(@_);
382 Set the name_sep for all existing storages
388 foreach my $source ($self->all_storages) {
389 $source->name_sep(@_);
396 Set the schema object for all existing storages
402 foreach my $source ($self->all_storages) {
403 $source->set_schema(@_);
410 set a debug flag across all storages
416 foreach my $source ($self->all_storages) {
424 set a debug object across all storages
430 foreach my $source ($self->all_storages) {
431 $source->debugobj(@_);
438 set a debugfh object across all storages
444 foreach my $source ($self->all_storages) {
445 $source->debugfh(@_);
452 set a debug callback across all storages
458 foreach my $source ($self->all_storages) {
459 $source->debugcb(@_);
466 disconnect everything
472 foreach my $source ($self->all_storages) {
473 $source->disconnect(@_);
480 Make sure we pass destroy events down to the storage handlers
486 ## TODO, maybe we can just leave this alone ???
492 Norbert Csongrádi <bert@cpan.org>
494 Peter Siklósi <einon@einon.hu>
496 John Napiorkowski <john.napiorkowski@takkle.com>
500 You may distribute this code under the same terms as Perl itself.
511 use DBIx::Class::Storage::DBI;
514 use base qw/Class::Accessor::Fast/;
516 __PACKAGE__->mk_accessors( qw/read_source write_source/ );
520 DBIx::Class::Storage::DBI::Replicated - ALPHA Replicated database support
524 The Following example shows how to change an existing $schema to a replicated
525 storage type and update it's connection information to contain a master DSN and
528 ## Change storage_type in your schema class
529 $schema->storage_type( '::DBI::Replicated' );
531 ## Set your connection.
533 $dsn, $user, $password, {
535 ## Other standard DBI connection or DBD custom attributes added as
536 ## usual. Additionally, we have two custom attributes for defining
537 ## slave information and controlling how the underlying DBD::Multi
538 slaves_connect_info => [
539 ## Define each slave like a 'normal' DBI connection, but you add
540 ## in a DBD::Multi custom attribute to define how the slave is
541 ## prioritized. Please see DBD::Multi for more.
542 [$slave1dsn, $user, $password, {%slave1opts, priority=>10}],
543 [$slave2dsn, $user, $password, {%slave2opts, priority=>10}],
544 [$slave3dsn, $user, $password, {%slave3opts, priority=>20}],
545 ## add in a preexisting database handle
546 [$dbh, '','', {priority=>30}],
547 ## DBD::Multi will call this coderef for connects
548 [sub { DBI->connect(< DSN info >) }, '', '', {priority=>40}],
549 ## If the last item is hashref, we use that for DBD::Multi's
550 ## configuration information. Again, see DBD::Multi for more.
551 {timeout=>25, failed_max=>2},
556 ## Now, just use the schema as normal
557 $schema->resultset('Table')->find(< unique >); ## Reads will use slaves
558 $schema->resultset('Table')->create(\%info); ## Writes will use master
562 Warning: This class is marked ALPHA. We are using this in development and have
563 some basic test coverage but the code hasn't yet been stressed by a variety
564 of databases. Individual DB's may have quirks we are not aware of. Please
565 use this in development and pass along your experiences/bug fixes.
567 This class implements replicated data store for DBI. Currently you can define
568 one master and numerous slave database connections. All write-type queries
569 (INSERT, UPDATE, DELETE and even LAST_INSERT_ID) are routed to master
570 database, all read-type queries (SELECTs) go to the slave database.
572 For every slave database you can define a priority value, which controls data
573 source usage pattern. It uses L<DBD::Multi>, so first the lower priority data
574 sources used (if they have the same priority, the are used randomized), than
575 if all low priority data sources fail, higher ones tried in order.
579 Please see L<DBD::Multi> for most configuration information.
585 my $class = ref( $proto ) || $proto;
588 bless( $self, $class );
590 $self->write_source( DBIx::Class::Storage::DBI->new );
591 $self->read_source( DBIx::Class::Storage::DBI->new );
599 my @sources = ($self->read_source, $self->write_source);
601 return wantarray ? @sources : \@sources;
606 my $master = $self->write_source->_connect_info;
607 $master->[-1]->{slave_connect_info} = $self->read_source->_connect_info;
612 my ($self, $source_info) = @_;
614 ## if there is no $source_info, treat this sub like an accessor
615 return $self->_connect_info
618 ## Alright, let's conect the master
619 $self->write_source->connect_info($source_info);
621 ## Now, build and then connect the Slaves
622 my @slaves_connect_info = @{$source_info->[-1]->{slaves_connect_info}};
623 my $dbd_multi_config = ref $slaves_connect_info[-1] eq 'HASH'
624 ? pop @slaves_connect_info : {};
626 ## We need to do this since SQL::Abstract::Limit can't guess what DBD::Multi is
627 $dbd_multi_config->{limit_dialect} = $self->write_source->sql_maker->limit_dialect
628 unless defined $dbd_multi_config->{limit_dialect};
630 @slaves_connect_info = map {
631 ## if the first element in the arrayhash is a ref, make that the value
632 my $db = ref $_->[0] ? $_->[0] : $_;
633 my $priority = $_->[-1]->{priority} || 10; ## default priority is 10
635 } @slaves_connect_info;
637 $self->read_source->connect_info([
638 'dbi:Multi:', undef, undef, {
639 dsns => [@slaves_connect_info],
644 ## Return the formated connection information
645 return $self->_connect_info;
649 shift->read_source->select( @_ );
652 shift->read_source->select_single( @_ );
654 sub throw_exception {
655 shift->read_source->throw_exception( @_ );
658 shift->read_source->sql_maker( @_ );
660 sub columns_info_for {
661 shift->read_source->columns_info_for( @_ );
664 shift->read_source->sqlt_type( @_ );
667 shift->read_source->create_ddl_dir( @_ );
669 sub deployment_statements {
670 shift->read_source->deployment_statements( @_ );
672 sub datetime_parser {
673 shift->read_source->datetime_parser( @_ );
675 sub datetime_parser_type {
676 shift->read_source->datetime_parser_type( @_ );
678 sub build_datetime_parser {
679 shift->read_source->build_datetime_parser( @_ );
682 sub limit_dialect { $_->limit_dialect( @_ ) for( shift->all_sources ) }
683 sub quote_char { $_->quote_char( @_ ) for( shift->all_sources ) }
684 sub name_sep { $_->quote_char( @_ ) for( shift->all_sources ) }
685 sub disconnect { $_->disconnect( @_ ) for( shift->all_sources ) }
686 sub set_schema { $_->set_schema( @_ ) for( shift->all_sources ) }
691 undef $self->{write_source};
692 undef $self->{read_sources};
696 shift->write_source->last_insert_id( @_ );
699 shift->write_source->insert( @_ );
702 shift->write_source->update( @_ );
705 shift->write_source->update_all( @_ );
708 shift->write_source->delete( @_ );
711 shift->write_source->delete_all( @_ );
714 shift->write_source->create( @_ );
717 shift->write_source->find_or_create( @_ );
719 sub update_or_create {
720 shift->write_source->update_or_create( @_ );
723 shift->write_source->connected( @_ );
725 sub ensure_connected {
726 shift->write_source->ensure_connected( @_ );
729 shift->write_source->dbh( @_ );
732 shift->write_source->txn_do( @_ );
735 shift->write_source->txn_commit( @_ );
738 shift->write_source->txn_rollback( @_ );
741 shift->write_source->sth( @_ );
744 shift->write_source->deploy( @_ );
746 sub _prep_for_execute {
747 shift->write_source->_prep_for_execute(@_);
751 shift->write_source->debugobj(@_);
754 shift->write_source->debug(@_);
757 sub debugfh { shift->_not_supported( 'debugfh' ) };
758 sub debugcb { shift->_not_supported( 'debugcb' ) };
761 my( $self, $method ) = @_;
763 die "This Storage does not support $method method.";
768 L<DBI::Class::Storage::DBI>, L<DBD::Multi>, L<DBI>
772 Norbert Csongrádi <bert@cpan.org>
774 Peter Siklósi <einon@einon.hu>
776 John Napiorkowski <john.napiorkowski@takkle.com>
780 You may distribute this code under the same terms as Perl itself.