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]

Reply via email to