2か月におよぶ格闘の末RDSのコネクション上限周りのボトルネックを解消した話

* MySQLのmax_connectionsの話ではありません
* エフェメラルポート枯渇の話ではありません

コネクションの上限と聞くとこれらのパラメータが真っ先に思い浮かびますが、今回の原因はこれではありませんでした。
もっとうまくやれたなと思ったので反省と共有のため記します。

## 何が起きたか(時系列順に)

ある日のアクセス数のグラフですが、見事に頭打ちになっています。
この間、当然ながらレスポンスが遅くなっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143313/3235e521-1edd-c50f-ebbe-180cfb0e0155.png)

このアクセス数は以下の構成で、経路上にあるALBのアクセス数の値です。

```
CloudFront => ALB => ECS Fargate(web) => RDS
                                      => NLB => EC2(cache) => s3
```

※ECSから先は、DBの情報をもとにNLBへHTTPリクエストを行っています。

この頭打ちになっている原因調査のために以下のようなことを調べました

* パフォーマンスモニタリング/USEメソッド
  * 上記の経路のすべてのリソースを確認したところ上限に到達していそうなところはありませんでした
  * データベースも一つ一つのクエリのパフォーマンスは落ちていませんでした
  * サポートへ問い合わせを行いましたが、基盤上の上限は確認できなかったそうです
* プロファイリング
  * ログのレスポンスタイムやスロークエリログからNLBやDBの速度は遅くなっていないことが分かりました
  * 遅くなっているのはECSのレスポンスタイムだけでした
  * でもDBとNLB以降が遅くなっておらず、リソースにも余裕があるのに、なぜECSが遅くなるのか分かりませんでした
  * ECSの何が遅くなっているか確認できる状態ではありませんでした
  * なのでECSにアプリケーションプロファイラを導入しました
  * しかしDB周りのライブラリが遅くなっている以上のことが分かりませんでした
      * PHPのプロファイラでネイティブライブラリの中の処理は追えませんでした
      * connectできないでいるのか
      * ライブラリ内に含まれるその他のシステムコールで遅くなっているのか
      * RDS側から通信が返ってこないのか、などが不明でした
  * これ以上を調べるために、ECSで動かしているコンテナをEC2で動作させました
  * これによりECS Fargate特有の問題でないことが分かりました
  * そしてtcpdumpおよびstraceおよびeBPFツールを用いてSyn+Ackが返ってきていないことが分かりました。

では、なんでSyn+Ackが返ってこないのか？

max_connectionに到達していない、エフェメラルポートの上限も到達していない、
パケットdropもしていない、負荷が高いときに再現するからネットワークの不調でもない、
somaxとか確認しても何ともない

**・・・・・・・・・詰んだ**

しょうがないので、ワンチャンにかけて、RDSのインスタンスタイプ上げてみました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143313/c91fd6a5-4a9a-9902-23a5-405b8976d007.png)

**!!!!**

張り付いてません、やった・・・！！

## 何が起きていたか(後日談)

結論、インスタンスタイプを上げましょう！完

なんてことに、なりかねない問題でしたが、最終的にAWSサポート様からの回答により事実が判明しました。

結果として、RDSについているSecurityGroupの設定がよくなかったとのことです。

SecurityGroupには接続制御のために、 [接続追跡](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/ec2-security-groups.html#security-group-connection-tracking)  ということを行っています。
コネクションごとに追跡するため、AWS側で状態を保持しているということです。
そしてこの接続追跡の状態ですが、なんとこの状態数に上限が存在しているそうです。
しかも、インスタンスタイプごとに上限が変わるとのこと。
今回はタイプを変えたことによって問題が緩和されたというわけです。

知りませんでした。

## 技術的な解決方法

今回はタイプを変えることによって解決しましたが、不要なリソースとコストがかかることになるため、あくまでも結果的な解決かなと思います。

根本的な技術的解決方法としては、いくつかありますがここでは２つ挙げておきます

まず、ガバガバファイアウォール対策です。
接続追跡ですが、0.0.0.0/0などで無条件でアクセスできるようにしておけば、接続は追跡されないそうです。
そのため上限に到達することはありません。
ガバガバといいましたが、IAM認証やMySQLの認証機能を利用すれば特に問題にはなりません。

次にコネクションプール対策です。
コネクションを接続、切断をしなければ追跡の状態は１つなります。
そのため接続プールを利用すれば問題は起きません。
書いてませんでしたが、ECS から RDSへは都度コネクションを張って、クエリを実行するということをしています。
HTTP2でもコネクションは張りっぱなしにしているよう、
都度コネクションの接続切断は高コスト高リスクといったところでしょうか。

他にもDBをDynamoDBに変える、DBの台数を増やす、などいろいろな方法はあります。

## 反省

明確にドキュメントに上限ありますと書かれてないため、
やらかしかではないと思っていますが、
もっと早く解決できたなとは思っています

* 調査面での反省点
  * 早々に同じ環境を用意して状況を再現すればよかったです
    * メトリクスの確認とアクセスログの確認とプロファイラのための設定といろいろ行っていたら、状況再現という基本的なことがすっぽり抜け落ちてしまっていました。
    * 再現できればパラメータを変えて実験的な手法も取れたのにと思います
* システム構成的な面での反省
  * コネクションプールは最初から考えたほうが良さそうかもしれません
  * 共有してよいセッションであればプールがあって困ることはそうそうないからです
  * 過去にもmemcachedのエフェメラルポート枯渇問題など経験があったのでこの手のものは設計から考えるべきかなと思いました
* 運用の面での反省
  * 今回の事象は、検証環境を用意して負荷テストを行ったところ再現しました
  * 逆に言うと負荷テストを行っていれば問題が表面化しなかったものです
  * 負荷テスト自体は難しくないのですが、１回の負荷テストによって通信料が見過ごせないことになります(弊社比)
  * 毎回やれるものではなくどう運用に組み込んだものかと頭を抱えている部分ですが検討しようかなと思いました

## まとめ

今回の件、さらっと書きましたが、およそ2か月に及ぶ調査の末に解決した事象です。
ユーザの皆様には長期間ご不便をおかけしていまい申し訳なく感じております。

AWSサポートの皆様には本当にお世話になりました。
基盤上問題ないかと聞いた時に事象が判明しなかったことを考えると、なかなかのレアケースを引いたのかもしれません。

もっとうまく解決できるよう精進していこうと思います。
(解消したなんてカッコよくいってるけどテキトウに対応したらうまくいった、という何となく技術者的に切ない解決でした。)