BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

Amazon RDS Proxy が BASE にもたらした期待以上の導入メリット

はじめに

基盤チームでバックエンドエンジニアをやっている松田( @tadamatu )です。

以前にCTO川口が当ブログ内で公開した以下の記事があります。

devblog.thebase.in

新規接続の限界

BASE のアクセス量の伸びは凄まじくこの構成でも接続エラーが発生するようになってしまいました。 ピーク時に秒間 2 万もの新規接続が primary インスタンスへ行われているといった状態です。

この記事が公開されたのが約2年前で、当時100万程度 だったショップ数は170万を超え、我々はまだまだ伸ばしたいと考えています。 これは、ショップ数の伸びとともに、指数関数的に増えていくユーザのアクセスを捌く必要があることを意味します。

ブログ公開当時、我々はさまざまな検討の末、以下のような対策を取りました。

残された手段は primary のインスタンスに対しての接続数を如何にして減らすか、ということのみです。 ここで出来ることアプリケーション側の接続をいかに reader インスタンスに行うか、ということです。 これはクエリを reader に向けるというよくある負荷対策ではなく、新規接続自体を reader に最初から向ける

対応後もメディア露出などによりアクセスが集中すると、たびたびMySQLへの新規接続がボトルネックとなり、接続しづらい状態に陥っていました。 そこでBASEでは、RDS Proxy が2020/06にGAされたこともあり、検証を開始し、導入するまでに至りました。 aws.amazon.com

本記事では、そこで得た知見PHPとの相性はどうなのか検証した方法導入後どの程度効果があったのか導入後発生した障害、など、RDS Proxy導入におけるメリット・デメリット を公開させていただこうと思います。

RDS Proxy導入方法など基本的なことは他の記事に譲ります。この記事では実際の導入を通して得たTips をできるだけ網羅しましたので、RDS Proxyが気になっている方導入を検討している方のお役に立てれば嬉しいです。

RDS Proxyとは

まず、 RDS Proxy を導入することで公式に記載されているメリットを簡単に書いておきます。 aws.amazon.com

 (1) 接続プーリングによるパフォーマンスの向上
 (2) フルマネージド
 (3) フェイルオーバー発生時の時間短縮などによるアプリケーションの可用性向上
 (4) IAM 認証を利用しセキュリティレベルの向上(オプション)

などいくつかありますが、もちろん我々が一番得たかったメリットは(1)になります。

導入前の検証

さて、検証を始めていくわけですが、ポイントとなったものを記載していきます。

アプリケーション的な障壁はほぼなかった

まず手始めに実際にDEV環境にRDS Proxyを構築してみて、アプリケーションからのDB接続をRDS Proxy経由に切り替えて検証をしてみます。 これに関してはQAチームに協力をしていただき、一通りリグレッションテストを通すことで検証、動作担保を行いました。

結果、あっさりリグレッションテストをパス!

つまり、RDS Proxyの切り替えに関してコードの変更はほぼなしで可能で、コード変更するのは接続先の切り替えくらいというライトな感覚で切り替えが可能 ということが分かりました。 (後述しますが、BASEではRDS Proxyの切り替えで、全くアプリのコードは変更しませんでした)

費用面について見積もり

RDS ProxyはRDSインスタンスのvCPUの総数に応じて費用がかかります。 aws.amazon.com

BASEでは、過去に障害が発生していた経緯もあり、安定運用のため、少し力技でAuroraのインスタンスを増やし、インスタンスサイズも大きくしたものを利用していました。

そのままシフトしてRDS Proxy採用した場合で見積もると、年間300万円程度の上乗せ費用が発生することが分かりました。 決して小さな額ではないですが、BASEでは将来の接続安定性が得れるのであれば導入しようということで、検証を続けました。

しかし導入後、力技分のインスタンスを減らし、インスタンスサイズも落とす、という調整ができたことで、結果的にRDSの削減分も含めると 年間960万円もの削減 ができる計算になります。 これは非常に大きなメリットでした。

ピン留めが心配

RDS Proxyを導入するにあたって、一番気になっていたのはピン留めという現象です。

ピン留めを回避する より抜粋

RDS Proxy は、他のセッションに不適切なセッション状態の変化を検出すると、クライアント接続を特定の DB 接続に自動的にピン留めします。ピン留めにより、接続の再利用の有効性が低下します。

ピン留めは CloudWatchDatabaseConnectionsCurrentlySessionPinned メトリクスによって確認ができます。 検証の結果、BASEのアプリケーションからの接続では ピン留めが全く発生しませんでした。 BASEではCakePHPをフレームワークとして利用していますが、PHPは一般的に(ミドルウェアなどで接続プーリングを実装していない限り)リクエストのたびに接続・切断を繰り返すため、ピン留めの影響を全く受けなかったのではないかと推測しています。

ちなみに、この悪魔の証明とも言えそうなメトリクスに表示されないピン留めをどのように検証したかというと、まずRDS Proxy経由でmysqlクライアントにより接続し、SETステートメントやLOCK TABLE 操作をすることにより無理やりピン留めを発生させ、メトリクスとしては検知できることを確認します。 その後、QAのリグレッションテストを通したときにピン留めメトリクスが検出されないことを確認することで発生していないと結論づけました。 (実際、本番リリース後もこのメトリクスはほぼ検知されませんでした)

負荷試験について

BASEでは、負荷試験用のDEV環境があります。 今回は、その環境で動かすアプリケーションからのDB接続を、

 (A) RDS直接接続した場合(現状の環境想定)
 (B) RDS Proxy経由し接続した場合

で比較し、検証しました。

検証する軸としては以下の2軸

 (1) インスタンスタイプによる調整
 (2) スクリプトによりアクセス数を調整

まず (A) の限界値 を見極めます。 (1) を本番と同じものに固定し、(2) のアクセス数を上げていき、エラーが出る限界値をだしました。

次に、(B)の限界値 を各軸で上下させることで出していきます。 (2)を固定にして、(1)を下げていきます。 結果、インスタンスタイプを1/4 サイズに落としてもエラーが出ないという好成績 に。

これ以上インスタンスタイプを落とし続けても意味がないため、次に(1)インスタンスタイプを1/4の状態で固定し、(2)のアクセス数を増やし限界値を見極めます。 こちらも結果として、5倍以上のアクセス数にしてもDBのエラーが出ません でした。なんと、ボトルネックがRedisやApacheなど他に移ってしまい、限界値が計測できなかったくらい効果がありました。

接続切断を繰り返すというオーバーヘッドがそれほど大きかったのだろうという結論にいたりました。 PHPと相性がとても良かったという結果だと思います。

カスタムエンドポイント問題

RDS Proxyを導入すると、DBへの接続はRDS Proxyがcluster単位でコントロール をするようになります。

BASEでは、DBレプリケーションをclusterに依存し、Writer/Reader構造を利用して運用をしていました。 少し特殊な部分として、調査や分析などでサービスへの影響を与えないよう、Readerのうちの1つを分析用として利用し、他のDBをカスタムエンドポイントによりDB接続をコントロールしていました。(下記イメージのAS-ISの部分)

RDS Proxyを導入すると、すべてのDBがコントロール対象となってしまい、サービスに影響を与えず分析できるDBが確保できないという状態になるため少し問題です。 ここで私たちが取った解決策としては、Binlogによるレプリケーションした別clusterを自分たちで構築 しました。(下記イメージのTO-BE)

Binlogによるレプリケーション構築

余談ですが、実は我々がRDS Proxyの導入をする上で一番手を焼いたのが、この別clusterをつくるという部分でした。 スナップショットをとり、そのピンポイントのタイミングのポジションをとり、別clusterでBinlogのレプリケーションを開始する。この操作がダウンタイムなしでは難しく、メンテナンスコントロールなど必要でした。

ただこれは、BASEの独特な環境の問題なのでRDS Proxy導入で必須ではありません。

MySQLユーザのアクセスコントロール

RDS Proxyを導入する場合、RDS Proxyに対してMySQLへのアクセス情報(ユーザ/パスワード)をアタッチしておく必要があります。 これは、すべてのアクセスをRDS Proxy経由にした場合、MySQLに登録してあるユーザをすべてアタッチしておく必要があるということです。 ユーザを増やした場合、都度追加する必要があります。権限管理のためにユーザも増やすこともよくありますし、管理的にも煩雑でよくありません。

そこで私たちは小さくルールづけをして、コントロールすることにしました。以下のようなものです。

- SREがコントロールするユーザに関しては、RDS Proxyを経由してアクセスをする
  - 基本的にはサービスからのアクセス
- それ以外のユーザに関してはRDS Proxyを経由せずアクセスする

前者のユーザを増やした場合のみ、アタッチが必要となります。たとえアタッチが漏れたとしても動作検証時に発見できます。 一般ユーザ追加などの頻度の高い操作ではアタッチが不要です。DB操作開発者が増えてユーザ作成したが、DBにアクセスできない、なんでだぁ、といった混乱が防げます。

いざリリース

接続エンドポイントの切り替え

BASEでは、アプリケーションからのRDSへの接続は clusterのエンドポイント を利用せず、Route53経由することで行なっています。 リリースはこれを RDS ProxyのエンドポイントRoute53上で切り替えることで実施しました。

実行はアクセスの少ない深夜帯に、ダウンタイムなし(ユーザサイドから見たメンテナンス状態にせず)で切り替え完了。 切り替え後、モニタリングでも逆に心配になるくらい何も問題なく、レイテンシーが少し発生しているなーくらいで完了しました。

リバート

実際はしなかったのですが、リバートについてもRoute53上でエンドポイントを戻すだけというライトなものでしたので、今回は思い切った実施ができました。 心の安心は大切ですね。

(余談)切り替え後、接続が増え続け不安に

切り替え直後、DBへの接続が24時間のあいだ一方的に増え続けモニタリングしていて不安になりましたが、原因は以下の通りでした。

MaxIdleConnectionsPercent より抜粋

RDS プロキシは、データベース接続が使用されなくなった 24 時間後にその接続を閉じます。プロキシは、アイドル状態の最大接続数の設定値に関係なく、このアクションを実行します。

以下は、CloudWatchDatabaseConnections メトリクスなのですが、切り替え直後は24時間で接続がガクンと落ち、その後すこしづつ平準化されていく動きになっていることがわかると思います。

RDS Proxy切り替え後のDatabaseConnectionsメトリクス

導入後の測定・効果

ConnectionAttemptsが無風に

これまでの障害で接続サージが発生していた時は、新規接続を試す数である ConnectionAttempts というメトリクスが異常な値を示して いました。 RDS Proxyを導入する前までは、それなりの数値を示していたのですが、導入後は接続プールでコントロールされるため1桁になりました。 これも1つの安定接続を得たという証拠となります。 (グラフではゼロに見えますが実際は少しあります。それだけ減ったということです。)

RDS Proxy切り替え時のConnectionAttemptsメトリクス

接続が安定

前述しましたが、DatabaseConnections が非常に安定 しました(下図)。 導入前に比べて若干増加してるのは、RDS Proxyがアイドル状態になったコネクションを開いたまま保持するためと考えられます。 アイドル状態のコネクション保持最大値については、 MaxIdleConnectionsPercent という値で設定できますので環境に合わせて調整すると良いと思います。

RDS Proxy切り替え後のDatabaseConnectionsメトリクス

下図は、最大接続数のグラフなのですが、多くても 1,200 程度にしかなっていません。 RDSに設定してある max_connections12,000 なので、現状はかなりの余裕がある結果となっています。

RDS Proxy切り替え後の現在の最大接続数

Readerインスタンスが減らせた

上記の接続安定の結果を受け、接続に余裕をもてると分かったことで、Readerインスタンスを1つ削除できました。

削除した後もCPU使用量が少し増え各DBのネットワークスループット量が増えたくらいで、想定通りの結果となりました。 メモリ使用量は増えませんでした。

下図は削除前後のCPU使用量 (CPUUtilizationメトリクス) なのですが、削除後は 30-35% で遷移しており、これまで接続安定化のために無駄にサイズを上げてましたが、むしろ最適化できたのではないかと考えています。

Readerインスタンス削除時のCPUUtilizationメトリクス

削除して以降、問題は発生しておらず、費用削減に貢献しました。

Writerインスタンスのサイズを下げることができた

さらに接続安定の結果を受け、Writerインスタンスを半分にすることができました。

サイズを下げた後もReaderと同様でCPU使用量が少し増え たくらいで、問題は発生しておらず、費用削減に貢献しました。

ここで特筆すべきは、Writerインスタンスのサイズを下げたときの フェールオーバーの動き です。

サイズの変更は、まずcluster内に「切り替え先となるDB」を事前に作成し、フェールオーバーさせることで切り替え、切り替えた後に「切り替え元のDB」を削除したのですが、ここでもRDS Proxyのおもしろい動きを見てとることができました。

下図は、フェールオーバーさせたときの RDS Proxyのメトリクス です。 切り替えた瞬間、ClientConnections が急激に増え 、クライアントからの接続をRDS Proxyが待機させていることが見て取れます。 それと同時に DatabaseConnections が急激に増えている ことが分かります。これはおそらく待機させた接続をいっきに捌くためだと考えられます。

Writerインスタンス変更時のRDS Proxyのメトリクス

そして下図が QueryResponseLatency メトリクス です。 これを見ても最大で4秒ほど待機させていることが分かります。 つまりユーザから見ると、「あれ?ちょっと遅いな」程度で済んでいるということです。

Writerインスタンス変更時のQueryResponseLatencyメトリクス

もちろんエラーが全くなかった訳ではありません。Sentryにエラー 2006 MySQL server has gone away が出力されていましたが、かなり少なく通常のフェールオーバーと比べるとかなり小さな影響で済んだと言えると思います。

そして下図が Database側から見た DatabaseConnectionsメトリクス(1日以上経過したもの) です。 前述しましたが、フェールオーバー直後リクエストを捌くため一気に増え、またアイドル状態で24時間経過したものから解放されていくというのが見て取れるメトリクスになっています。 24時間後は接続も必要分だけとなり、安定しています。

Writerインスタンス変更時のDatabaseConnectionsメトリクス

RDS Proxyの特徴の一つである「(3) フェイルオーバー発生時の時間短縮などによるアプリケーションの可用性向上」を確認することができました。

レイテンシーが発生する

AWS公式では応答時間に 平均5ミリ秒/クエリ のネットワークレイテンシーが発生すると記載があります。

BASEでも例外なく、導入したことでAPIのレスポンスタイムが 0.05〜0.1秒 程度増加 しました。 BASEではRDS Proxy により多段になったことによる、仕方のないコストとして、将来のアクセス安定性を取り許容することにしました。

RDS Proxy導入後のレイテンシー

モニタリングの統合について

BASEでは監視サービスとして MackerelNew Relic を利用し、モニタリングしているのですが、RDS Proxyのメトリクスをそれらに 統合できませんでした。統合することはできます(下に追記あり)

RDS Proxyは、まだまだ利用者が多いと言えず監視サービス側で適用されていないのだと思います。 今後、RDS Proxyの利用者が増えてくれば対応されてくる可能性もあるかもしれません。

以下が、各サービスのメトリクスになりますので、状況はこちらで確認ください。

2023/03/10 追記

上記ページのメトリクスは旧来からのAPI Pollingの対応表であり、新規の対応をしていないためだそうです。 CloudWatch Metric Streams での対応をすることで、統合することが可能です。 (New Relic の担当者様からご連絡いただきました。ありがたい。)

導入後に発生した障害

ここは検証で発見できなかったBASEで発生した障害(失敗談)になります。 つまり環境に依存した内容も含まれますが、参考になればということで書いておきます。

長時間接続問題

BASEサービスには常時動作しているWorkerがあります。 こちらもPHPで実装されており、基本はプロセスが生きている間はDBへ接続されっぱなしになっているのですが、これが影響しエラーが発生しました。

RDS Proxy の導入前は、MySQLの設定値 wait_timeout = 28800 (8時間・デフォルト値) により長時間接続ができていたのですが、RDS Proxy の導入後は、前段のRDS Proxyサイドでタイムアウトにより切断されてしまうことが原因でした。

RDS Proxy には、アイドルクライアントの接続タイムアウト(IdleClientTimeout) という設定があり、これが30分 になっていたため、タイムアウトがこれまでよりも短時間で頻発するようになっていました。 今回はこれをMySQLの時間と同じ 8時間に設定する ことで解決しました。

デフォルト値が30分ですので、DB接続を長時間維持する部分がある場合は注意が必要です。

大量のレコードが発生するものは極端にレイテンシーが発生する

RDS Proxy導入後、あるページが特定ユーザで表示できない現象が発生しました。

原因調査した結果、SQLクエリの結果が大量レコードを返すことが分かり、RDS Proxy導入後、そのクエリだけが異常に遅くなっていることが分かりました。(数秒程度だったレスポンスが、数分かかるようになっていた)

転送レコード量が適切になるよう修正する ことでこれを解決しました。

公式に記述はないのですが、多段になったことで転送が量に応じて時間がかかるようになったのではないかと想像しています。 実際、DBクライアントなどで、大量レコードを返す同じクエリを、RDS直接続と、RDS Proxy経由接続した場合で比較すると明らかにレスポンスタイムが変わって来ます。

大量レコードを返す処理がある場合には注意ポイントだと思います。

クエリ数の調査

障害ではないですが、前述した通りクエリ数に応じてレイテンシーが発生することが分かっていたので、後追いで1リクエストのクエリ数の調査を実施しました。

調査の結果、N+1問題がちらほら発生していることが確認できたので、ひどいものに関しては即時対応を実施しました。 ここはRDS Proxy関係なくケアしていきたいところです。

RDS Proxy 自体の設定変更は実行中も導入ユーザへの影響はない

上記に記載したような、いくつかのトラブルが発生し、RDS Proxyの設定を変更したい場合がありました。 アイドルクライアントの接続タイムアウト(IdleClientTimeout) の調整や、RDS Proxyの ログ記録を有効 にしたい場合などです。 AWSサイドにも確認をしましたが、これらの変更はダウンタイムなしで切り替わるため、気軽に変更しても大丈夫のようです。

まとめ

BASE が RDS Proxy を導入することで得たメリット・デメリットをまとめると以下のようになります。

メリット

  • MySQLへの接続が超安定
    • ボトルネックが別に移ったと言っても過言ではないほどの安定接続を獲得できた
  • 費用的なメリットあるかも
    • 本来、RDS Proxyにはコストがかかるが、RDSのインスタンスを減らすことができたため、実質コストダウンになった
  • PHPとの相性が非常によかった
    • 毎アクセスで接続切断を繰り返すような言語やアプリケーション(PHPサーバーレスアプリケーション など)とは相性が良い可能性あり

デメリット(今後の課題や許容した副作用)

  • 若干のレイテンシーが発生する
    • BASEではRDS Proxyを挟む分、仕方のないコストと考えた
    • プロジェクトによっては許容できない可能性もあるので注意が必要
  • 現状は監視サービスでモニタリング統合ができない

BASEではRDS Proxyを導入することで、安定した接続ができるようになり、今後増えていくであろうアクセスを迎え入れる準備もでき、非常に大きなメリットがあったと考えています。 この記事が、皆様のRDS Proxy導入検討の参考になれば嬉しいです。

以上、BASEでのRDS Proxy導入レポートでした。

最後に

BASEでは、このように提供するサービスを利用してくださる購入者やショップオーナーの皆様のことを第一に考えて、様々な角度からサービスを共に発展させていく仲間を募集しております。 カジュアル面談も実施しておりますので、ぜひお気軽にお問い合わせください。

https://herp.careers/v1/base/WJELx9x0uPU5herp.careers