はじめに
Azureが提供しているデータベースには、下記のドキュメントにあるように一時的な接続エラーに対処する必要があります。これをSpring Bootで実装してみます。
- 一時的なエラーへの対応 - Azure SQL Database | Microsoft Docs
- 一時的な接続エラー - Azure Database for MySQL | Microsoft Docs
- Azure Database for PostgreSQL - Single Server の一時的な接続エラーに対処する | Microsoft Docs
以下の記事で簡単なリトライ処理については確認しましたが、トランザクションをかけた場合はうまくリトライできませんでした。
今回はその対応をします。
実装
@Transactional
をつけているメソッドに対して@Retryable
をつけてあげます。以下のような感じです。
@Retryable(maxAttempts = 12, backoff = @Backoff(delay = 5_000, maxDelay = 10_000)) @Transactional public void insert() { log.info("Start insert method..."); // 省略 log.info("End insert method..."); }
maxAttempts
やdelay
などの値は適宜調整してください。調整のためのヒントとしては、マイクロソフトが公開している上記のドキュメントがあります。マイクロソフトのドキュメントには以下のように書かれています。
初回再試行の前には 5 秒間待機します。
以降再試行するたびに、60 秒を上限として、待ち時間を指数関数的に増やします。
操作が失敗したとアプリケーションで見なされる最大再試行回数を設定します。
これを元に5秒x12回のリトライをするようにしています。
確認
動作を確認してみます。
(省略) 2021-10-26 11:46:08.567 INFO 18 --- [ main] com.github.miyohide.retry.MyService : Insert record. i = [38], first_name = [Josh00038], last_name = [hogehoge00038]" 2021-10-26 11:46:09.571 INFO 18 --- [ main] com.github.miyohide.retry.MyService : Insert record. i = [39], first_name = [Josh00039], last_name = [hogehoge00039]" at java.base/sun.security.ssl.SSLSocketImpl$AppOutputStream.write(Unknown Source) ~[na:na]" at org.springframework.retry.interceptor.RetryOperationsInterceptor$1.doWithRetry(RetryOperationsInterceptor.java:93) ~[spring-retry-1.3.1.jar:na]" at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61) ~[HikariCP-4.0.3.jar:na]" at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.12.jar:5.3.12]" at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:960) ~[spring-jdbc-5.3.12.jar:5.3.12]" at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:401) ~[postgresql-42.2.24.jar:42.2.24]" at org.springframework.retry.interceptor.RetryOperationsInterceptor.invoke(RetryOperationsInterceptor.java:119) ~[spring-retry-1.3.1.jar:na]" at com.github.miyohide.retry.RetryApplication.run(RetryApplication.java:22) ~[classes/:na]" at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.12.jar:5.3.12]" at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:349) ~[postgresql-42.2.24.jar:42.2.24]" at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:791) ~[spring-boot-2.5.6.jar:2.5.6]" at com.github.miyohide.retry.MyService$$EnhancerBySpringCGLIB$$30791361.insert(<generated>) ~[classes/:na]" at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:1025) ~[spring-jdbc-5.3.12.jar:5.3.12]" at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:329) ~[spring-retry-1.3.1.jar:na]" at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.invoke(AnnotationAwareRetryOperationsInterceptor.java:163) ~[spring-retry-1.3.1.jar:na]" org.postgresql.util.PSQLException: An I/O error occurred while sending to the backend." at org.postgresql.core.v3.QueryExecutorImpl.sendSync(QueryExecutorImpl.java:1482) ~[postgresql-42.2.24.jar:42.2.24]" at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:775) ~[spring-boot-2.5.6.jar:2.5.6]" 2021-10-26 11:46:09.592 WARN 18 --- [ main] com.zaxxer.hikari.pool.ProxyConnection : HikariPool-1 - Connection org.postgresql.jdbc.PgConnection@575b5f7d marked as broke at java.base/java.io.BufferedOutputStream.flushBuffer(Unknown Source) ~[na:na]" at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.12.jar:5.3.12]" at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:481) ~[postgresql-42.2.24.jar:42.2.24]" at java.base/java.io.BufferedOutputStream.flush(Unknown Source) ~[na:na]" at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) ~[spring-aop-5.3.12.jar:5.3.12]" ... 38 common frames omitted" at java.base/java.net.SocketOutputStream.socketWrite0(Native Method) ~[na:na]" Caused by: java.net.SocketException: Connection reset by peer (Write failed)" at com.github.miyohide.retry.MyService$$FastClassBySpringCGLIB$$a8112c7.invoke(<generated>) ~[classes/:na]" at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:209) ~[spring-retry-1.3.1.jar:na]" at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.12.jar:5.3.12]" at com.github.miyohide.retry.RetryApplication.main(RetryApplication.java:14) ~[classes/:na]" at org.springframework.boot.SpringApplication.run(SpringApplication.java:1332) ~[spring-boot-2.5.6.jar:2.5.6]" at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.12.jar:5.3.12]" at org.springframework.jdbc.core.JdbcTemplate.lambda$update$2(JdbcTemplate.java:965) ~[spring-jdbc-5.3.12.jar:5.3.12]" at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.12.jar:5.3.12]" at java.base/java.net.SocketOutputStream.socketWrite(Unknown Source) ~[na:na]" at com.github.miyohide.retry.MyService.insert(MyService.java:33) ~[classes/:na]" at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java) ~[HikariCP-4.0.3.jar:na]" at org.postgresql.core.PGStream.flush(PGStream.java:665) ~[postgresql-42.2.24.jar:42.2.24]" at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.12.jar:5.3.12]" at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:320) ~[postgresql-42.2.24.jar:42.2.24]" at org.postgresql.jdbc.PgPreparedStatement.executeUpdate(PgPreparedStatement.java:130) ~[postgresql-42.2.24.jar:42.2.24]" at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:164) ~[postgresql-42.2.24.jar:42.2.24]" at org.springframework.boot.SpringApplication.run(SpringApplication.java:345) ~[spring-boot-2.5.6.jar:2.5.6]" at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.12.jar:5.3.12]" at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783) ~[spring-aop-5.3.12.jar:5.3.12]" at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:1015) ~[spring-jdbc-5.3.12.jar:5.3.12]" at java.base/sun.security.ssl.SSLSocketOutputRecord.deliver(Unknown Source) ~[na:na]" at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-2.5.6.jar:2.5.6]" at java.base/java.net.SocketOutputStream.write(Unknown Source) ~[na:na]" at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.12.jar:5.3.12]" at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.12.jar:5.3.12]" at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:651) ~[spring-jdbc-5.3.12.jar:5.3.12]" 2021-10-26 11:46:10.168 INFO 18 --- [ main] c.g.m.retry.config.RetryableDataSource : getting connection ..." 2021-10-26 11:46:10.168 DEBUG 18 --- [ main] o.s.retry.support.RetryTemplate : Retry: count=0" 2021-10-26 11:46:10.170 WARN 18 --- [ main] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection org.postgresql.jdbc.PgConnection@1b134 2021-10-26 11:46:10.171 WARN 18 --- [ main] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection org.postgresql.jdbc.PgConnection@7bd96 2021-10-26 11:46:10.172 WARN 18 --- [ main] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection org.postgresql.jdbc.PgConnection@64dae 2021-10-26 11:46:10.178 WARN 18 --- [ main] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection org.postgresql.jdbc.PgConnection@1e4c6 2021-10-26 11:46:10.177 WARN 18 --- [ main] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection org.postgresql.jdbc.PgConnection@1fedf 2021-10-26 11:46:10.179 WARN 18 --- [ main] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection org.postgresql.jdbc.PgConnection@2ee48 2021-10-26 11:46:10.180 WARN 18 --- [ main] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection org.postgresql.jdbc.PgConnection@1d901 2021-10-26 11:46:10.181 WARN 18 --- [ main] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection org.postgresql.jdbc.PgConnection@6d08b 2021-10-26 11:46:10.181 WARN 18 --- [ main] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection org.postgresql.jdbc.PgConnection@1015a 2021-10-26 11:46:49.206 INFO 18 --- [ main] c.g.m.retry.config.RetryableDataSource : getting connection ..." 2021-10-26 11:46:49.206 DEBUG 18 --- [ main] o.s.retry.support.RetryTemplate : Retry: count=1" 2021-10-26 11:46:49.206 DEBUG 18 --- [ main] o.s.retry.support.RetryTemplate : Checking for rethrow: count=1" at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java) ~[HikariCP-4.0.3.jar:na]" at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.12.jar:5.3.12]" at com.github.miyohide.retry.MyService$$EnhancerBySpringCGLIB$$30791361.insert(<generated>) ~[classes/:na]" at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:651) ~[spring-jdbc-5.3.12.jar:5.3.12]" at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:107) ~[spring-jdbc-5.3.12.jar:5.3.12]" at org.springframework.jdbc.core.JdbcTemplate.translateException(JdbcTemplate.java:1541) ~[spring-jdbc-5.3.12.jar:5.3.12]" at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:79) ~[spring-jdbc-5.3.12.jar:5.3.12]" at java.base/java.io.BufferedOutputStream.flush(Unknown Source) ~[na:na]" ... 30 common frames omitted" at org.postgresql.core.v3.QueryExecutorImpl.sendSync(QueryExecutorImpl.java:1482) ~[postgresql-42.2.24.jar:42.2.24]" at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-2.5.6.jar:2.5.6]" at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:1015) ~[spring-jdbc-5.3.12.jar:5.3.12]" at org.postgresql.core.PGStream.flush(PGStream.java:665) ~[postgresql-42.2.24.jar:42.2.24]" at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:349) ~[postgresql-42.2.24.jar:42.2.24]" ... 38 common frames omitted" at java.base/java.net.SocketOutputStream.socketWrite0(Native Method) ~[na:na]" at org.springframework.retry.interceptor.RetryOperationsInterceptor.invoke(RetryOperationsInterceptor.java:119) ~[spring-retry-1.3.1.jar:na]" at org.postgresql.jdbc.PgPreparedStatement.executeUpdate(PgPreparedStatement.java:130) ~[postgresql-42.2.24.jar:42.2.24]" at org.springframework.jdbc.core.JdbcTemplate.lambda$update$2(JdbcTemplate.java:965) ~[spring-jdbc-5.3.12.jar:5.3.12]" at java.base/java.net.SocketOutputStream.socketWrite(Unknown Source) ~[na:na]" at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:329) ~[spring-retry-1.3.1.jar:na]" Caused by: java.net.SocketException: Connection reset by peer (Write failed)" at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:1025) ~[spring-jdbc-5.3.12.jar:5.3.12]" at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:960) ~[spring-jdbc-5.3.12.jar:5.3.12]" at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:164) ~[postgresql-42.2.24.jar:42.2.24]" at com.github.miyohide.retry.RetryApplication.main(RetryApplication.java:14) ~[classes/:na]" at org.springframework.boot.SpringApplication.run(SpringApplication.java:345) ~[spring-boot-2.5.6.jar:2.5.6]" at java.base/sun.security.ssl.SSLSocketOutputRecord.deliver(Unknown Source) ~[na:na]" at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.12.jar:5.3.12]" at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:320) ~[postgresql-42.2.24.jar:42.2.24]" at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.12.jar:5.3.12]" at java.base/java.io.BufferedOutputStream.flushBuffer(Unknown Source) ~[na:na]" at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:667) ~[spring-jdbc-5.3.12.jar:5.3.12]" at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:70) ~[spring-jdbc-5.3.12.jar:5.3.12]" at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:79) ~[spring-jdbc-5.3.12.jar:5.3.12]" at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.12.jar:5.3.12]" at org.springframework.boot.SpringApplication.run(SpringApplication.java:1332) ~[spring-boot-2.5.6.jar:2.5.6]" at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:209) ~[spring-retry-1.3.1.jar:na]" at com.github.miyohide.retry.RetryApplication.run(RetryApplication.java:22) ~[classes/:na]" at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61) ~[HikariCP-4.0.3.jar:na]" at java.base/java.net.SocketOutputStream.write(Unknown Source) ~[na:na]" at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:401) ~[postgresql-42.2.24.jar:42.2.24]" at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:481) ~[postgresql-42.2.24.jar:42.2.24]" Caused by: org.postgresql.util.PSQLException: An I/O error occurred while sending to the backend." at org.springframework.retry.interceptor.RetryOperationsInterceptor$1.doWithRetry(RetryOperationsInterceptor.java:93) ~[spring-retry-1.3.1.jar:na]" at java.base/sun.security.ssl.SSLSocketImpl$AppOutputStream.write(Unknown Source) ~[na:na]" at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:791) ~[spring-boot-2.5.6.jar:2.5.6]" at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.invoke(AnnotationAwareRetryOperationsInterceptor.java:163) ~[spring-retry-1.3.1.jar:na]" at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) ~[spring-aop-5.3.12.jar:5.3.12]" at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.12.jar:5.3.12]" at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783) ~[spring-aop-5.3.12.jar:5.3.12]" at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.12.jar:5.3.12]" 2021-10-26 11:46:51.627 ERROR 18 --- [ main] o.s.t.i.TransactionInterceptor : Application exception overridden by rollback exception" at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.12.jar:5.3.12]" at com.github.miyohide.retry.MyService$$FastClassBySpringCGLIB$$a8112c7.invoke(<generated>) ~[classes/:na]" at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.12.jar:5.3.12]" at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.12.jar:5.3.12]" org.springframework.dao.DataAccessResourceFailureException: PreparedStatementCallback; SQL [INSERT INTO customers(first_name, last_name) VALUES (?, ?)]; An I/O error occurred while at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.12.jar:5.3.12]" at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:775) ~[spring-boot-2.5.6.jar:2.5.6]" at com.github.miyohide.retry.MyService.insert(MyService.java:33) ~[classes/:na]" 2021-10-26 11:46:59.489 DEBUG 18 --- [ main] o.s.retry.support.RetryTemplate : Retry: count=0" 2021-10-26 11:46:59.490 INFO 18 --- [ main] c.g.m.retry.config.RetryableDataSource : getting connection ..." 2021-10-26 11:46:59.488 DEBUG 18 --- [ main] o.s.retry.support.RetryTemplate : Checking for rethrow: count=1" 2021-10-26 11:46:59.488 DEBUG 18 --- [ main] o.s.retry.support.RetryTemplate : Retry: count=1" 2021-10-26 11:46:59.498 INFO 18 --- [ main] com.github.miyohide.retry.MyService : Insert record. i = [0], first_name = [Josh00000], last_name = [hogehoge00000]" 2021-10-26 11:46:59.497 INFO 18 --- [ main] com.github.miyohide.retry.MyService : Start insert method..." 2021-10-26 11:47:01.033 INFO 18 --- [ main] com.github.miyohide.retry.MyService : Insert record. i = [1], first_name = [Josh00001], last_name = [hogehoge00001]" 2021-10-26 11:47:02.038 INFO 18 --- [ main] com.github.miyohide.retry.MyService : Insert record. i = [2], first_name = [Josh00002], last_name = [hogehoge00002]" 2021-10-26 11:47:03.042 INFO 18 --- [ main] com.github.miyohide.retry.MyService : Insert record. i = [3], first_name = [Josh00003], last_name = [hogehoge00003]" 2021-10-26 11:47:04.048 INFO 18 --- [ main] com.github.miyohide.retry.MyService : Insert record. i = [4], first_name = [Josh00004], last_name = [hogehoge00004]" 2021-10-26 11:47:05.052 INFO 18 --- [ main] com.github.miyohide.retry.MyService : Insert record. i = [5], first_name = [Josh00005], last_name = [hogehoge00005]"
org.postgresql.util.PSQLException: An I/O error occurred while sending to the backend.
が発生したり、java.net.SocketException: Connection reset by peer (Write failed)
が発生したりしていますが、2021-10-26 11:46:59.498
時点で再度0
からInsert処理が始まりました。無事リトライ処理が行われているようです。
無事、一時的な接続エラーに対処することができました。
実装ソース
ソースは以下に置いてあります。
non_retry
フォルダにあるものはリトライ処理を実装していないもの、retry
フォルダにあるのがリトライ処理を実装したものになります。