Aurora の Blue/Green 切り替え後に read-only エラーが止まらない原因と対処(HikariCP)
Aurora の Blue/Green デプロイで切り替えたあと、次のエラーが止まらなくなったことはないでしょうか。
SQLTransientConnectionException: (conn=10383)
The MySQL server is running with the --read-only option so it cannot execute this statement切り替え中に RDS が出したイベントでは、書き込みのダウンタイムは約 4 秒と報告されていました。
ところが私の検証では、アプリは待っていても復旧せず、Pod を再起動するまでこのエラーを返し続けました。
本記事では、実測したログからその原因と、AWS Advanced JDBC Driver で解消した結果を紹介します。
切り替え後も更新系だけが失敗し続けた
検証環境で Aurora MySQL の Blue/Green 切り替えを実行し、0.5 秒間隔で API を叩き続けて様子を見ました。
アプリは Java(Spring)で、コネクションプールは HikariCP、ドライバは MariaDB Connector/J です。
| 項目 | 結果 |
|---|---|
| RDS イベントの書き込みダウンタイム | 約 4 秒 |
| 更新系の API | 失敗し続けた |
| 参照系の API | ほぼ影響なし |
| 自動復旧 | しない(3 分以上兆候なし) |
| 復旧方法 | Pod 再起動 |
参照系がほぼ影響を受けていない点が手がかりになります。
DB に繋がらないのであれば、参照系も同じように失敗するはずです。
切り替え前の writer へのコネクションが残り続けていた
記録されたエラー 306 件の内訳を見ると、原因がはっきりします。
- 306 件中 304 件が
--read-only optionによる INSERT 拒否 - そのうち大半が同一のコネクション
conn=10383 Connection is not available(プール枯渇)はわずか 2 件
つまり「プールが枯渇して接続できない」のではありません。
接続はできていて、その接続先が読み取り専用になっていたのです。
Blue/Green の切り替えでは、旧クラスタが -old1 のような名前にリネームされて残り、読み取り専用の状態に固定されます。
これは AWS の公式ドキュメントにも明記されている仕様です。
切り替え前に writer だったインスタンスが読み取り専用になったにもかかわらず、アプリはそのコネクションを掴んだまま INSERT を投げ続けていました。
HikariCP が古いコネクションを破棄しない理由
ではなぜ、コネクションプールは役に立たなくなったコネクションを捨てないのでしょうか。
理由は単純で、プールから見るとそのコネクションは「正常」だからです。
HikariCP がコネクションを捨てるのは切断されたと判断したときですが、今回は次の 2 つが重なって、その判断が起きませんでした。
- 接続が一度も切れていない:切り替えをまたいで、同じ
conn=10383が使われ続けていた - 生存確認が成功してしまう:貸し出し前の
isValid()や、connectionTestQueryに設定したSELECT 1は、読み取り専用のインスタンスに対しても通る
生存確認が見ているのは接続が生きているかどうかだけで、書き込めるかどうかまでは確かめません。
そのため確認をすり抜け、実際に INSERT を投げた瞬間にだけエラーになります。
これが自動復旧しない理由であり、プール設定の調整では回避できない理由でもあります。
maxLifetime を短くすればいずれ作り直されますが、寿命が来るのを待つだけで、切り替えを検知しているわけではありません。
公式ドキュメントの記述と食い違う点
先ほどの公式ドキュメントの Switchover actions には、切り替え時の動作としてこう書かれています。
「両環境の DB インスタンスへのコネクションを切断し、新規の接続を許可しない」(Drops connections to the DB instances in both environments and doesn’t allow new connections.)
ドキュメントどおりならコネクションは切断され、HikariCP がそれを検知して張り直すので、今回のような固着は起きません。
しかし実測では、切り替えをまたいで同じ conn=10383 が read-only エラーを出し続けていました。
切断と再接続が起きていれば、コネクション ID は新しいものに変わるはずです。
以前は同じ構成でも自力で復旧していた
実はこのシステムでは、以前にも同じ構成で Blue/Green の切り替えを行ったことがあります。
| 項目 | 以前の切り替え | 2026 年に入ってからの切り替え |
|---|---|---|
| アプリの構成 | 同じ | 同じ |
| 切り替え後のアプリ | 1 分未満で復旧 | 古い接続先に書き込み続けた |
| Pod の再起動 | 不要 | 必要 |
アプリ側は何も変えていないので、変わったのは AWS 側だと考えられます。
思い当たるのが、単一リージョン構成のダウンタイムを「通常 5 秒以下」に縮めた、2026 年 1 月 20 日に AWS が発表した Blue/Green の切り替えの高速化です。
時期が重なることから、高速化によって切り替え時のコネクションの扱いが変わり、既存のコネクションが切断されなくなったのではないかと私は考えています。
あくまで仮説で、AWS がコネクションの扱いを変えたと明言している資料は見つけられていません。
それでも、ドキュメントどおりなら起きないはずの事象が実際に起きたことは確かです。
後述の手順で再現できるので、気になる方はご自身の環境で確かめてみてください。
既存の Pod は自力では復旧しない
興味深いのは、エラーが続いている間も一部のリクエストは成功していたことです。
成否を分けていたのは Pod がいつ起動したかでした。
| Pod の起動タイミング | 観測時点の稼働時間 | 結果 |
|---|---|---|
| 切り替え前から稼働 | 4h19m | 500 を返し続けた |
| 切り替え後に起動 | 2m47s | 201 を返した |
切り替え後に起動した Pod は、当然ながら新しいクラスタに接続するので正常に動きます。
一方で切り替え前から動いていた Pod は、古いコネクションを抱えたままエラーを返し続けました。
kubectl rollout restart で全 Pod を入れ替えたところ、ようやく解消しました。
裏を返せば、切り替え直後にアクセスが少なくスケールアウトが起きない時間帯ほど、この問題は長引きます。
AWS Advanced JDBC Driver では自動復旧した
同じ条件で、接続に AWS Advanced JDBC Driver(JDBC Wrapper)を使って再検証しました。
結果は明確でした。
| 指標 | MariaDB Connector/J | AWS Advanced JDBC Driver |
|---|---|---|
| 自力での復旧 | しない | する |
| アプリが復旧するまで | ―(復旧しない) | 31 秒 |
| 復旧手段 | Pod 再起動が必要 | 不要 |
| 記録されたエラー | --read-only option | Connection is closed |
DB 側の断は約 4 秒でも、アプリが新しい writer に書き込めるようになるまでには 31 秒かかりました。
メンテナンスの影響を見積もるときは、RDS のイベントの値ではなくアプリ側で計測した値を使ったほうが安全です。
注目すべきは、Wrapper では --read-only エラーが 1 件も出ず、代わりに Connection is closed が記録されていたことです。
つまりドライバが切り替えを検知し、古いコネクションを自分で破棄しているということです。
破棄されれば HikariCP が新しいコネクションを張り直すので、結果として自動復旧します。
2 つのドライバの違いは復旧までの時間の長短ではなく、自力で復旧するかどうかです。
人が気付いて Pod を再起動しないと戻らない状態は、深夜のメンテナンスでは特に重くのしかかります。
有効にしたプラグイン
今回の検証で wrapperPlugins に指定したのは次の 4 つで、failoverMode は指定していません。
wrapperPlugins=auroraConnectionTracker,readWriteSplitting,failover2,efm2Blue/Green 専用の bg プラグインは、この構成に含まれていません。
今回は、切り替えのたびに Pod を再起動しなくて済むように、ドライバの入れ替えだけを先に検証しました。
ドライバの入れ替えと bg プラグインの導入を同時に行うと、問題が起きたときや性能を比べるときに、どちらの影響なのか見分けられなくなるためです。
bg プラグインを使えば 31 秒より短くなる可能性がある
2026 年 1 月のAWS の発表では、AWS Advanced JDBC Driver を使った場合の書き込みのダウンタイムは「通常 2 秒以下」とされていて、今回の 31 秒とは大きな開きがあります。
AWS の公式ブログで案内されているのは、bg プラグインを読み込む構成です。
bg プラグインは Blue/Green の進行状況を DB から読み取り、切り替えの間は新しい接続やクエリの実行を一時的に止めて待たせます。
進行状況は mysql.rds_topology テーブルから読むので、アプリが使う DB ユーザーには次の権限が必要です。
GRANT SELECT ON mysql.rds_topology TO 'your_user'@'%';この権限は、Blue/Green を作成する前に付与しておくのが簡単です。
作成後にもとのクラスタで付与しても、切り替え先の環境には反映されないためです。
なお bg プラグインは Wrapper 2.6.0 以降で使え、Aurora MySQL はエンジン 3.07 以上が対象です。
bg プラグインでどこまで短くなるかは未検証なので、記事中の 31 秒はbg プラグインなしで Pod の再起動を不要にできた構成の値として読んでください。
この現象を再現する手順
自分の環境で確かめるときは、次の順で進めます。
- エンジンバージョンを変えずに Blue/Green を作る(例:3.10.5 → 3.10.5)。アップグレード処理が挟まらないので、切り替えの動作だけを観察できる
- 更新系の API を 0.5 秒間隔で叩き続ける
- その状態で切り替えを実行する
- 切り替えの正確な時刻と書き込みダウンタイムを、RDS のイベントで確認する
API を叩く curl には、必ずタイムアウトを付けてください。
curl --connect-timeout 5 --max-time 30 ...付けないと断の瞬間にハングしてしまい、肝心の記録が飛びます。
RDS のイベントは次のコマンドで確認できます。
aws rds describe-events --region ap-northeast-1 \
--source-identifier your-cluster-name --source-type db-cluster \
--duration 60 --query 'Events[].{Time:Date,Message:Message}' --output tableなお macOS の date はミリ秒に対応していないため、記録には gdate +%H:%M:%S.%3N を使うと細かく追えます。
まとめ
Aurora の Blue/Green 切り替えで --read-only option エラーが続く現象について整理します。
- 根本の問題は、切り替え前の writer へのコネクションが切り替わらないまま残ること
- 旧クラスタは削除されず読み取り専用で残るため、そこを掴んだままだと更新系だけが落ちる
- プールの生存確認(
isValid()やSELECT 1)は read-only でも成功するのですり抜ける - 既存 Pod は自力で復旧しない。Pod 再起動が必要
- AWS Advanced JDBC Driver では古いコネクションが破棄され、31 秒で自動復旧した
- 今回は
bgプラグインを使っていない。使えばさらに短くなる可能性があるが未検証(DB ユーザーにmysql.rds_topologyの参照権限が必要) - RDS のイベントが報告する書き込みダウンタイム(約 4 秒)と比べるべきはこの 31 秒。影響の見積もりにはアプリ側の計測値を使う
公式ドキュメントには「コネクションを切断する」と書かれているにもかかわらず、実測では同じコネクションが生き残っていました。
以前は同じ構成で問題なかったことから、2026 年 1 月の高速化で挙動が変わったのではないかと考えていますが、裏付けは取れていません。
「以前うまくいった構成だから」「ドキュメントに切断すると書いてあるから」と安心するのは危険だと感じています。
Blue/Green の切り替えを本番で行う前に、一度は検証環境で実際に計測してみることをおすすめします。
アプリが諦めたあとも DB 側でクエリが走り続ける、JDBC のタイムアウトの落とし穴は次の記事にまとめています。
