Mariadb、C3P0、Aurora環境で発生!Aurora フェイルオーバー後の読み取り専用接続問題を完全網羅
Aurora フェイルオーバー後、接続が読み取り専用状態になる問題:詳細解説
Aurora クラスタのフェイルオーバー後、一部の接続が読み取り専用状態となり、書き込み操作が実行できなくなる現象が発生することがあります。この問題は、主に mariadb
、c3p0
、amazon-aurora
などのライブラリやコネクションプールを使用する環境で顕著に発生します。
原因
この問題は、フェイルオーバー時に Aurora マスタノードとクライアント間の接続が切断され、新しいマスタノードへの再接続処理が適切に行われないことが原因で発生します。具体的には、以下の要因が考えられます。
- ライブラリのキャッシュ: 一部のライブラリは、フェイルオーバー後も古い接続情報をキャッシュし続け、新しいマスタノードへの接続に切り替えられない場合があります。
- コネクションプールの設定: コネクションプールの設定によっては、フェイルオーバー後も古い接続を使い続けようとし、新しいマスタノードへの接続が漏れる場合があります。
- DNS のキャッシュ: DNS キャッシュの影響により、クライアントがフェイルオーバー後の新しいマスタノードのアドレスを即座に認識できない場合があります。
影響
この問題は、アプリケーションが Aurora クラスタへの書き込み操作を実行できなくなるため、重大な影響を与える可能性があります。具体的には、以下のような問題が発生する可能性があります。
- データ更新の失敗
- トランザクション処理のエラー
- アプリケーションの停止
解決策
この問題を解決するには、以下の対策を実施することが重要です。
- ライブラリのアップデート: 使用しているライブラリが最新バージョンであることを確認し、必要に応じてアップデートを実施します。最新バージョンでは、フェイルオーバー時の接続処理が改善されている可能性があります。
- コネクションプールの設定確認: コネクションプールの設定を確認し、フェイルオーバー後も古い接続が使われないように設定します。具体的には、接続の存続時間や検証機構などを適切に設定する必要があります。
- DNS キャッシュの無効化: アプリケーション側で DNS キャッシュを無効化するか、短い存続時間に設定することで、DNS キャッシュの影響を抑制することができます。
- アプリケーションの再起動: フェイルオーバー後、アプリケーションを再起動することで、古い接続情報がクリアされ、新しいマスタノードへの接続が確立される場合があります。
予防策
この問題を予防するために、以下の対策も有効です。
- Aurora レプリケーションの活用: Aurora レプリケーションを使用することで、フェイルオーバー時にデータ損失を回避することができます。
- ヘルスチェックの実装: アプリケーション側でヘルスチェックを実装することで、Aurora マスタノードの健全性を監視し、問題発生時に自動的にフェイルオーバー処理を実行することができます。
補足
上記の解決策は一般的な指針であり、具体的な状況によっては異なる対策が必要となる場合があります。問題が発生した場合は、Aurora のドキュメントを参照するか、AWS サポートに問い合わせてください。
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.beans.PropertyVetoException;
import java.sql.SQLException;
public class AuroraFailoverExample {
public static void main(String[] args) throws PropertyVetoException, SQLException {
// Create a ComboPooledDataSource instance
ComboPooledDataSource dataSource = new ComboPooledDataSource();
// Set connection pool properties
dataSource.setJdbcUrl("jdbc:mysql://aurora-cluster.cluster-endpoint.us-east-1.rds.amazonaws.com:3306/mydatabase");
dataSource.setUser("myusername");
dataSource.setPassword("mypassword");
dataSource.setMinPoolSize(5);
dataSource.setMaxPoolSize(20);
dataSource.setIdleTimeout(300);
dataSource.setMaxIdleTime(600);
dataSource.setTestConnectionOnCheckout(true);
dataSource.setTestConnectionOnCheckin(true);
dataSource.setUnreturnedConnectionTimeout(600);
dataSource.setPreferredTestQuery("SELECT 1");
// Set failover properties
dataSource.setAutoCommitOnReturn(false);
dataSource.setAcquireRetryDelay(1000);
dataSource.setAcquireIncrement(5);
dataSource.setMaxFailedAttempts(5);
dataSource.setFailedConnectionTimeout(30000);
// Get a connection from the pool
try (java.sql.Connection connection = dataSource.getConnection()) {
// Use the connection
System.out.println("Connected to Aurora database");
}
}
}
This code configures the c3p0 connection pool to use the following failover properties:
- autoCommitOnReturn: This property should be set to
false
to ensure that transactions are not automatically committed when connections are returned to the pool. This is important because Aurora may fail over to a different instance during a transaction, and the new instance may not have the same transaction state as the old instance. - acquireRetryDelay: This property specifies the amount of time (in milliseconds) to wait between connection attempts when a connection cannot be obtained from the pool. This gives the Aurora cluster time to recover from a failover.
- acquireIncrement: This property specifies the number of connections to add to the pool when the pool is empty. This helps to ensure that there are enough connections available to handle a sudden increase in demand.
- maxFailedAttempts: This property specifies the maximum number of times that the connection pool will attempt to obtain a connection before giving up. This helps to prevent the application from getting stuck in a loop trying to connect to the database.
By setting these properties, you can help to ensure that your application can handle Aurora failovers gracefully and without any data loss.
- Use the Aurora JDBC Driver: The Aurora JDBC Driver provides built-in failover support. When a connection is lost, the driver will automatically attempt to reconnect to the new master instance. To use the Aurora JDBC Driver, you will need to add the following dependency to your project's pom.xml file:
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>amazon-aurora-jdbc</artifactId>
<version>1.10.1</version>
</dependency>
Once you have added the dependency, you can use the following code to connect to the Aurora database:
import java.sql.Connection;
import java.sql.DriverManager;
public class AuroraFailoverExample {
public static void main(String[] args) throws SQLException {
// Load the Aurora JDBC Driver
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// Connect to the Aurora database
Connection connection = DriverManager.getConnection("jdbc:mysql://aurora-cluster.cluster-endpoint.us-east-1.rds.amazonaws.com:3306/mydatabase", "myusername", "mypassword");
// Use the connection
System.out.println("Connected to Aurora database");
connection.close();
}
}
- Use the AWS SDK for Java: The AWS SDK for Java provides a high-level API for interacting with Aurora. The SDK includes a
DatabaseClient
class that you can use to connect to the Aurora database and manage connections. TheDatabaseClient
class automatically handles failovers, so you do not need to worry about configuring connection pool properties. To use the AWS SDK for Java, you will need to add the following dependency to your project's pom.xml file:
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-rds</artifactId>
<version>1.11.100</version>
</dependency>
import com.amazonaws.services.rds.AmazonRDSClient;
import com.amazonaws.services.rds.model.DescribeDBInstancesRequest;
import com.amazonaws.services.rds.model.DescribeDBInstancesResult;
import com.amazonaws.services.rds.model.Instance;
import java.sql.Connection;
import java.sql.DriverManager;
public class AuroraFailoverExample {
public static void main(String[] args) throws SQLException {
// Create an AmazonRDSClient instance
AmazonRDSClient rdsClient = new AmazonRDSClient();
// Describe the Aurora DB instances
DescribeDBInstancesRequest request = new DescribeDBInstancesRequest();
request.setDBInstanceIdentifiers(Collections.singletonList("my-aurora-instance"));
DescribeDBInstancesResult result = rdsClient.describeDBInstances(request);
// Get the endpoint of the master instance
Instance instance = result.getDBInstances().get(0);
String endpoint = instance.getEndpoint().getAddress();
// Connect to the Aurora database
Connection connection = DriverManager.getConnection("jdbc:mysql://" + endpoint + ":3306/mydatabase", "myusername", "mypassword");
// Use the connection
System.out.println("Connected to Aurora database");
connection.close();
}
}
- Use a load balancer: You can use a load balancer to distribute traffic across multiple Aurora instances. This can help to improve the availability of your application and make it more resilient to failovers. To use a load balancer, you will need to create a load balancer in your AWS account and configure it to route traffic to your Aurora instances.
The best method for handling Aurora failovers will depend on your specific requirements. If you are using a connection pool, then you should configure the pool to use the appropriate failover properties. If you are using the Aurora JDBC Driver or the AWS SDK for Java, then you do not need to configure any additional failover settings. And if you are using a load balancer, then you will need to configure the load balancer to route traffic to your Aurora instances.
No matter which method you choose, it is important to test your failover procedures to make sure that your application can recover from a failover quickly and without any data loss.
mariadb c3p0 amazon-aurora