DNSラウンドロビンで負荷分散しているDB(Aurora等)にコネクションプールを使って接続すると設定次第で負荷分散ができないっていう話と解決法


環境

RDSのクラスタ構成(Aurora@MySQL5.7使用。全部、db.t2.small)

アプリ構成

* Spring-boot:2.1.0
* HikariCP:3.2.0

※JVMがDNSキャッシュしないようにしておくこと

実験内容

hikariCPのmaxLifetimeを変えながら、コネクションプール使用環境下で継続的にDBアクセスがある場合に負荷分散が行われるかどうかを確認する。

  • コネクションプール数は最大10とする。
  • abコマンドを 4リクエスト、4同時接続でバックグラウンドで実行する。
  • ↑のabコマンドをwhileでループさせて、3秒に1回実行する、というのをしばらくの間継続して実行し続ける。
  • リクエスト1回で、DBへselectを実行し、数秒(少なくともリクエストが滞留する程度には遅い)でselectの結果が返ってくるようにしてある。

※リクエスト毎に読み込みエンドポイントに対して、↓の状態のテーブルに、

SELECT * FROM hoge a, hoge b, hoge c, hoge d, hoge e, hoge f, hoge g, hoge h where a.pk != #{id} order by a.pk desc limit 1

を投げるようになっている。
※#{id} の部分にはランダムで数値が入るようにして、結果がキャッシュされないようにする

どこのIPアドレスに接続しているかはnetstatで常時監視

while :; do netstat -ano | grep ":3306 "; sleep 1; clear; done

実験結果

動画とれれば面白いのですがそういうの無いので、結果のテキストだけ。。

maxlifetimeを30"分"にして実行する(hikariCPのデフォルト値)

最初に接続したIPアドレスから接続先が変わりません。
※レプリカ3台にしていますが、最初に接続したのが2台だった場合、残りの1台には30分間に接続しません。

maxlifetimeを30"秒"にして実行する(hikariCPの最小値)

30秒毎に接続しているIPアドレスが変化しているのが確認できます。
※Spring-bootのログからもmaxlifetimeに引っかかり再接続しにいくログが見れます
※30秒間は同じところに繋ぎに行くのが良いのかどうかはまた別の話。。

結論

DNSラウンドロビンで負荷分散をしているシステムに対して、コネクションプールを張り続けると負荷分散ができない。
※厳密にいうとできていない訳ではないけど、、
ので、短いスパンでDB接続を一旦張りなおす設定をいれましょう。
※maxlifetimeでなくても、規定秒idleで切断する(idleTimeout)とか。idleしないと切れないけど。。

参考