[Bast-commits] r4559 - in DBIx-Class/0.08/branches/replication_dedux: lib/DBIx/Class lib/DBIx/Class/Storage/DBI lib/DBIx/Class/Storage/DBI/Replicated t

jnapiorkowski at dev.catalyst.perl.org jnapiorkowski at dev.catalyst.perl.org
Mon Jul 7 22:38:49 BST 2008


Author: jnapiorkowski
Date: 2008-07-07 22:38:49 +0100 (Mon, 07 Jul 2008)
New Revision: 4559

Modified:
   DBIx-Class/0.08/branches/replication_dedux/lib/DBIx/Class/PK.pm
   DBIx-Class/0.08/branches/replication_dedux/lib/DBIx/Class/Row.pm
   DBIx-Class/0.08/branches/replication_dedux/lib/DBIx/Class/Storage/DBI/Replicated.pm
   DBIx-Class/0.08/branches/replication_dedux/lib/DBIx/Class/Storage/DBI/Replicated/Balancer.pm
   DBIx-Class/0.08/branches/replication_dedux/t/93storage_replication.t
Log:
updated documentation, adding some hints and details, changed the way we can use the resultset attribute to force a particular storage backend.

Modified: DBIx-Class/0.08/branches/replication_dedux/lib/DBIx/Class/PK.pm
===================================================================
--- DBIx-Class/0.08/branches/replication_dedux/lib/DBIx/Class/PK.pm	2008-07-07 19:16:32 UTC (rev 4558)
+++ DBIx-Class/0.08/branches/replication_dedux/lib/DBIx/Class/PK.pm	2008-07-07 21:38:49 UTC (rev 4559)
@@ -25,7 +25,7 @@
   return (map { $self->{_column_data}{$_} } $self->primary_columns);
 }
 
-=head2 discard_changes
+=head2 discard_changes ($attrs)
 
 Re-selects the row from the database, losing any changes that had
 been made.
@@ -33,14 +33,17 @@
 This method can also be used to refresh from storage, retrieving any
 changes made since the row was last read from storage.
 
+$attrs is expected to be a hashref of attributes suitable for passing as the
+second argument to $resultset->search($cond, $attrs);
+
 =cut
 
 sub discard_changes {
-  my ($self) = @_;
+  my ($self, $attrs) = @_;
   delete $self->{_dirty_columns};
   return unless $self->in_storage; # Don't reload if we aren't real!
   
-  if( my $current_storage = $self->get_from_storage) {
+  if( my $current_storage = $self->get_from_storage($attrs)) {
   	
     # Set $self to the current.
   	%$self = %$current_storage;

Modified: DBIx-Class/0.08/branches/replication_dedux/lib/DBIx/Class/Row.pm
===================================================================
--- DBIx-Class/0.08/branches/replication_dedux/lib/DBIx/Class/Row.pm	2008-07-07 19:16:32 UTC (rev 4558)
+++ DBIx-Class/0.08/branches/replication_dedux/lib/DBIx/Class/Row.pm	2008-07-07 21:38:49 UTC (rev 4559)
@@ -799,18 +799,28 @@
   $class->mk_group_accessors('column' => $acc);
 }
 
-=head2 get_from_storage
+=head2 get_from_storage ($attrs)
 
 Returns a new Row which is whatever the Storage has for the currently created
 Row object.  You can use this to see if the storage has become inconsistent with
 whatever your Row object is.
 
+$attrs is expected to be a hashref of attributes suitable for passing as the
+second argument to $resultset->search($cond, $attrs);
+
 =cut
 
 sub get_from_storage {
     my $self = shift @_;
+    my $attrs = shift @_;
     my @primary_columns = map { $self->$_ } $self->primary_columns;
-    return $self->result_source->resultset->search(undef, {execute_reliably=>1})->find(@primary_columns); 	
+    my $resultset = $self->result_source->resultset;
+    
+    if(defined $attrs) {
+    	$resultset = $resultset->search(undef, $attrs);
+    }
+    
+    return $resultset->find(@primary_columns);	
 }
 
 =head2 throw_exception

Modified: DBIx-Class/0.08/branches/replication_dedux/lib/DBIx/Class/Storage/DBI/Replicated/Balancer.pm
===================================================================
--- DBIx-Class/0.08/branches/replication_dedux/lib/DBIx/Class/Storage/DBI/Replicated/Balancer.pm	2008-07-07 19:16:32 UTC (rev 4558)
+++ DBIx-Class/0.08/branches/replication_dedux/lib/DBIx/Class/Storage/DBI/Replicated/Balancer.pm	2008-07-07 21:38:49 UTC (rev 4559)
@@ -163,8 +163,9 @@
 around 'select' => sub {
   my ($select, $self, @args) = @_;
   
-  if ($args[-1]->{execute_reliably}) {
-    return $self->master->select(@args);
+  if (my $forced_pool = $args[-1]->{force_pool}) {
+    delete $args[-1]->{force_pool};
+    return $self->_get_forced_pool($forced_pool)->select(@args); 
   } else {
     $self->increment_storage;
     return $self->$select(@args);
@@ -182,8 +183,9 @@
 around 'select_single' => sub {
   my ($select_single, $self, @args) = @_;
   
-  if ($args[-1]->{execute_reliably}) {
-    return $self->master->select_single(@args);
+  if (my $forced_pool = $args[-1]->{force_pool}) {
+  	delete $args[-1]->{force_pool};
+  	return $self->_get_forced_pool($forced_pool)->select_single(@args); 
   } else {
   	$self->increment_storage;
     return $self->$select_single(@args);
@@ -203,6 +205,25 @@
   $self->increment_storage;
 };
 
+=head2 _get_forced_pool ($name)
+
+Given an identifier, find the most correct storage object to handle the query.
+
+=cut
+
+sub _get_forced_pool {
+  my ($self, $forced_pool) = @_;
+  if(blessed $forced_pool) {
+    return $forced_pool;
+  } elsif($forced_pool eq 'master') {
+    return $self->master;
+  } elsif(my $replicant = $self->pool->replicants($forced_pool)) {
+    return $replicant;
+  } else {
+    $self->master->throw_exception("$forced_pool is not a named replicant.");
+  }   
+}
+
 =head1 AUTHOR
 
 John Napiorkowski <john.napiorkowski at takkle.com>

Modified: DBIx-Class/0.08/branches/replication_dedux/lib/DBIx/Class/Storage/DBI/Replicated.pm
===================================================================
--- DBIx-Class/0.08/branches/replication_dedux/lib/DBIx/Class/Storage/DBI/Replicated.pm	2008-07-07 19:16:32 UTC (rev 4558)
+++ DBIx-Class/0.08/branches/replication_dedux/lib/DBIx/Class/Storage/DBI/Replicated.pm	2008-07-07 21:38:49 UTC (rev 4559)
@@ -29,12 +29,28 @@
     [$dsn3, $user, $pass, \%opts],
   );
   
+  ## Now, just use the $schema as normal
+  $schema->resultset('Source')->search({name=>'etc'});
+  
+  ## You can force a given query to use a particular storage using the search
+  ### attribute 'force_pool'.  For example:
+  
+  my $RS = $schema->resultset('Source')->search(undef, {force_pool=>'master'});
+  
+  ## Now $RS will force everything (both reads and writes) to use whatever was
+  ## setup as the master storage.  'master' is hardcoded to always point to the
+  ## Master, but you can also use any Replicant name.  Please see:
+  ## L<DBIx::Class::Storage::Replicated::Pool> and the replicants attribute for
+  ## More. Also see transactions and L</execute_reliably> for alternative ways
+  ## to force read traffic to the master.
+  
 =head1 DESCRIPTION
 
-Warning: This class is marked ALPHA.  We are using this in development and have
-some basic test coverage but the code hasn't yet been stressed by a variety
-of databases.  Individual DB's may have quirks we are not aware of.  Please
-use this in development and pass along your experiences/bug fixes.
+Warning: This class is marked BETA.  This has been running a production
+website using MySQL native replication as it's backend and we have some decent
+test coverage but the code hasn't yet been stressed by a variety of databases.
+Individual DB's may have quirks we are not aware of.  Please use this in first
+development and pass along your experiences/bug fixes.
 
 This class implements replicated data store for DBI. Currently you can define
 one master and numerous slave database connections. All write-type queries
@@ -54,9 +70,8 @@
 
 The consistancy betweeen master and replicants is database specific.  The Pool
 gives you a method to validate it's replicants, removing and replacing them
-when they fail/pass predefined criteria.  It is recommened that your application
-define two schemas, one using the replicated storage and another that just 
-connects to the master.
+when they fail/pass predefined criteria.  Please make careful use of the ways
+to force a query to run against Master when needed.  
 
 =head1 ATTRIBUTES
 
@@ -624,6 +639,43 @@
   }
 }
 
+=head1 GOTCHAS
+
+Due to the fact that replicants can lag behind a master, you must take care to
+make sure you use one of the methods to force read queries to a master should
+you need realtime data integrity.  For example, if you insert a row, and then
+immediately re-read it from the database (say, by doing $row->discard_changes)
+or you insert a row and then immediately build a query that expects that row
+to be an item, you should force the master to handle reads.  Otherwise, due to
+the lag, there is no certainty your data will be in the expected state.
+
+For data integrity, all transactions automatically use the master storage for
+all read and write queries.  Using a transaction is the preferred and recommended
+method to force the master to handle all read queries.
+
+Otherwise, you can force a single query to use the master with the 'force_pool'
+attribute:
+
+  my $row = $resultset->search(undef, {force_pool=>'master'})->find($pk);
+
+This attribute will safely be ignore by non replicated storages, so you can use
+the same code for both types of systems.
+
+Lastly, you can use the L</execute_reliably> method, which works very much like
+a transaction.
+
+For debugging, you can turn replication on/off with the methods L</set_reliable_storage>
+and L</set_balanced_storage>, however this operates at a global level and is not
+suitable if you have a shared Schema object being used by multiple processes,
+such as on a web application server.  You can get around this limitation by
+using the Schema clone method.
+
+  my $new_schema = $schema->clone;
+  $new_schema->set_reliable_storage;
+  
+  ## $new_schema will use only the Master storage for all reads/writes while
+  ## the $schema object will use replicated storage.
+
 =head1 AUTHOR
 
   John Napiorkowski <john.napiorkowski at takkle.com>

Modified: DBIx-Class/0.08/branches/replication_dedux/t/93storage_replication.t
===================================================================
--- DBIx-Class/0.08/branches/replication_dedux/t/93storage_replication.t	2008-07-07 19:16:32 UTC (rev 4558)
+++ DBIx-Class/0.08/branches/replication_dedux/t/93storage_replication.t	2008-07-07 21:38:49 UTC (rev 4559)
@@ -569,18 +569,18 @@
        => 'Got expected single result from transaction';	  
 }     
 
-## Test the reliable_storage resultset attribute.
+## Test the force_pool resultset attribute.
 
 {
 	ok my $artist_rs = $replicated->schema->resultset('Artist')
         => 'got artist resultset';
 	   
-	## Turn on Reliable Storage
-	ok my $reliable_artist_rs = $artist_rs->search(undef, {execute_reliably=>1})
-        => 'Created a resultset using reliable storage';
+	## Turn on Forced Pool Storage
+	ok my $reliable_artist_rs = $artist_rs->search(undef, {force_pool=>'master'})
+        => 'Created a resultset using force_pool storage';
 	   
     ok my $artist = $reliable_artist_rs->find(2) 
-        => 'got an artist result via reliable storage';
+        => 'got an artist result via force_pool storage';
 }
 
 ## Delete the old database files




More information about the Bast-commits mailing list