Zenn
🐑

【Go】MySQLドライバのRejectReadOnlyフラグで解決するフェイルオーバー問題

2025/03/15に公開
1

はじめに

AWS Aurora MySQLを利用したシステム開発において、フェイルオーバーへの対応は避けて通れない課題です。特にGolangでコネクションプーリングを使っている場合、フェイルオーバー発生時に思わぬトラブルに見舞われることがあります。

今回は、あまり知られていないRejectReadOnlyオプションを使って、この問題を解決する方法をご紹介します。

フェイルオーバー後のDNS更新とコネクション問題

AWS Auroraでフェイルオーバーが発生すると、クラスター内のリーダーノードがライターに昇格し、DNS情報が更新されます。
しかし、アプリケーション側のコネクションプールには古いDNS解決結果に基づいたコネクションが残っているため、以下のような問題が発生します

  1. アプリケーションは古いDNS情報に基づいて接続先を選択
  2. 書き込み処理が(現在はリードオンリーとなった)旧ライターに送信される
  3. MySQLはリードオンリーモードなので書き込みエラーが発生

Amazon Aurora DB クラスターのフェイルオーバー - Amazon Aurora

一般的な解決策

この問題に対処するための一般的な方法は主に3つあります

1. クライアント側でフェイルオーバー検知とコネクション再確立

アプリケーション側でフェイルオーバーを検知し、コネクションプールをクリアして新しいコネクションを確立する方法です。これにはフェイルオーバーの検知ロジックの実装が必要で、実装の複雑さが増します。

2. Connection Lifetimeの設定

database/sqlパッケージのSetConnMaxLifetimeメソッドを使って、コネクションの最大寿命を設定する方法です。これにより古いコネクションは一定時間後に自動的に破棄され、新しいコネクションが確立されます。

db.SetConnMaxLifetime(5 * time.Minute)

この方法はシンプルですが、フェイルオーバー発生直後の問題には対応できず、最大寿命の間はエラーが発生する可能性があります。

Managing connections - The Go Programming Language

3. RejectReadOnlyオプション

上記の方法には一長一短がありますが、実はgo-sql-driver/mysqlにはRejectReadOnlyというあまり知られていないオプションが存在します。

このオプションを有効にすると、リードオンリーサーバーへの書き込み処理を自動的に検出し、適切にエラー処理を行うことができます。

GitHub - go-sql-driver/mysql: Go MySQL Driver is a MySQL driver for Go's (golang) database/sql package

仕組み

  1. RejectReadOnly=trueを設定すると、ドライバは各クエリ実行後のMySQL応答を監視
  2. サーバーが「ERROR 1290 (HY000): The MySQL server is running with the --read-only option」などのリードオンリーエラーを返した場合
  3. ドライバはdriver.ErrBadConnエラーを返す
  4. database/sqlパッケージはこのエラーを検出し、問題のあるコネクションを破棄して新しいコネクションを確立

実装方法

DSNにrejectReadOnly=trueパラメータを追加するだけです

dsn := "user:password@tcp(aurora-cluster-endpoint:3306)/dbname?rejectReadOnly=true"
db, err := sql.Open("mysql", dsn)

または、Config構造体を使用する場合

config := mysql.Config{
    User:           "user",
    Passwd:         "password",
    Net:            "tcp",
    Addr:           "aurora-cluster-endpoint:3306",
    DBName:         "dbname",
    RejectReadOnly: true,
}
dsn := config.FormatDSN()
db, err := sql.Open("mysql", dsn)

動作の詳細:コードで理解する

go-sql-driver/mysqlのソースコードでは、以下のようにリードオンリーエラーを検出しています

https://github.com/go-sql-driver/mysql/blob/341a5a5246835b2ac4b8d36bb12a9dfad70663f4/packets.go#L578-L593

ErrReadOnlyは「The MySQL server is running with the --read-only option」というエラーメッセージのバイト列です。

そして、database/sqlパッケージはdriver.ErrBadConnを検出すると、自動的にコネクションを閉じます

次回データベース操作が実行される際に、コネクションプールから利用可能なコネクションがなければ、その時点で新しいコネクションが確立されます

https://cs.opensource.google/go/go/+/master:src/database/sql/sql.go;drc=971ab11ee2477cf81f7b7db520bb5c151440d298;l=1348

まとめ

AWS Aurora MySQLを使用するGolangアプリケーションでは、フェイルオーバー時のコネクション問題が発生することがあります。この問題に対してgo-sql-driver/mysqlRejectReadOnlyオプションは非常に便利な解決策です。

実装も簡単で、DSNに一つのパラメータを追加するだけで済みます。

この機能はドキュメントにも記載されていますが、あまり知られていないため、Aurora MySQLを使用している開発者はぜひ検討してみてください。

参考リンク

1

Discussion

ログインするとコメントできます