mazyu36の日記

某SIer所属のクラウドエンジニアのブログ

Spring BootでAWS Advanced JDBC wrapperを使ってAurora PotgreSQLに接続する

最近Java(Spring)を使う機会があり、DB接続周りでAWS Advanced JDBC wrapperを試してみたのでその記録です。

やりたいことは以下です。

  • Secrets ManagerからDBの認証情報を取得する。
  • Failoverに対応する。
  • WriterとReaderでクエリを振り分ける

※なお筆者はSpring初心者です。

目次

AWS Advanced JDBC wrapperとは

AWS作成のJDBCのWrapperです。Wrapperなので通常のMySQLPostgreSQLJDBCドライバーとセットで使う形になります。

プラグイン形式で認証情報の取得、Failover対応、参照系のクエリをリードレプリカに振り分けなど、さまざまな便利機能が使えます。

github.com

特にコネクションプール周りでFailover対応は厄介な事もあるので、非常に便利かなと思います。

この辺りはエムスリーさんのブログがわかりやすかったです。フェイルオーバーにおける問題点や、今回使用するWrapperではどのような仕組みで動作するかなどが詳しく解説されています。

www.m3tech.blog

ざっくり使い方

1. 依存関係の定義

今回はMavenを使用したので、pom.xmlに以下を追加しました。以下の3点になります。

  • PostgreSQLJDBCドライバー
  • 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

なお作成時には、以下のサンプルを参考にしています。 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の切り替えのオーバーヘッドが発生し、パフォーマンスが低下する可能性があります(読み取り操作はリーダーエンドポイントに接続して処理することが推奨されている)。

詳細は下記ドキュメントも確認しましょう。

github.com

動作確認

Failoverの検証はクラメソさんの記事が非常に参考なります。

dev.classmethod.jp

なので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の連携について学ぶことができます。