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/
DBI も DBIC も、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__