最近のツールは zero config を謳い、デフォルト設定のまま使えばうまくいくような構成になっていることが多いです。 が、例外もあります。今回は Quarkus Redis 拡張の接続プールまわりの設定が怪しかったので調査しました。
ネットワークは本業ではない
最初に断っておくのですが、 私は web アプリケーション界隈の人なのでネットワークには疎いです。 なので以下の内容は疑いながら読んでください・・・。
接続プールに求めること
Quarkus の Redis 拡張、というか内部で使っている Vert.x redis client は Redis 接続プールを持ちます。
接続プールは既にご存知かと思いますが、 TCP 接続時のオーバーヘッドを減らすために、接続済みのコネクションを保持して再利用するための仕組みです。 Redis 以外ではデータベース接続でよく使われます。
この接続プールに期待する動作として、 「接続が切れた場合にその接続を破棄して新しい接続を作成する」というものがあります。 接続が切れた、という判断基準は色々とありますが、 そのうちの一つとして一定時間応答がなかったら切断された扱いとする、 いわゆるタイムアウトがあります。
タイムアウト設定を探す
つぎに tcp まわりのタイムアウトに関連しそうな設定を見てみます。
| 設定項目 | デフォルト設定 | デフォルト値での動作 |
|---|---|---|
quarkus.redis.tcp.idle-timeout | なし | 無効 |
quarkus.redis.tcp.read-idle-timeout | なし | 無効 |
quarkus.redis.tcp.write-idle-timeout | なし | 無効 |
quarkus.redis.tcp.keep-alive | なし | 無効 |
この辺の設定がデフォルトで無効になっており、アイドル時のタイムアウトが作動しないという状態になっています。 さらに keep alive も無効になっており、 定期的な生存確認も走らないようです。
つまり、異常時もしばらくは接続が残ったままになるということになります。 いわゆるゾンビ接続です。
切断とは
TCP 接続を切断する主なケースとしては
- クライアントとサーバー側で通信が行われて、お互い接続切断しようねというやり取りがトラブルなく交わされたとき
- 何らかの原因によるセッション切れがあり
- RST が返る
- DROP される
というのが主なパターンだと思ってます。
この中の DROP されるパターンが厄介で、 通信の結果が返ってこないので接続が切れているか分からず何度かリトライをします。 上限までリトライすると ETIMEDOUT になり接続を閉じられる状況になるらしいです。 この上限が Linux デフォルトだと15回リトライ&指数バックオフで約13〜30分かかるようです。 手元の環境で再現した時は 13 分かかりました。1
最低限入れておきたい設定
quarkus.redis.tcp.read-idle-timeout は少なくとも入れておきたいです。
サーバーから何も返ってこないパターンなので、 read の timeout を設定します。
idle-timeout は read または write のどちらかがあるとタイマーがリセットされるものですので設定しません。 こちら側は生きている状態であれば write はそこそこの頻度で発生する可能性があるので、 idle-timeout や write-idle-timeout による効果は期待できないと考えられます。
具体的には application.properties に以下を追加します。
quarkus.redis.tcp.read-idle-timeout=30s値は用途に応じて調整してください。 短くしすぎると通常の処理中にタイムアウトが発生するリスクがあります。
これを設定すると、タイミングによってはタイムアウト直前の接続が払い出される可能性があるので、 リトライ処理を仕込んでおくと安全です。2
まとめ
ここで紹介した設定値の他に quarkus.redis.tcp.connection-timeout (新規接続時のタイムアウト) などもデフォルト値が設定されていません。
これらの設定値もしっかりと設計し、障害発生時のシミュレーションもテストに含んでおくほうがよさそうです。
Claude に聞いてみた
Claude におすすめ設定を聞いて記事を追記してもらいました。
# 接続確立のタイムアウト(未設定だと無限待ちの可能性)
quarkus.redis.tcp.connection-timeout=5s
# サーバーが応答しない場合のタイムアウト
quarkus.redis.tcp.read-idle-timeout=30s
# OS レベルの死活確認を有効化
quarkus.redis.tcp.keep-alive=true
# 切断後の自動再接続(デフォルト 0 = 再接続しない)
quarkus.redis.reconnect-attempts=3
quarkus.redis.reconnect-interval=1stcp.keep-alive は OS レベルの keep-alive probe を有効にするものなので、
read-idle-timeout と組み合わせると二重の保護になって良さそうです。
reconnect-attempts のデフォルトが 0 なのは気づいていませんでした。
切断後に自動で再接続しないのはさすがに困るので、あわせて設定しておきたいところです。