【Go】MySQLドライバのRejectReadOnlyフラグで解決するフェイルオーバー問題
はじめに
AWS Aurora MySQLを利用したシステム開発において、フェイルオーバーへの対応は避けて通れない課題です。特にGolangでコネクションプーリングを使っている場合、フェイルオーバー発生時に思わぬトラブルに見舞われることがあります。
今回は、あまり知られていないRejectReadOnly
オプションを使って、この問題を解決する方法をご紹介します。
フェイルオーバー後のDNS更新とコネクション問題
AWS Auroraでフェイルオーバーが発生すると、クラスター内のリーダーノードがライターに昇格し、DNS情報が更新されます。
しかし、アプリケーション側のコネクションプールには古いDNS解決結果に基づいたコネクションが残っているため、以下のような問題が発生します
- アプリケーションは古いDNS情報に基づいて接続先を選択
- 書き込み処理が(現在はリードオンリーとなった)旧ライターに送信される
- 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
というあまり知られていないオプションが存在します。
このオプションを有効にすると、リードオンリーサーバーへの書き込み処理を自動的に検出し、適切にエラー処理を行うことができます。
仕組み
-
RejectReadOnly=true
を設定すると、ドライバは各クエリ実行後のMySQL応答を監視 - サーバーが「ERROR 1290 (HY000): The MySQL server is running with the --read-only option」などのリードオンリーエラーを返した場合
- ドライバは
driver.ErrBadConn
エラーを返す -
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
のソースコードでは、以下のようにリードオンリーエラーを検出しています
ErrReadOnly
は「The MySQL server is running with the --read-only option」というエラーメッセージのバイト列です。
そして、database/sql
パッケージはdriver.ErrBadConn
を検出すると、自動的にコネクションを閉じます
次回データベース操作が実行される際に、コネクションプールから利用可能なコネクションがなければ、その時点で新しいコネクションが確立されます
まとめ
AWS Aurora MySQLを使用するGolangアプリケーションでは、フェイルオーバー時のコネクション問題が発生することがあります。この問題に対してgo-sql-driver/mysql
のRejectReadOnly
オプションは非常に便利な解決策です。
実装も簡単で、DSNに一つのパラメータを追加するだけで済みます。
この機能はドキュメントにも記載されていますが、あまり知られていないため、Aurora MySQLを使用している開発者はぜひ検討してみてください。
Discussion