Sanity check.
I need to have DBIC work with an existing database that includes slaves. As
the Replicated docs note, there's often a replication delay from master to
slaves. So, when a write happens I want to direct all reads to the master
for some period of time (for the specific user that did the write). In
addition, I need a way to communicate to other applications/processes that
they also need to use the master for any requests by that user.
The communication between applications is done via memcached.
I'm using Postgresql and Slony. I have not looked into implementing
lag_behind_master (as I'm not clear how that works in ::Replicated).
The existing (non-DBIC) application will set a flag in memcached when a
write happens. This is keyed by user id. And each request memcached is
checked to see if the current user needs to read from the master. I'm
looking at a way to duplicate that behavior with ::Replicated.
I'd like to know if this seems like a reasonable approach, and if anyone
sees any gotchas that I need to be aware of.
First, I subclass ::Replicated in Catalyst::Model::DBIC::Schema config via
storage_type => 'MyApp::DB::Replicated'. The point of this subclass is two
things: 1) set a flag in memcached and 2) force all reads to the master for
the remainder of the request.
This subclass looks like:
package MyApp::DB::Replicated;
use Moose;
extends 'DBIx::Class::Storage::DBI::Replicated';
use namespace::autoclean;
has flag_write => ( is => 'rw' );
my @methods = qw/
insert
insert_bulk
update
delete
/;
after \...@methods => sub {
my $self = shift;
$self->flag_write->();
$self->set_reliable_storage;
};
__PACKAGE__->meta->make_immutable;
1;
So, after the methods listed a sub is called to flag (in memcached) that a
write happened, and then set_reliable_storage is forced on to make any
subsequent reads go to the master (for the remainder of the request).
Replicated will force all reads to the master for reads inside a
transaction, but a single request might span multiple transactions (and
selects outside of a txn_do), so forcing it for the reminder of the request
seems the best option.
Now, in the Catalyst Model I need a way to force reads to the master, and
also set memcached when a write to the master happens:
before 'ACCEPT_CONTEXT' => sub {
my ( $self, $c ) = @_;
my $schema = $self->schema;
my $storage = $schema->storage;
return if $c->stash->{_replicated_set}++
|| !$storage->isa( 'DBIx::Class::Storage::DBI::Replicated' );
# callback to flag that reads go to master.
$storage->flag_write( sub { $self->force_master($c, 1) } );
# Should all reads go to master?
if ( $self->force_master( $c ) ) {
$storage->set_reliable_storage;
}
else {
$storage->set_balanced_storage;
}
};
That doesn't feel bullet proof by any means, but does this seem like a good
way to hook into DBIC for this?
BTW -- Do you think ::Replicated should load any class specified by
"storage_type"? I'm having to explicitly "use" my subclass.
Thanks,
--
Bill Moseley
[email protected]
_______________________________________________
List: http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/dbix-class
IRC: irc.perl.org#dbix-class
SVN: http://dev.catalyst.perl.org/repos/bast/DBIx-Class/
Searchable Archive: http://www.grokbase.com/group/[email protected]