最近Java(Spring)を使う機会があり、DB接続周りでAWS Advanced JDBC wrapperを試してみたのでその記録です。
やりたいことは以下です。
- Secrets ManagerからDBの認証情報を取得する。
- Failoverに対応する。
- WriterとReaderでクエリを振り分ける
※なお筆者はSpring初心者です。
目次
AWS Advanced JDBC wrapperとは
AWS作成のJDBCのWrapperです。Wrapperなので通常のMySQLやPostgreSQLのJDBCドライバーとセットで使う形になります。
プラグイン形式で認証情報の取得、Failover対応、参照系のクエリをリードレプリカに振り分けなど、さまざまな便利機能が使えます。
特にコネクションプール周りでFailover対応は厄介な事もあるので、非常に便利かなと思います。
この辺りはエムスリーさんのブログがわかりやすかったです。フェイルオーバーにおける問題点や、今回使用するWrapperではどのような仕組みで動作するかなどが詳しく解説されています。
ざっくり使い方
1. 依存関係の定義
今回はMavenを使用したので、pom.xml
に以下を追加しました。以下の3点になります。
- PostgreSQLのJDBCドライバー
- AWS Advanced JDBC wrapper(今回のメイン)
- Secrets ManagerのSDK(Secrets Managerを使用しない場合は不要)
<!-- https://mvnrepository.com/artifact/org.postgresql/postgresql --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.6.0</version> <scope>runtime</scope> </dependency> <!-- https://mvnrepository.com/artifact/software.amazon.jdbc/aws-advanced-jdbc-wrapper --> <dependency> <groupId>software.amazon.jdbc</groupId> <artifactId>aws-advanced-jdbc-wrapper</artifactId> <version>2.2.2</version> <scope>runtime</scope> </dependency> <!-- https://mvnrepository.com/artifact/software.amazon.awssdk/secretsmanager --> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>secretsmanager</artifactId> <version>2.20.115</version> <scope>runtime</scope> </dependency>
2. application.ymlの設定
Spring Bootのapplication.yml
で接続情報や各種プラグインの設定をしていきます。
以下設定例を記載します。よりチューニングなどをしたい場合はドキュメントを参照してください。
なお作成時には、以下のサンプルを参考にしています。 github.com
# Spring spring: datasource: # クラスターエンドポイントとデータベース名は置き換える url: jdbc:aws-wrapper:postgresql://<CLUSTER_ENDPOINT>:5432/<DB_NAME> driver-class-name: software.amazon.jdbc.Driver hikari: data-source-properties: # 以下のプラグインを使用 # ・failover:フェイルオーバー制御用 # ・readWriteSplitting:クエリに応じてReader, Writerの振り分け用の設定 # ・awsSecretsManager:Secrets Managerから認証情報を取得する wrapperPlugins: failover,readWriteSplitting,efm,awsSecretsManager wrapperDialect: aurora-pg # awsSecretsManager用の設定。 # secretsManagerSecretIdはID or ARNを設定。ARNを設定した場合、secretsManagerRegionは不要 secretsManagerSecretId: <SECRET_ID> secretsManagerRegion: ap-northeast-1 exception-override-class-name: software.amazon.jdbc.util.HikariCPSQLException # 以下Hikariに関する設定 max-lifetime: 840000 minimum-idle: 20 maximum-pool-size: 20 idle-timeout: 900000 # JPA jpa: hibernate: ddl-auto: none open-in-view: false properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect logging: level: software: amazon: # ログ出力用。なおTRACEにするとSecrets Managerの認証情報もログに流れるので注意してください。 jdbc: TRACE
これを元に後は従来通り実装していくだけで、以下2つは実現できます。
- Secrets ManagerからDBの認証情報を取得する。
- Failoverに対応する。
3. WriterとReaderでクエリを振り分けるための設定
Read Write Splitting Pluginのドキュメントを見ると、ReadOnlyの設定を行うことで、接続先をリーダーに切り替えることが示されています。
Upon calling setReadOnly(true), the plugin will establish a connection to a reader instance and direct subsequent queries to this instance. Future calls to setReadOnly will switch between the established writer and reader connections according to the boolean argument you supply to the setReadOnly method.
これはServiceで@Transactional(readOnly = true)
とすることで実現できました。以下実装イメージです。
ここではCrudRepository#findByIdに対して適用しています。
import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.repository.EmployeeRepository; @Service public class EmployeeService { @Autowired private EmployeeRepository repository; // readOnlyをつけるとReaderに対してクエリを実行する。 @Transactional(readOnly = true) public Employee findById(String id) { Optional<Employee> employee = repository.findById(id); return employee.get(); } }
※なお @Transactional(readOnly = true)
による Write と Read の分割は、internal connection pools での利用が推奨されています。
上記のHIkariCPのようなexternal connection pools の場合はReadとWriteの切り替えのオーバーヘッドが発生し、パフォーマンスが低下する可能性があります(読み取り操作はリーダーエンドポイントに接続して処理することが推奨されている)。
詳細は下記ドキュメントも確認しましょう。
動作確認
Failoverの検証はクラメソさんの記事が非常に参考なります。
なのでFailoverの検証は割愛し、WriterとReaderでクエリを振り分けることができているかの確認をします。
まずは@Transactional(readOnly = true)
を付与したメソッド実行時に、Readerに対して処理が実行されているかを確認します。
以下実装例の再掲になります。
// readOnlyをつけるとReaderに対してクエリを実行する。 @Transactional(readOnly = true) public Employee findById(String id) { Optional<Employee> employee = repository.findById(id); return employee.get(); }
以下Read Write Splitting Plugin
のログの抜粋になります。少しわかりづらいですが以下のように接続先をWriter<->Readerで切り替えていることが読み取れます。
- 6行目でWriterからReaderにスイッチ(
Switched from a writer to a reader host. New reader host: 'xxxxx.xxxxx.ap-northeast-1.rds.amazonaws.com:5432/'
) - 中略後の3行目でReaderからWriterにスイッチ(
Switched from a reader to a writer host. New writer host: 'xxxxxx.xxxxx.ap-northeast-1.rds.amazonaws.com:5432/'
)
2023-08-01 07:59:31.240 TRACE 2712 --- [0.1-8009-exec-1] s.a.j.p.r.ReadWriteSplittingPlugin : Successfully connected to a new reader host: 'xxxxx.xxxxx.ap-northeast-1.rds.amazonaws.com:5432/' 2023-08-01 07:59:31.241 TRACE 2712 --- [0.1-8009-exec-1] s.a.j.p.r.ReadWriteSplittingPlugin : Reader connection set to 'xxxxx.xxxxx.ap-northeast-1.rds.amazonaws.com:5432/' 2023-08-01 07:59:31.246 TRACE 2712 --- [0.1-8009-exec-1] s.a.j.p.r.ReadWriteSplittingPlugin : Reader connection set to 'xxxxx.xxxxx.ap-northeast-1.rds.amazonaws.com:5432/' 2023-08-01 07:59:31.247 TRACE 2712 --- [0.1-8009-exec-1] s.a.j.p.r.ReadWriteSplittingPlugin : Setting the current connection to 'xxxxx.xxxxx.ap-northeast-1.rds.amazonaws.com:5432/' 2023-08-01 07:59:31.247 DEBUG 2712 --- [0.1-8009-exec-1] s.a.j.p.r.ReadWriteSplittingPlugin : Switched from a writer to a reader host. New reader host: 'xxxxx.xxxxx.ap-northeast-1.rds.amazonaws.com:5432/' ~~~ 中略 ~~~~ 2023-08-01 07:59:31.539 TRACE 2712 --- [0.1-8009-exec-1] s.a.j.p.r.ReadWriteSplittingPlugin : Writer connection set to 'xxxxxx.xxxxx.ap-northeast-1.rds.amazonaws.com:5432/' 2023-08-01 07:59:31.539 TRACE 2712 --- [0.1-8009-exec-1] s.a.j.p.r.ReadWriteSplittingPlugin : Setting the current connection to 'xxxxxx.xxxxx.ap-northeast-1.rds.amazonaws.com:5432/' 2023-08-01 07:59:31.539 DEBUG 2712 --- [0.1-8009-exec-1] s.a.j.p.r.ReadWriteSplittingPlugin : Switched from a reader to a writer host. New writer host: 'xxxxxx.xxxxx.ap-northeast-1.rds.amazonaws.com:5432/'
試しに@Transactional(readOnly = false)
としてやってみましたが、その場合は上記のRead Write Splitting Plugin
に関するログは出力されていませんでした。readOnly
の設定が効いていることがわかります。
終わりに(余談)
Spring×AWSの勉強を最近しているのですが、以下の書籍が非常にわかりやすくておすすめです(Kindle Unlimitedで読めるのも良い)。
ある程度AWSとSpringの知識があること前提ですが、程よい難易度でメジャーなAWSのサービスとSpringの連携について学ぶことができます。