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',
152 Contains the classname which will instantiate the L</pool> object. Defaults
153 to: L<DBIx::Class::Storage::DBI::Replicated::Pool>.
162 default=>'DBIx::Class::Storage::DBI::Replicated::Pool',
164 'create_pool' => 'new',
171 The replication pool requires a balance class to provider the methods for
172 choose how to spread the query load across each replicant in the pool.
176 has 'balancer_type' => (
181 default=>'DBIx::Class::Storage::DBI::Replicated::Balancer',
183 'create_balancer' => 'new',
190 Is a <DBIx::Class::Storage::DBI::Replicated::Pool> or derived class. This is a
191 container class for one or more replicated databases.
197 isa=>'DBIx::Class::Storage::DBI::Replicated::Pool',
211 Is a <DBIx::Class::Storage::DBI::Replicated::Balancer> or derived class. This
212 is a class that takes a pool (<DBIx::Class::Storage::DBI::Replicated::Pool>)
218 isa=>'DBIx::Class::Storage::DBI::Replicated::Balancer',
220 handles=>[qw/next_storage/],
225 This class defines the following methods.
229 Lazy builder for the L</master> attribute.
234 DBIx::Class::Storage::DBI->new;
238 =head2 _build_current_replicant
240 Lazy builder for the L</current_replicant_storage> attribute.
244 sub _build_current_replicant {
246 $self->next_storage($self->pool);
252 Lazy builder for the L</pool> attribute.
262 =head2 _build_balancer
264 Lazy builder for the L</balancer> attribute.
268 sub _build_balancer {
270 $self->create_balancer;
274 =head2 around: create_replicants
276 All calls to create_replicants needs to have an existing $schema tacked onto
281 around 'create_replicants' => sub {
282 my ($method, $self, @args) = @_;
283 $self->$method($self->schema, @args);
287 =head2 after: get_current_replicant_storage
289 Advice on the current_replicant_storage attribute. Each time we use a replicant
290 we need to change it via the storage pool algorithm. That way we are spreading
291 the load evenly (hopefully) across existing capacity.
295 after 'current_replicant' => sub {
297 my $next_replicant = $self->next_storage($self->pool);
299 warn '......................';
300 $self->set_current_replicant($next_replicant);
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
315 return grep {defined $_ && blessed $_} (
324 Check that the master and at least one of the replicants is connected.
332 $self->master->connected &&
333 $self->pool->connected_replicants;
337 =head2 ensure_connected
339 Make sure all the storages are connected.
343 sub ensure_connected {
345 foreach my $source ($self->all_storages) {
346 $source->ensure_connected(@_);
353 Set the limit_dialect for all existing storages
359 foreach my $source ($self->all_storages) {
360 $source->limit_dialect(@_);
367 Set the quote_char for all existing storages
373 foreach my $source ($self->all_storages) {
374 $source->quote_char(@_);
381 Set the name_sep for all existing storages
387 foreach my $source ($self->all_storages) {
388 $source->name_sep(@_);
395 Set the schema object for all existing storages
401 foreach my $source ($self->all_storages) {
402 $source->set_schema(@_);
409 set a debug flag across all storages
415 foreach my $source ($self->all_storages) {
423 set a debug object across all storages
429 foreach my $source ($self->all_storages) {
430 $source->debugobj(@_);
437 set a debugfh object across all storages
443 foreach my $source ($self->all_storages) {
444 $source->debugfh(@_);
451 set a debug callback across all storages
457 foreach my $source ($self->all_storages) {
458 $source->debugcb(@_);
465 disconnect everything
471 foreach my $source ($self->all_storages) {
472 $source->disconnect(@_);
479 Make sure we pass destroy events down to the storage handlers
485 ## TODO, maybe we can just leave this alone ???
491 Norbert Csongrádi <bert@cpan.org>
493 Peter Siklósi <einon@einon.hu>
495 John Napiorkowski <john.napiorkowski@takkle.com>
499 You may distribute this code under the same terms as Perl itself.
510 use DBIx::Class::Storage::DBI;
513 use base qw/Class::Accessor::Fast/;
515 __PACKAGE__->mk_accessors( qw/read_source write_source/ );
519 DBIx::Class::Storage::DBI::Replicated - ALPHA Replicated database support
523 The Following example shows how to change an existing $schema to a replicated
524 storage type and update it's connection information to contain a master DSN and
527 ## Change storage_type in your schema class
528 $schema->storage_type( '::DBI::Replicated' );
530 ## Set your connection.
532 $dsn, $user, $password, {
534 ## Other standard DBI connection or DBD custom attributes added as
535 ## usual. Additionally, we have two custom attributes for defining
536 ## slave information and controlling how the underlying DBD::Multi
537 slaves_connect_info => [
538 ## Define each slave like a 'normal' DBI connection, but you add
539 ## in a DBD::Multi custom attribute to define how the slave is
540 ## prioritized. Please see DBD::Multi for more.
541 [$slave1dsn, $user, $password, {%slave1opts, priority=>10}],
542 [$slave2dsn, $user, $password, {%slave2opts, priority=>10}],
543 [$slave3dsn, $user, $password, {%slave3opts, priority=>20}],
544 ## add in a preexisting database handle
545 [$dbh, '','', {priority=>30}],
546 ## DBD::Multi will call this coderef for connects
547 [sub { DBI->connect(< DSN info >) }, '', '', {priority=>40}],
548 ## If the last item is hashref, we use that for DBD::Multi's
549 ## configuration information. Again, see DBD::Multi for more.
550 {timeout=>25, failed_max=>2},
555 ## Now, just use the schema as normal
556 $schema->resultset('Table')->find(< unique >); ## Reads will use slaves
557 $schema->resultset('Table')->create(\%info); ## Writes will use master
561 Warning: This class is marked ALPHA. We are using this in development and have
562 some basic test coverage but the code hasn't yet been stressed by a variety
563 of databases. Individual DB's may have quirks we are not aware of. Please
564 use this in development and pass along your experiences/bug fixes.
566 This class implements replicated data store for DBI. Currently you can define
567 one master and numerous slave database connections. All write-type queries
568 (INSERT, UPDATE, DELETE and even LAST_INSERT_ID) are routed to master
569 database, all read-type queries (SELECTs) go to the slave database.
571 For every slave database you can define a priority value, which controls data
572 source usage pattern. It uses L<DBD::Multi>, so first the lower priority data
573 sources used (if they have the same priority, the are used randomized), than
574 if all low priority data sources fail, higher ones tried in order.
578 Please see L<DBD::Multi> for most configuration information.
584 my $class = ref( $proto ) || $proto;
587 bless( $self, $class );
589 $self->write_source( DBIx::Class::Storage::DBI->new );
590 $self->read_source( DBIx::Class::Storage::DBI->new );
598 my @sources = ($self->read_source, $self->write_source);
600 return wantarray ? @sources : \@sources;
605 my $master = $self->write_source->_connect_info;
606 $master->[-1]->{slave_connect_info} = $self->read_source->_connect_info;
611 my ($self, $source_info) = @_;
613 ## if there is no $source_info, treat this sub like an accessor
614 return $self->_connect_info
617 ## Alright, let's conect the master
618 $self->write_source->connect_info($source_info);
620 ## Now, build and then connect the Slaves
621 my @slaves_connect_info = @{$source_info->[-1]->{slaves_connect_info}};
622 my $dbd_multi_config = ref $slaves_connect_info[-1] eq 'HASH'
623 ? pop @slaves_connect_info : {};
625 ## We need to do this since SQL::Abstract::Limit can't guess what DBD::Multi is
626 $dbd_multi_config->{limit_dialect} = $self->write_source->sql_maker->limit_dialect
627 unless defined $dbd_multi_config->{limit_dialect};
629 @slaves_connect_info = map {
630 ## if the first element in the arrayhash is a ref, make that the value
631 my $db = ref $_->[0] ? $_->[0] : $_;
632 my $priority = $_->[-1]->{priority} || 10; ## default priority is 10
634 } @slaves_connect_info;
636 $self->read_source->connect_info([
637 'dbi:Multi:', undef, undef, {
638 dsns => [@slaves_connect_info],
643 ## Return the formated connection information
644 return $self->_connect_info;
648 shift->read_source->select( @_ );
651 shift->read_source->select_single( @_ );
653 sub throw_exception {
654 shift->read_source->throw_exception( @_ );
657 shift->read_source->sql_maker( @_ );
659 sub columns_info_for {
660 shift->read_source->columns_info_for( @_ );
663 shift->read_source->sqlt_type( @_ );
666 shift->read_source->create_ddl_dir( @_ );
668 sub deployment_statements {
669 shift->read_source->deployment_statements( @_ );
671 sub datetime_parser {
672 shift->read_source->datetime_parser( @_ );
674 sub datetime_parser_type {
675 shift->read_source->datetime_parser_type( @_ );
677 sub build_datetime_parser {
678 shift->read_source->build_datetime_parser( @_ );
681 sub limit_dialect { $_->limit_dialect( @_ ) for( shift->all_sources ) }
682 sub quote_char { $_->quote_char( @_ ) for( shift->all_sources ) }
683 sub name_sep { $_->quote_char( @_ ) for( shift->all_sources ) }
684 sub disconnect { $_->disconnect( @_ ) for( shift->all_sources ) }
685 sub set_schema { $_->set_schema( @_ ) for( shift->all_sources ) }
690 undef $self->{write_source};
691 undef $self->{read_sources};
695 shift->write_source->last_insert_id( @_ );
698 shift->write_source->insert( @_ );
701 shift->write_source->update( @_ );
704 shift->write_source->update_all( @_ );
707 shift->write_source->delete( @_ );
710 shift->write_source->delete_all( @_ );
713 shift->write_source->create( @_ );
716 shift->write_source->find_or_create( @_ );
718 sub update_or_create {
719 shift->write_source->update_or_create( @_ );
722 shift->write_source->connected( @_ );
724 sub ensure_connected {
725 shift->write_source->ensure_connected( @_ );
728 shift->write_source->dbh( @_ );
731 shift->write_source->txn_do( @_ );
734 shift->write_source->txn_commit( @_ );
737 shift->write_source->txn_rollback( @_ );
740 shift->write_source->sth( @_ );
743 shift->write_source->deploy( @_ );
745 sub _prep_for_execute {
746 shift->write_source->_prep_for_execute(@_);
750 shift->write_source->debugobj(@_);
753 shift->write_source->debug(@_);
756 sub debugfh { shift->_not_supported( 'debugfh' ) };
757 sub debugcb { shift->_not_supported( 'debugcb' ) };
760 my( $self, $method ) = @_;
762 die "This Storage does not support $method method.";
767 L<DBI::Class::Storage::DBI>, L<DBD::Multi>, L<DBI>
771 Norbert Csongrádi <bert@cpan.org>
773 Peter Siklósi <einon@einon.hu>
775 John Napiorkowski <john.napiorkowski@takkle.com>
779 You may distribute this code under the same terms as Perl itself.