end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

perlのDBIx::ClassによるAWS RDS(mysql)接続中のフェイルオーバを考える

amazon web servicesのRDS は、手軽に冗長構成を手に入れることができますが、DBの一貫性を保ちながらサービスするには、気を使いますよね?
で、考えてみました。

AWS RDS のフェイルオーバとは?

AWSで用意されているRDB(RDS)は、冗長構成を選択でき、一方のRDSが障害により停止すると、もう一方のRDSに自動制御的に切り替えられます。

この切替の際、DNS?により 〜.rds.amazonaws.com に割り当てられるIPが書き換えられる為、アプリは、切替によるIP書き換え?を必要としません。

http://blog.suz-lab.com/2011/06/rds.html
http://aws.amazon.com/jp/rds/faqs/

フェイルオーバによる悪影響とは?

-- TRANSACTION ------------------>
-- RDS A--->      -- RDS B---->

AutoCommit==true な状態は別として、
上記のように同一トランザクション中にRDS切替が発生すると、ACID特性の中では、一貫性が保てませんよね? きっと

DBI や、DBIx::Class の再接続の挙動は?

初めに DBI や、DBIx::Class の再接続の挙動を試したところ、次のような結果となりました。

http://search.cpan.org/perldoc?DBD%3A%3Amysql
http://search.cpan.org/dist/DBIx-Class/

DBIDBIC も、DBD::mysqlに依存しているにも関わらず、挙動が異なるのが、面白いですね。

DBIは、DBD::mysql の仕様に従い、mysql_auto_reconnect や $ENV{MOD_PERL} の設定により再接続していますが、
DBICは、mysql_auto_reconnect 等に関わらず、DBIx::Class::Storage::DBI::mysql で再接続します。

(上記urlのドキュメントやsrcも合せて読むと、理解できると思います)

DBI
-------------------------------------------------------------------
COND:                                                   RESULT:
FailOver        AutoCommit      mysql_auto_reconnect    RE-CONNECT
-------------------------------------------------------------------
NO              ON              ON                      OK
YES             ON              ON                      OK
NO              ON              OFF                     NG
YES             ON              OFF                     NG
NO              OFF             ON                      NG
YES             OFF             ON                      NG
NO              OFF             OFF                     NG
YES             OFF             OFF                     NG
-------------------------------------------------------------------
DBIC
-------------------------------------------------------------------
COND:                                                   RESULT:
FailOver        AutoCommit      mysql_auto_reconnect    RE-CONNECT
-------------------------------------------------------------------
NO              ON              ON                      OK
YES             ON              ON                      NG
NO              ON              OFF                     OK
YES             ON              OFF                     OK
NO              OFF             ON                      -
YES             OFF             ON                      -
NO              OFF             OFF                     -
YES             OFF             OFF                     -
-------------------------------------------------------------------

結局、DBIx::Class での対策は?

DBIx::Class::Storage::DBI::mysql の子クラスを作成し、DBIx::Class::Storage::DBI::mysql の再接続method ( ensure_connected() )をオーバーライドしました。

この新しい ensure_connected() 内で AutoCommit や mysql_auto_reconnect の設定に従い、再接続の有無をコントロールしています。

srcは、以下の通り。

スキーマクラス
package TestSchema;
use strict;
use warnings;
use base qw/DBIx::Class::Schema/;
use TestSchemaStorage;

my $DB_CONF =
    {db_name=>'rdstest',
     host=>'ないしょ.cbsplvtrcd5c.us-west-2.rds.amazonaws.com',
     port=>'3306',
     db_user=>'ないしょ',
     db_pass=>'ないしょ',
     db_opt=>
     {AutoCommit=> 1,
      PrintError=> 0,
      RaiseError=> 1,
      ShowErrorStatement =>1,
      AutoInactiveDestroy=>1,
      mysql_enable_utf8  =>1,
      mysql_auto_reconnect=> 0} #### 再接続禁止設定
    };

my $DB_STR = join(';',
                  "DBI:mysql:database=$DB_CONF->{db_name}",
                  "host=$DB_CONF->{host}",
                  "port=$DB_CONF->{port}");

#### 自作する DBIx::Class::Storage::DBI::mysqlの子
__PACKAGE__
    ->storage_type('TestSchemaStorage');
__PACKAGE__
    ->connection($DB_STR,
                 $DB_CONF->{db_user},
                 $DB_CONF->{db_pass},
                 $DB_CONF->{db_opt});

__PACKAGE__->load_classes(qw/User/);

1;
__END__
ストレージクラス ( DBIx::Class::Storage::DBI::mysqlの子 )
package TestSchemaStorage;
use strict;
use warnings;
use base qw{DBIx::Class::Storage::DBI::mysql};
use Data::Dumper;

# sub _connect {
#     print STDERR "_CONNECT\n";
#     my $self = shift;
#     my $dbh = $self->SUPER::_connect(@_);
#     $dbh->{mysql_auto_reconnect} = 0 if $dbh;
#     return $dbh;
# }

sub ensure_connected {
#    my @caller = caller();

    ## ORIGINAL-> $_[0]->connected || ( $_[0]->_populate_dbh && 1 );

    return if $_[0]->connected;

    if( $_[0]->_dbic_connect_attributes->{mysql_auto_reconnect} or
        $_[0]->_dbic_connect_attributes->{AutoCommit} ) {

        print STDERR "re-connecting to mysql\n";
        ($_[0]->_populate_dbh && 1);
    }
}

1;
__END__