この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
ども、大瀧です。
Amazon Linux 2015.09RC版のリリース告知に"Amazon Aurora JDBC driver"という項目があり、調べてみるとAuroraの可用性をアップさせるナイスな機能だったのでご紹介してみたいと思います。
RDS Multi-AZの"普通の"フェイルオーバー
Amazon RDSで提供される可用性機能としてMulti-AZがあります。一般に言うActive-Standbyクラスタで、エンドポイントとなるDNSレコードのIPアドレスをアクティブノードに向ける、DNSベースの仕組みとなります。
以下のように、Masterノード障害を検知すると、エンドポイントのレコードをSlaveノードのIPアドレスに切り替える要領です。
AuroraでもCluster Endpointというエンドポイントが提供され、フェイルオーバーを実装することができます。しかし、DNSという仕組み上レコードのTTL(Time To Live)が切り替えまでの遅延時間に追加されるため、遅延を短くするのには限界があります。
MariaDB Connector/Jのフェイルオーバー
MariaDB Connector/JはMariaDBのJDBC(JavaのDBライブラリ)ドライバーです。バージョン1.2でフェイルオーバーおよび高可用性機能が追加され、サポートするクラスタタイプの一つとしてAuroraがサポートされます。
以下の動作の記述は、AWSによる正式な説明に寄るものでは無く私の独自検証による推測が多分にあります。動作を保証できるものではない情報である点、ご了承ください。また、誤りがあればご指摘ください。
MariaDB Connector/JのAurora実装では、Auroraの全ノードと常時接続しWriterノード(AuroraのMaster)をノードとの通信で判断します。Writerノードの判断はAuroraのドキュメントにあるグローバル変数innodb_read_only
を利用し、OFF
となっているWriterノードを検索、通常時はそのノードにクエリを発行するようにドライバーが動作します。
クラスタはWriterノードの障害を検知するとinnodb_read_only
を速やかに変更するので、ドライバーは新たなWriterノード検出のためにReaderノード(Auroraのリードレプリカ)を検索し、OFF
のノードを新たなWriterノードとして以後のクエリ発行先を変更します。
こうすることでDNSに依らない、より短い時間での切り替えを実現しています。具体的なコードはGitHubで確認できます。
動作検証
動作確認環境
- AMI: amzn-ami-hvm-2015.03.1.x86_64-gp2 *1 (ami-d5c5d1e5) (オレゴンリージョン)
サンプルのJavaプログラムを作成し、実行してみました。
aurora_sample.java
import java.sql.*;
class AuroraSample {
public static void main (String[] args) {
Connection conn = null;
try {
Class.forName("org.mariadb.jdbc.Driver");
conn = DriverManager.getConnection(
"jdbc:mysql:aurora://"
+ "mydb.xxxxxxxxxxxx.us-west-2.rds.amazonaws.com:3306,"
+ "mydb-us-west-2b.xxxxxxxxxxxx.us-west-2.rds.amazonaws.com:3306"
+ "/mydb?user=master&password=masterpass"
);
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
while(true) {
try {
Statement stmt = conn.createStatement();
System.out.print(new java.util.Date());
ResultSet rset = stmt.executeQuery("SELECT id, name FROM users");
while ( rset.next() ) {
System.out.println(rset.getInt(1) + "\t" + rset.getString(2));
}
rset.close();
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
1秒おきにSELECT文を発行するシンプルな物です。DriverManager.getConnection()
に渡す接続先に、Auroraクラスタの全ノードのInstance Endpointを記述します。そのため、後からのレプリカ追加の度に接続先を変更する必要がある点には注意しましょう。また、Cluster Endpointは使用しません。コンパイル、実行するとこんな感じです。
$ sudo yum install mariadb-connector-java java-1.7.0-openjdk-devel -y
$ echo 'export CLASSPATH=/usr/share/java/mariadb-connector-java.jar:.' >> .bash_profile
$ source .bash_profile
$ javac aurora_sample.java
$ java AuroraSample
Tue Sep 01 10:21:59 UTC 20151 ryuta
Tue Sep 01 10:22:01 UTC 20151 ryuta
:
では、Auroraクラスタでフェイルオーバーを実行してみます。
Tue Sep 01 10:08:54 UTC 20151 ryuta
Tue Sep 01 10:08:55 UTC 20151 ryuta
Tue Sep 01 10:08:56 UTC 20151 ryuta
Tue Sep 01 10:08:57 UTC 20159 01, 2015 10:08:57 午前 org.mariadb.jdbc.internal.mysql.listener.AbstractMastersSlavesListener handleFailover
警告: SQL Primary node [HostAddress{host='mydb-us-west-2a.cw5eqrkofj2u.us-west-2.rds.amazonaws.com', port=3306, type='null'}] connection fail
9 01, 2015 10:08:57 午前 org.mariadb.jdbc.internal.mysql.listener.impl.AuroraListener searchByStartName
警告: SQL Secondary node [HostAddress{host='mydb-us-west-2a.cw5eqrkofj2u.us-west-2.rds.amazonaws.com', port=3306, type='null'}] connection fail
java.sql.SQLNonTransientConnectionException: Communications link failure with primary host mydb-us-west-2a.cw5eqrkofj2u.us-west-2.rds.amazonaws.com:3306. Could not read resultset: unexpected end of stream, read 0 bytes from 4. Driver will try to reconnect primary after 49908 milliseconds or after 29 query(s)
at org.mariadb.jdbc.internal.SQLExceptionMapper.get(SQLExceptionMapper.java:136)
at org.mariadb.jdbc.internal.SQLExceptionMapper.throwException(SQLExceptionMapper.java:106)
at org.mariadb.jdbc.MySQLStatement.executeQueryEpilog(MySQLStatement.java:252)
at org.mariadb.jdbc.MySQLStatement.execute(MySQLStatement.java:278)
at org.mariadb.jdbc.MySQLStatement.executeQuery(MySQLStatement.java:333)
at org.mariadb.jdbc.MySQLStatement.executeQuery(MySQLStatement.java:392)
at AuroraSample.main(aurora_sample.java:25)
Caused by: org.mariadb.jdbc.internal.common.QueryException: Communications link failure with primary host mydb-us-west-2a.cw5eqrkofj2u.us-west-2.rds.amazonaws.com:3306. Could not read resultset: unexpected end of stream, read 0 bytes from 4. Driver will try to reconnect primary after 49908 milliseconds or after 29 query(s)
at org.mariadb.jdbc.internal.mysql.MySQLProtocol.getResult(MySQLProtocol.java:968)
at org.mariadb.jdbc.internal.mysql.MySQLProtocol.executeQuery(MySQLProtocol.java:1038)
at org.mariadb.jdbc.internal.mysql.MySQLProtocol.executeQuery(MySQLProtocol.java:1020)
at sun.reflect.GeneratedMethodAccessor3.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.mariadb.jdbc.internal.mysql.listener.AbstractMastersListener.invoke(AbstractMastersListener.java:275)
at org.mariadb.jdbc.internal.mysql.FailoverProxy.invoke(FailoverProxy.java:124)
at com.sun.proxy.$Proxy0.executeQuery(Unknown Source)
at org.mariadb.jdbc.MySQLStatement.execute(MySQLStatement.java:271)
... 3 more
Caused by: java.io.EOFException: unexpected end of stream, read 0 bytes from 4
at org.mariadb.jdbc.internal.common.packet.buffer.ReadUtil.readFully(ReadUtil.java:84)
at org.mariadb.jdbc.internal.common.packet.buffer.ReadUtil.readFully(ReadUtil.java:92)
at org.mariadb.jdbc.internal.common.packet.RawPacket.nextPacket(RawPacket.java:77)
at org.mariadb.jdbc.internal.common.packet.SyncPacketFetcher.getRawPacket(SyncPacketFetcher.java:67)
at org.mariadb.jdbc.internal.mysql.MySQLProtocol.getResult(MySQLProtocol.java:931)
... 12 more
Tue Sep 01 10:08:58 UTC 20151 ryuta
Tue Sep 01 10:08:59 UTC 20151 ryuta
Tue Sep 01 10:09:00 UTC 20151 ryuta
Tue Sep 01 10:09:01 UTC 20151 ryuta
Tue Sep 01 10:09:02 UTC 20151 ryuta
Tue Sep 01 10:09:04 UTC 20151 ryuta
Tue Sep 01 10:09:05 UTC 20151 ryuta
フェイルオーバーに関する例外の出力がありつつ、例外発生の後1〜2秒で正常な出力になっていることがわかりますね。今回は人為的なフェイルオーバーのため実際の障害に比べて検知時間が短い形にはなりますが、DNSベースのTTL(AuroraのCluster Endpointレコード)が5秒なので、それよりも切り替え時間が短いと言えます。
まとめ
MariaDB Connector/JによるAmazon Auroraの高速フェイルオーバーの仕組みとサンプルをご紹介しました。Java向けの実装でしたが、仕組み自体は汎用のものですので、他のJavaフレームワークや言語への移植に期待したいところですね。
参考URL
- About MariaDB Connector/J - MariaDB Knowledge Base
- Failover and High availability with MariaDB Connector/J - MariaDB Knowledge Base
脚注
- 2015.03.1でもyumリポジトリに含まれていました ↩